12.3 Android中相机的使用
相机(Camera)是移动设备拍摄图像和视频信息的核心硬件,在移动设备中通常有一个甚至多个(比如,很多设备有前置和后置的两个相机)。在Android中,android.hardware.Camera类是为开发者操纵相机准备的,使用Camera.getCameraInfo函数,可以读取设备上所有相机的基本信息:
//读取移动设备上相机的数量
int numberOfCameras=Camera.getNumberOfCameras();
//依次获取各个相机的基本信息
CameraInfo cameraInfo=new CameraInfo();
for(int cameraId=0;cameraId<numberOfCameras;cameraId++){
Camera.getCameraInfo(i, cameraInfo);
if(cameraInfo.facing==CameraInfo.CAMERA_FACING_BACK){
…//处理后置相机
}else{
…//处理其他相机
}
}
其中,cameraId是不同相机的标识,记录这个标识,才能够对指定的相机进行操作。在Android中,每个相机的资源都是独占的,即任何时候都只能有一个应用程序对相机进行控制和操作。一旦被占用,其他应用都无法使用相机,直至前一个占用者释放相机资源。因此,在实际开发中,应用往往会在Activity.onResume函数中占用相机资源,在Activity.onPause函数中释放占用的相机,使得每个被切换到前台的界面组件都可以使用相机:
protected void onResume(){
super.onResume();
//打开默认相机,即第一个后置相机
mCamera=Camera.open();
…
}
protected void onPause(){
super.onPause();
…
//关闭打开的相机
mCamera.release();
mCamera=null;
}
当应用成功调用Camera.open函数占用了相机资源后,就使用相机进行拍照。整个拍照流程大抵可以分成两个步骤。首先,是图像预览,将相机当前所捕获的画面呈现给用户,以便取景拍摄。图像预览需要将相机对象与预览界面控件绑定,将相机获取到的预览图像快速地呈现出来。预览界面控件常使用android.view.SurfaceView[1],以便预览界面的呈现更为流畅:
//获取界面上的SurfaceView
SurfaceView surface=getSurfaceView(…);
//获取SurfaceView的事件对象,绑定回调函数
SurfaceHolder holder=surface.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback(){
public void surfaceCreated(SurfaceHolder holder){
//当SurfaceView构造完成后,会回调该函数,此时,可以建立绑定
try{
mCamera.setPreviewDisplay(holder);
}catch(IOException exception){
…
}
}
public void surfaceDestroyed(SurfaceHolder holder){
//当SurfaceView被销毁前回调该函数,此时,需要停止预览
mCamera.stopPreview();
}
public void surfaceChanged(SurfaceHolder holder,
int format, int w, int h){
//当SurfaceView尺寸变更后,会回调该函数,此时可以设置参数
//并开始预览
Camera.Parameters parameters=mCamera.getParameters();
parameters.setPreviewSize(A_WIDTH, A_HEIGHT);
mCamera.setParameters(parameters);
mCamera.startPreview();
}
});
在预览过程中,除了可以将预览图像直接绘制到屏幕上,也可以通过Camera.setPreviewCallback设置预览的回调函数,随时获取预览图像,进行操作或存储。值得注意的是,为了保证预览过程足够流畅,Android采取了YCrCb(Nv21)格式来存储预览图像数据,该格式的图片可以最小化预览图片所占用的内存空间。开发者需要根据需求对其进行转码或解析:
mCamera.setPreviewCallback(new PreviewCallback(){
void onPreviewFrame(byte[]data, Camera camera){
//data中默认存放着YCrCb Nv21格式的图像数据
//开发者可以根据需求对其进行转码,相关讨论可参见:
//http://chenweihuacwh.iteye.com/blog/571223
}
});
有了图像预览做基础,就可以选择时机调用Camera.takePicture函数进行拍照,拍照获得的图像信息,可以按照不同的压缩格式提供给开发者:
mCamera.takePicture(null, null, null,
new PictureCallback(){
void onPictureTaken(byte[]data, Camera camera){
//data中默认存放着JPG格式的图像数据
SaveToJPGFile(data);
…
}
});
相比之下,通过相机录制视频要略显复杂一些。使用相机录制视频,需要使用上一节介绍的MediaRecorder对象,将MediaRecorder对象与相机绑定在一起,就可以实现视频的录制了:
//获取相机并构造录音对象
camera=getCameraInstance();
mediaRecorder=new MediaRecorder();
//将相机绑定到录音对象上
camera.unlock();
mediaRecorder.setCamera(camera);
//初始化一些参数
//在Android 2.2以上版本,可以使用CamcorderProfile对象
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
…
绑定完成后,使用MediaRecorder录制视频的方式与前一节介绍的通过MediaRecorder录制音频的方式无异。特别需要注意的是,在录制开始前,务必要调用Camera.unlock函数将占用的相机对象解锁(unlock),使相机的控制权能够顺利交接给MediaRecorder对象使用,避免出现相机资源被占用的情况。同样,在录制完成析构了MediaRecorder对象后,需要调用Camera.lock函数将相机的控制权重新拿回来,继续使用相机:
//使用完成析构了MediaRecorder对象后,需要回收相机的控制权
mediaRecorder.reset();
mediaRecorder.release();
mediaRecorder=null;
camera.lock();
通过上述流程,可以实现最基本的通过相机拍照和录制视频。此外,Android为相机提供了大量的特征支持,比如支持对焦控制、支持缩放比率控制、支持模式调节等,甚至在新版的Android 4.0中,还提供了人脸识别、全景相片拍摄等高级功能,在实际开发中,需要根据需求,妥善地选择需要支持的相机功能[2]。
所以,在很多项目的实际开发中,应用并不一定要直接按照上述方式构建拍照和预览流程。如果开发者只是需要通过相机拍摄一张照片或者一段视频,可以直接发出Intent请求,使用专业的第三方摄像应用来完成拍照或视频录制的工作,从而降低应用的开发难度。比如,请求拍照,并从中获取结果的示例如下[3]:
//构建发起拍照请求的Intent对象
Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//由于拍摄的图片较大,因此可以为拍照应用指定一个文件地址,存放照片
//如果仅需要小尺寸的图(比如用于添加头像),可以不添加该Extra
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
//发出请求,并等待回调
startActivityForResult(intent, REQUEST_CODE);
…
//拍摄完成后,会回调该函数
protected void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode==REQUEST_CODE&&resultCode==RESULT_OK){
//读取图片地址,并进行处理
//如果仅需要小尺寸图片,可以从MediaStore.EXTRA_OUTPUT读取
image_uri=data.getData();
…
}
}
[1]关于SurfaceView的特点介绍,可以参见第7章的相关内容。
[2]Android所支持的相机功能非常丰富,并且随着SDK版本的提升,不断地进行完善,详情可以参见:http://developer.android.com/guide/topics/media/camera.html#camera-features。
[3]更多通过Intent请求拍摄照片或视频的相关内容,可以参见SDK:http://developer.android.com/guide/topics/media/camera.html#intents。