Android5.0之后,新推出来了一个类,android.hardware.camera2,与原来的camera的类实现照相和拍视频的流程有所不同,原来的camera的类并没有深入分析。在做项目的时候,由于需要涉及到这方面的知识,自己学了一下。由于本人英文也不是很优秀,看着看着还要看前人的总结。这个是在半年前就简单总结了一下,现在po上来。如有错误,敬请指教!
Camera2流程示意图:
CameraManager:管理所有的摄像头(CameraDevice)设备的管理者,打开摄像头等功能。
CameraDevice:一个手机设备一般有两个摄像头(CameraDevice),前置和后置。该类通过CameraCharacteristics对象提供摄像头的硬件信息,配置信息和输出参数等。
CameraCaptureSession:通过CameraDevice 中createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)创建一个CaptureSession会话,所有的CaptureRequest和返回的data都在这个会话中进行。其中,该类中的capture (CaptureRequest request, CameraCaptureSession.CaptureCallback listener, Handler handler)的功能是捕获一次(one-shot),一般用于照相setRepeatingRequest (CaptureRequest request, CameraCaptureSession.CaptureCallbacklistener, Handler handler)是不停的发出capture的请求,也就是一直在捕获画面,一般用于捕获画面输出至预览界面或者录制视频。Capture()比setRepeatingRequest ()优先级高,当在setRepeatingRequest 时进行Capture,会先处理Capture,然后继续setRepeatingRequest 。(PS:可以根据平时使用相机时,首先我们看到的预览界面是setRepeatingRequest 显示出来的,当点击拍照时执行Capture,然后又出现预览界面继续实行setRepeatingRequest )。
CameraRequest:request中定义了照相效果的一些参数,并且必须使用addTarget()函数为这个request添加一个target surface,在最后CameraDevice返回的数据送到这个target surface中。在android camera2的API文档中,这个target surface可以是Surface View,Surface Texture,将返回的数据传递到预览界面中;还可以是MediaRecorder或mageReader,将返回的数据传给这两个类,进行进一步处理,形成视频文件或者图片。
TotalCaptureResult:继承CaptureResult类,CaptureResult继承CameraMetadata类。包含camera device的状态信息。
Camera2Basic在显示预览界面和拍照时创建了一个session,两个request,mPreviewRequest和captureBuilder.build()分别将数据返回给预览界面和Image。
显示preview的代码:
private void openCamera(int width, int height) {
setUpCameraOutputs(width, height);
configureTransform(width, height);
Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);//根据mCameraId打开前置或者后置摄像头
//mBackgroundHandler是处理打开摄像头的线程
//mStateCallback打开摄像头后,进入这个回调函数
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {//若成功打开,进入这个函数
// This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();//创建显示预览界面的函数
}
//………
}
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);//target为surface,就是手机的界面
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),//拍照的session,注意这里有两个surface
//一个是手机的界面,一个是图片。
//也就是说,这个session形成的数据流,
//可以一个传向手机界面,一个形成图片
//具体看Caputre()或者SetRepeatingRequest函数里面的
//参数request的addtarget()里面的值
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == mCameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
try {
// Auto focus should be continuous for camera preview.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Flash is automatically enabled when necessary.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,//mPreviewRequest的target是手机界面的surface,就是形成预览
//因此需要setRepeatingRequest,持续捕获帧形成视频
mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//……….
}
CameraDevice.StateCallback:
当CameraDevice状态改变(打开或关闭)后调用该函数,一般在该函数执行如下功能:
创建CameraDevice.TEMPLATE_PREVIEW类型的previewRequest,设置addTarget()为preview的surface。
创建session,这个session有两个request,因此要把request的target surface都放到List中,createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)中的List为preview和ImageReader的surface。CameraCaptureSession.StateCallback里面进行setRepeatingCapture(),将捕获的画面显示在preview上。mCaptureCallback说是捕获完成后的回调函数,暂不分析。
拍照(捕获静态图像)的代码:
private void captureStillPicture() {
try {
final Activity activity = getActivity();
if (null == activity || null == mCameraDevice) {
return;
}
// This is the CaptureRequest.Builder that we use to take a picture.
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface()); //target是image
// Use the same AE and AF modes as the preview.
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// Orientation
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
CameraCaptureSession.CaptureCallback CaptureCallback
= new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
showToast("Saved: " + mFile);
unlockFocus();
}
};
mCaptureSession.stopRepeating();//停掉之前的setrepeating的持续不断的捕获
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);//capture的时候,数据流形成image
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
类似上述分析,不过此时request的类型cameraDevice.TEMPLATE_STILL_CAPTURE并且addTarget()的对象ImageReader,另外setRepeatingCapture换成了capture。即捕获一个frame,返回至ImageReader中形成图片。
private void openCamera(int width, int height) {
final Activity activity = getActivity();
if (null == activity || activity.isFinishing()) {
return;
}
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
String cameraId = manager.getCameraIdList()[0];//后置摄像头的id
// Choose the sizes for camera preview and video recording
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
width, height, mVideoSize);
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
configureTransform(width, height);
mMediaRecorder = new MediaRecorder();
manager.openCamera(cameraId, mStateCallback, null);//打开相机,进入回调函数
//……
}
private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
startPreview(); //进入这个函数
mCameraOpenCloseLock.release();
if (null != mTextureView) {
configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
}
}
//……
}
private void startPreview() {
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
return;
}
try {
setUpMediaRecorder();//设置mediarecoder的参数,具体的介绍看android developer文档
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);//此时request的参数是record
List surfaces = new ArrayList();
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewBuilder.addTarget(previewSurface);//target是预览界面
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);//target是mediarecorder
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
mPreviewSession = cameraCaptureSession;
updatePreview();//进入这个函数
}
//……….
}, mBackgroundHandler);}
//此为mediaRecoder的设置,具体见MediaRecorder
private void setUpMediaRecorder() throws IOException {
final Activity activity = getActivity();
if (null == activity) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setOutputFile(getVideoFile(activity).getAbsolutePath());
mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int orientation = ORIENTATIONS.get(rotation);
mMediaRecorder.setOrientationHint(orientation);
mMediaRecorder.prepare();
}
private void updatePreview() {
if (null == mCameraDevice) {
return;
}
try {
setUpCaptureRequestBuilder(mPreviewBuilder);
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);//session发送请求,持续捕获帧
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//进行上面设置之后,点击button,执行startRecordingVideo()函数,mMediaRecorder.start();开始录制视频
private void startRecordingVideo() {
try {
// UI
mButtonVideo.setText(R.string.stop);
mIsRecordingVideo = true;
// Start recording
mMediaRecorder.start();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
//再次点击button,停止录制。源代码会出现一些问题,应该先关闭capture之后再停止录制,具体问题在stackoverflow里面有写。
private void stopRecordingVideo() {
// UI
mIsRecordingVideo = false;
mButtonVideo.setText(R.string.record);
//modifying!!!!~~~~~android官方的demo里面在停止拍摄的时候会卡,所以改了一点
try {
// Abort all pending captures.
cameraCaptureSession.abortCaptures();
} catch (CameraAccessException e) {
e.printStackTrace();
}
// Stop recording
mMediaRecorder.stop();
mMediaRecorder.reset();
Activity activity = getActivity();
if (null != activity) {
Toast.makeText(activity, "Video saved: " + getVideoFile(activity),
Toast.LENGTH_SHORT).show();
}
startPreview();//最后执行这个函数,重新开始预览,准备录制视频
}
}
在Camera2Video中:只创建了一个session,一个request有两个target surface-MediaRecorder和Preview。SetRepeatingRequest()不停的捕获画面一方面显示在preview上,另一方面形成视频流。当MediaRecorder.start()时,开始录制,之前SetRepeatingRequest的帧(frame)抛弃掉,从start开始帧输出到指定的文件中。