android camera2 API流程分析
Android camera2 API流程分析
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)
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中形成圖片。
三、錄影流程分析(參考Camera2Video)
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<Surface> surfaces = new ArrayList<Surface>();
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開始幀輸出到指定的