天天看点

Android多媒体应用开发系列(一) 拍照

辛苦堆砌,转载请注明出处,谢谢大家的支持。

       前段时间工作很忙,忙了也好,开始思考自己到底应该如何工作,如何生活。前段时间的项目主要围绕Android,MFC,Winform开展,每个项目基本上都进行过一轮架构调整,所以对这些方面都有了比较深刻的理解,最近闲下来了,有时间,把前段时间项目的东西做一些整理,方便查阅,也提供一个大家共同学习的平台。

       首先从Android的开始吧,比较热门嘛,主要是使用了Android多媒体开发的各个部分(Camera,Surface,MediaPlayer,MediaRecorder,MediaCodec等),知识也算系统了,基本都用到了,所以考虑写成一个系列,先从基本的拍照走起。讲原理贴代码想必大家也受够了,不如换一种轻松的方式,中间融合上开发的流程结合重构,能够更好地掌握技能。跳过创建项目等等,直接开始创作。

      Android拍照,就要使用相机Camera,核心的接口

android.hardware.Camera.takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg)
           

参数是三个回调,分别是shutter——快门回调,raw——没有压缩过的原始数据回调,jpeg——返回jpeg数据的回调

这里有一个小发现,如果将shutter设置为null,将不会发出拍照的“咔嚓”声,有兴趣的可以尝试一下。

       现在知道了要使用的接口,还有许多准备工作要做,但是好不容易查到的接口该如何是好,先封装函数吧,留着用(相机记得要释放)

public class MainActivity extends AppCompatActivity {
	private Camera mCamera;
	...

	@Override
	protected void onStart() {
		super.onStart();
		mCamera = Camera.open();
	}


	@Override
	protected void onStop() {
		if (mCamera != null) {
			mCamera.release();
		}
		super.onStop();
	}
	...

	private void takePicture() {
		mCamera.takePicture(null, null, null);
	}
}
           

清单文件中添加使用Camera的权限

<!--suppress DeprecatedClassUsageInspection -->
<uses-feature android:name="android.hardware.Camera" />

<uses-permission android:name="android.permission.CAMERA" />
           

       现在有点空虚了?不知道下一步该做什么了?好吧,那就先把回调填充上,shutter是要添加的,个人比较喜欢那种声音,jpeg也是需要的,我想把图片保存为jpeg格式,添加接口实现

public class MainActivity extends AppCompatActivity implements Camera.ShutterCallback, Camera.PictureCallback { 
	...
	private void takePicture() {
		mCamera.takePicture(this, null, this);
	}
	
	@Override
	public void onPictureTaken(byte[] data, Camera camera) {
	}


	@Override
	public void onShutter() {
	}
}
           

          接下来,该做什么?我们的PictureCallback回调给我们数据,但是数据源在哪里?没有数据源,当然无法回调数据,这时候要用到一个好东西了——SurfaceView,查看google的文档你会发现,Surface,SurfaceView和SurfaceHolder是桃园结义三兄弟,Surface负责图像的绘制,是一个很底层的类,所以我们一般不直接使用它,而是通过SurfaceHolder来间接操作Surface,SurfaceView则是一个视图类,是直接呈现给用户的视图,承载用户的操作以及呈现Surface上绘制的内容。我们需要做的就是,先在视图层添加SurfaceView再说。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.yjp.takepicture.MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/takePictureButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_alignBottom="@id/surfaceView"
        android:text="@string/takePictureButtonText"/>
</RelativeLayout>
           

代码中

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
	
	Button takePictureButton = (Button) findViewById(R.id.takePictureButton);
	takePictureButton.setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {
			takePicture();
		}
	});
}
           

        现在项目已经可以运行了,但是屏幕黑黑的,点击拍照按钮没有反应,没关系,下面就是添加代码的部分了。

        先让我们的SurfaceView能够看到图像,只要我们清楚两点,图像的来源是Camera,而图像的呈现要靠SurfaceView,如何把两者关联起来才是关键,前面有提到,Surface是负责绘制的,我们只要能把Camera采集的数据送给Surface,就能显示出摄像头采集到的数据了, 而我们不直接操作Surface,需要借助于SurfaceHolder才能操作Surface,那么我们就需要借助于SurfaceHolder将Camera和SurfaceView关联起来。

public class MainActivity extends AppCompatActivity
        implements Camera.ShutterCallback, Camera.PictureCallback, SurfaceHolder.Callback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //配置SurfaceView
        //setType使用外来数据源
        //设置SurfaceHolder.Callback
        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceHolder.addCallback(this);

        ...
    }

	...

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            //surface创建成功能够拿到回调的holder
            //holder中包含有成功创建的Surface
            //从而交给摄像机预览使用
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //surface的尺寸发生变化
        //配置预览参数,如分辨率等
        //这里使用的分辨率简单选取了支持的预览分辨率的第一项
        //网上可以查找对应的优选算法
        Camera.Parameters parameters = mCamera.getParameters();
        List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
        Camera.Size selected = sizes.get(0);
        parameters.setPreviewSize(selected.width, selected.height);
		parameters.setPictureSize(selected.width, selected.height);

        //给摄像机设置参数,开始预览
        mCamera.setParameters(parameters);
        mCamera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
}
           

运行程序,可以实现预览。接下来只剩下拍照了,只要实现前面提到的PictureCallback jpeg的onPictureTaken即可。

@Override
public void onPictureTaken(byte[] data, Camera camera) {
	try {
		FileOutputStream out;
		String filename = new Date().getTime() + ".jpg";
		String filePathname = Environment.getExternalStorageDirectory() + "/"
				+ Environment.DIRECTORY_PICTURES + "/" + filename;
		out = new FileOutputStream(filePathname);
		out.write(data);
		out.flush();
		out.close();

		//重新启动预览
		mCamera.startPreview();
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
}
           

拍照完成时,相机会停止预览,记得恢复预览。

由于需要写入文件,在清单文件中添加存储访问权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
           

运行程序,点击拍照按钮,现在就会在对应的目录下生成照片了。

      最后,修改MainActivity横屏无标题栏

<activity
	android:name=".MainActivity"
	android:screenOrientation="landscape"
	android:theme="@style/Theme.AppCompat.Light.NoActionBar">
	<intent-filter>
		<action android:name="android.intent.action.MAIN" />

		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>
           

至此,完成拍照功能。

       总结一下,拍照使用了Camera和SurfaceView,从而间接使用了SurfaceHolder和Surface,实现了Camera.ShutterCallback, Camera.PictureCallback, SurfaceHolder.Callback 三个接口,合理配置相机的接口调用顺序即可。

       下一篇文章将基于当前版本做一些重构,然后实现自动拍照和定时连拍。

源码已上传到 这里

Android多媒体应用开发系列(一) 拍照