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。