關於做者html
郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。java
文章目錄android
Android Camera 相關API也是Android生態碎片化最爲嚴重的一塊,首先Android自己就有兩套API,Android 5.0如下的Camera和Android 5.0以上的Camera2,並且 更爲嚴重的時,各家手機廠商都Camera2的支持程度也各不相同,這就致使咱們在相機開發中要花費很大精力來處理兼容性問題。git
相機開發的通常流程是什麼樣的?🤔程序員
相機開發通常須要注意哪些問題?🤔github
關於Camera/Camear2session
既然要解決這種兼容性問題,就要兩套並用,那是否是根據版原本選擇:Android 5.0 如下用Camera,Android 5.0以上用Camera2呢?🤔架構
事實上,這樣是不可取的。前面說過不一樣手機廠商對Camera2的支持程度各不相同,即使是Android 5.0 以上的手機,也存在對Camera2支持很是差的狀況,這個時候就要降級使用Camera,如何判斷對Camera的支持 程度咱們下面會說。框架
關於SurfaceView/TextureViewide
更多關於SurfaceView與TextureView區別的內容能夠參考這篇文章Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView.
那麼如何針對版本進行方案的選擇呢?🤔
官方的開源庫cameraview給出了方案:
既然要兩套並用,就要定義統一的接口,針對不一樣場景提供不一樣的實現,使用的時候也是根據不一樣的場景來建立不一樣的實例。
咱們不難發現,這個接口通常須要定義如下功能:
定義好了接口,咱們就有了思路,針對相機的具體特性實現相應的方案,那麼另外一個問題就出來了,相機在平常開發中通常做爲一個SDK的形式存在供各個業務方調用,那麼如何設計 出一個功能與UI相分離,高度可定製的相機SDK呢?🤔
答案就是利用Fragment,將各類點擊事件(點擊拍照、點擊切換攝像頭、點擊切換閃光模式等)對應的功能封裝在Fragment裏,業務方在用的時候能夠在Fragment之上蒙一層 UI(固然咱們也須要提供默認的實現),這樣就可讓功能和UI相分離,集成起來也很是的簡便。
相機SDK框架圖以下所示:
phoenix項目已經實現了這套方案,效果圖以下所示:
理解了總體的架構,咱們接着就來分析針對這套架構,Camera/Camera2分別該如何實現。
Camera API中主要涉及如下幾個關鍵類:
SurfaceView和普通的View相比有什麼區別呢?🤔
普通View都是共享一個Surface的,全部的繪製也都在UI線程中進行,由於UI線程還要處理其餘邏輯,所以對View的更新速度和繪製幀率沒法保證。這顯然不適合相機實時 預覽這種狀況,於是SurfaceView持有一個單獨的Surface,它負責管理這個Surface的格式、尺寸以及顯示位置,它的Surface繪製也在單獨的線程中進行,於是擁有更高 的繪製效率和幀率。
SurfaceHolder.Callback接口裏定義了三個函數:
打開相機以前咱們須要先獲取系統相機的相關信息。
//有多少個攝像頭
numberOfCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numberOfCameras; ++i) {
final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(i, cameraInfo);
//後置攝像頭
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
faceBackCameraId = i;
faceBackCameraOrientation = cameraInfo.orientation;
}
//前置攝像頭
else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
faceFrontCameraId = i;
faceFrontCameraOrientation = cameraInfo.orientation;
}
}
複製代碼
知道了相機相關信息,就能夠經過相機ID打開相機了。
camera = Camera.open(cameraId);
複製代碼
另外,打開相機之後你會得到一個Camera對象,從這個對象裏能夠獲取和設置相機的各類參數信息。
//獲取相機參數
camera.getParameters();
//設置相機參數
camera.getParameters();
複製代碼
常見的參數有如下幾種。
閃光燈配置參數,能夠經過Parameters.getFlashMode()接口獲取。
對焦模式配置參數,能夠經過Parameters.getFocusMode()接口獲取。
場景模式配置參數,能夠經過Parameters.getSceneMode()接口獲取。
關閉相機很簡單,只須要把相機釋放掉就能夠了。
camera.release();
複製代碼
Camera的預覽時經過SurfaceView的SurfaceHolder進行的,先經過,具體說來:
private void startPreview(SurfaceHolder surfaceHolder) {
try {
final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(currentCameraId, cameraInfo);
int cameraRotationOffset = cameraInfo.orientation;
//獲取相機參數
final Camera.Parameters parameters = camera.getParameters();
//設置對焦模式
setAutoFocus(camera, parameters);
//設置閃光模式
setFlashMode(mCameraConfigProvider.getFlashMode());
if (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_PHOTO
|| mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)
turnPhotoCameraFeaturesOn(camera, parameters);
else if (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_PHOTO)
turnVideoCameraFeaturesOn(camera, parameters);
final int rotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break; // Natural orientation
case Surface.ROTATION_90:
degrees = 90;
break; // Landscape left
case Surface.ROTATION_180:
degrees = 180;
break;// Upside down
case Surface.ROTATION_270:
degrees = 270;
break;// Landscape right
}
//根據前置與後置攝像頭的不一樣,設置預覽方向,不然會發生預覽圖像倒過來的狀況。
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
displayRotation = (cameraRotationOffset + degrees) % 360;
displayRotation = (360 - displayRotation) % 360; // compensate
} else {
displayRotation = (cameraRotationOffset - degrees + 360) % 360;
}
this.camera.setDisplayOrientation(displayRotation);
if (Build.VERSION.SDK_INT > 13
&& (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_VIDEO
|| mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)) {
// parameters.setRecordingHint(true);
}
if (Build.VERSION.SDK_INT > 14
&& parameters.isVideoStabilizationSupported()
&& (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_VIDEO
|| mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)) {
parameters.setVideoStabilization(true);
}
//設置預覽大小
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
parameters.setPictureSize(photoSize.getWidth(), photoSize.getHeight());
//設置相機參數
camera.setParameters(parameters);
//設置surfaceHolder
camera.setPreviewDisplay(surfaceHolder);
//開啓預覽
camera.startPreview();
} catch (IOException error) {
Log.d(TAG, "Error setting camera preview: " + error.getMessage());
} catch (Exception ignore) {
Log.d(TAG, "Error starting camera preview: " + ignore.getMessage());
}
}
複製代碼
關閉預覽很簡單,直接調用camera.stopPreview()便可。
camera.stopPreview();
複製代碼
拍照時經過調用Camera的takePicture()方法來完成的,
takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback postview, PictureCallback jpeg)
複製代碼
該方法有三個參數:
咱們通常用的就是最後一個,實現最後一個PictureCallback便可。
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] bytes, Camera camera) {
//存儲返回的圖像數據
final File pictureFile = outputPath;
if (pictureFile == null) {
Log.d(TAG, "Error creating media file, check storage permissions.");
return;
}
try {
FileOutputStream fileOutputStream = new FileOutputStream(pictureFile);
fileOutputStream.write(bytes);
fileOutputStream.close();
} catch (FileNotFoundException error) {
Log.e(TAG, "File not found: " + error.getMessage());
} catch (IOException error) {
Log.e(TAG, "Error accessing file: " + error.getMessage());
} catch (Throwable error) {
Log.e(TAG, "Error saving file: " + error.getMessage());
}
}
});
複製代碼
拍照完成後若是還要繼續拍照則調用camera.startPreview()繼續開啓預覽,不然關閉預覽,釋放相機資源。
視頻的錄製時經過MediaRecorder來完成的。
if (prepareVideoRecorder()) {
mediaRecorder.start();
isVideoRecording = true;
uiHandler.post(new Runnable() {
@Override
public void run() {
videoListener.onVideoRecordStarted(videoSize);
}
});
}
複製代碼
MediaRecorder主要用來錄製音頻和視頻,在使用以前要進行初始化和相關參數的設置,以下所示:
protected boolean preparemediaRecorder() {
mediaRecorder = new MediaRecorder();
try {
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
//輸出格式
mediaRecorder.setOutputFormat(camcorderProfile.fileFormat);
//視頻幀率
mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
//視頻大小
mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
//視頻比特率
mediaRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
//視頻編碼器
mediaRecorder.setVideoEncoder(camcorderProfile.videoCodec);
//音頻編碼率
mediaRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
//音頻聲道
mediaRecorder.setAudioChannels(camcorderProfile.audioChannels);
//音頻採樣率
mediaRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
//音頻編碼器
mediaRecorder.setAudioEncoder(camcorderProfile.audioCodec);
File outputFile = outputPath;
String outputFilePath = outputFile.toString();
//輸出路徑
mediaRecorder.setOutputFile(outputFilePath);
//設置視頻輸出的最大尺寸
if (mCameraConfigProvider.getVideoFileSize() > 0) {
mediaRecorder.setMaxFileSize(mCameraConfigProvider.getVideoFileSize());
mediaRecorder.setOnInfoListener(this);
}
//設置視頻輸出的最大時長
if (mCameraConfigProvider.getVideoDuration() > 0) {
mediaRecorder.setMaxDuration(mCameraConfigProvider.getVideoDuration());
mediaRecorder.setOnInfoListener(this);
}
mediaRecorder.setOrientationHint(getVideoOrientation(mCameraConfigProvider.getSensorPosition()));
//準備
mediaRecorder.prepare();
return true;
} catch (IllegalStateException error) {
Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + error.getMessage());
} catch (IOException error) {
Log.e(TAG, "IOException preparing MediaRecorder: " + error.getMessage());
} catch (Throwable error) {
Log.e(TAG, "Error during preparing MediaRecorder: " + error.getMessage());
}
releasemediaRecorder();
return false;
}
複製代碼
值得一提的是,平常的業務中常常對拍攝視頻的時長或者大小有要求,這個能夠經過mediaRecorder.setOnInfoListener()來處理,OnInfoListener會監聽正在錄製的視頻,而後咱們 能夠在它的回調方法裏處理。
@Override
public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED == what) {
//到達最大時長
} else if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED == what) {
//到達最大尺寸
}
}
複製代碼
更多關於MediaRecorder的介紹能夠參考MediaRecorder官方文檔。
結束視頻錄製也很簡單,只須要調用mediaRecorder.stop()方法便可。
mediaRecorder.stop();
複製代碼
此外,若是再也不使用相機,也要注意釋放相機資源。
以上即是Camera的所有內容,仍是比較簡單的,下面咱們接着來說Camera2的相關內容,注意體會二者的區別。
Camera2 API中主要涉及如下幾個關鍵類:
Camera2拍照流程以下所示:
開發者經過建立CaptureRequest向攝像頭髮起Capture請求,這些請求會排成一個隊列供攝像頭處理,攝像頭將結果包裝在CaptureMetadata中返回給開發者。整個流程創建在一個CameraCaptureSession的會話中。
打開相機以前,咱們首先要獲取CameraManager,而後獲取相機列表,進而獲取各個攝像頭(主要是前置攝像頭和後置攝像頭)的參數。
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
final String[] ids = mCameraManager.getCameraIdList();
numberOfCameras = ids.length;
for (String id : ids) {
final CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
final int orientation = characteristics.get(CameraCharacteristics.LENS_FACING);
if (orientation == CameraCharacteristics.LENS_FACING_FRONT) {
faceFrontCameraId = id;
faceFrontCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
frontCameraCharacteristics = characteristics;
} else {
faceBackCameraId = id;
faceBackCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
backCameraCharacteristics = characteristics;
}
}
} catch (Exception e) {
Log.e(TAG, "Error during camera initialize");
}
複製代碼
Camera2與Camera同樣也有cameraId的概念,咱們經過mCameraManager.getCameraIdList()來獲取cameraId列表,而後經過mCameraManager.getCameraCharacteristics(id) 獲取每一個id對應攝像頭的參數。
關於CameraCharacteristics裏面的參數,主要用到的有如下幾個:
注:事實上,在各個廠商的的Android設備上,Camera2的各類特性並不都是可用的,須要經過characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)方法 來根據返回值來獲取支持的級別,具體說來:
利用這個INFO_SUPPORTED_HARDWARE_LEVEL參數,咱們能夠來判斷是使用Camera仍是使用Camera2,具體方法以下:
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean hasCamera2(Context mContext) {
if (mContext == null) return false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false;
try {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
String[] idList = manager.getCameraIdList();
boolean notFull = true;
if (idList.length == 0) {
notFull = false;
} else {
for (final String str : idList) {
if (str == null || str.trim().isEmpty()) {
notFull = false;
break;
}
final CameraCharacteristics characteristics = manager.getCameraCharacteristics(str);
final int supportLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
notFull = false;
break;
}
}
}
return notFull;
} catch (Throwable ignore) {
return false;
}
}
複製代碼
更多ameraCharacteristics參數,能夠參見CameraCharacteristics官方文檔。
打開相機主要調用的是mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler)方法,如你所見,它有三個參數:
mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler);
複製代碼
上面咱們提到了CameraDevice.StateCallback,它是攝像頭打開的一個回調,定義了打開,關閉以及出錯等各類回調方法,咱們能夠在 這些回調方法裏作對應的操做。
private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
//獲取CameraDevice
mcameraDevice = cameraDevice;
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
//關閉CameraDevice
cameraDevice.close();
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
//關閉CameraDevice
cameraDevice.close();
}
};
複製代碼
經過上面的描述,關閉就很簡單了。
//關閉CameraDevice
cameraDevice.close();
複製代碼
Camera2都是經過建立請求會話的方式進行調用的,具體說來:
CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) 複製代碼
createCaptureRequest()方法裏參數templateType表明了請求類型,請求類型一共分爲六種,分別爲:
createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
複製代碼
createCaptureSession()方法一共包含三個參數:
關於CameraCaptureSession.StateCallback裏的回調方法:
理解了這些東西,建立預覽請求就十分簡單了。
previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(workingSurface);
//注意這裏除了預覽的Surface,咱們還添加了imageReader.getSurface()它就是負責拍照完成後用來獲取數據的
mCameraDevice.createCaptureSession(Arrays.asList(workingSurface, imageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.d(TAG, "Fail while starting preview: ");
}
}, null);
複製代碼
能夠發現,在onConfigured()裏調用了cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler),這樣咱們就能夠 持續的進行預覽了。
注:上面咱們說了添加了imageReader.getSurface()它就是負責拍照完成後用來獲取數據,具體操做就是爲ImageReader設置一個OnImageAvailableListener,而後在它的onImageAvailable() 方法裏獲取。
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//當圖片可獲得的時候獲取圖片並保存
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
}
};
複製代碼
關閉預覽就是關閉當前預覽的會話,結合上面開啓預覽的內容,具體實現以下:
if (captureSession != null) {
captureSession.close();
try {
captureSession.abortCaptures();
} catch (Exception ignore) {
} finally {
captureSession = null;
}
}
複製代碼
拍照具體來講分爲三步:
try {
//相機對焦
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
//修改狀態
previewState = STATE_WAITING_LOCK;
//發送對焦請求
captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
} catch (Exception ignore) {
}
複製代碼
咱們定義了一個CameraCaptureSession.CaptureCallback來處理對焦請求返回的結果。
private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
//等待對焦
final Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState == null) {
//對焦失敗,直接拍照
captureStillPicture();
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState
|| CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState
|| CaptureResult.CONTROL_AF_STATE_INACTIVE == afState
|| CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN == afState) {
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
previewState = STATE_PICTURE_TAKEN;
//對焦完成,進行拍照
captureStillPicture();
} else {
runPreCaptureSequence();
}
}
}
};
複製代碼
咱們定義了一個captureStillPicture()來進行拍照。
private void captureStillPicture() {
try {
if (null == mCameraDevice) {
return;
}
//構建用來拍照的CaptureRequest
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(imageReader.getSurface());
//使用相同的AR和AF模式做爲預覽
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//設置方向
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getPhotoOrientation(mCameraConfigProvider.getSensorPosition()));
//建立會話
CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
Log.d(TAG, "onCaptureCompleted: ");
}
};
//中止連續取景
captureSession.stopRepeating();
//捕獲照片
captureSession.capture(captureBuilder.build(), CaptureCallback, null);
} catch (CameraAccessException e) {
Log.e(TAG, "Error during capturing picture");
}
}
複製代碼
拍完照片後,咱們還要解鎖相機焦點,讓相機恢復到預覽狀態。
try {
//重置自動對焦
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
//相機恢復正常的預覽狀態
previewState = STATE_PREVIEW;
//打開連續取景模式
captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
} catch (Exception e) {
Log.e(TAG, "Error during focus unlocking");
}
複製代碼
//先關閉預覽,由於須要添加一個預覽輸出的Surface,也就是mediaRecorder.getSurface()
closePreviewSession();
//初始化MediaRecorder,設置相關參數
if (preparemediaRecorder()) {
final SurfaceTexture texture = Camera2Manager.this.texture;
texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());
try {
//構建視頻錄製aptureRequest
previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
final List<Surface> surfaces = new ArrayList<>();
//設置預覽Surface
final Surface previewSurface = workingSurface;
surfaces.add(previewSurface);
previewRequestBuilder.addTarget(previewSurface);
//設置預覽輸出Surface
workingSurface = mediaRecorder.getSurface();
surfaces.add(workingSurface);
previewRequestBuilder.addTarget(workingSurface);
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
captureSession = cameraCaptureSession;
previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
//持續發送Capture請求,實現實時預覽。
captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler);
} catch (Exception e) {
}
try {
//開始錄像
mediaRecorder.start();
} catch (Exception ignore) {
Log.e(TAG, "mediaRecorder.start(): ", ignore);
}
isVideoRecording = true;
uiHandler.post(new Runnable() {
@Override
public void run() {
cameraVideoListener.onVideoRecordStarted(videoSize);
}
});
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.d(TAG, "onConfigureFailed");
}
}, backgroundHandler);
} catch (Exception e) {
Log.e(TAG, "startVideoRecord: ", e);
}
}
複製代碼
關於MediaRecorder上面講Camera的時候咱們就已經說過,這裏再也不贅述。
以上即是視頻錄製的所有內容,就是簡單的API使用,仍是比較簡單的。
結束視頻錄製主要也是關閉會話以及釋放一些資源,具體說來:
//關閉預覽會話
if (captureSession != null) {
captureSession.close();
try {
captureSession.abortCaptures();
} catch (Exception ignore) {
} finally {
captureSession = null;
}
}
//中止mediaRecorder
if (mediaRecorder != null) {
try {
mediaRecorder.stop();
} catch (Exception ignore) {
}
}
//釋放mediaRecorder
try {
if (mediaRecorder != null) {
mediaRecorder.reset();
mediaRecorder.release();
}
} catch (Exception ignore) {
} finally {
mediaRecorder = null;
}
複製代碼
以上即是Camera/Camera2實踐的相關內容,更多關於圖像、視頻處理的內容能夠參見phoenix項目。