当前位置:网站首页>Android平臺Camera開發實踐指南,2021吊打面試官系列

Android平臺Camera開發實踐指南,2021吊打面試官系列

2022-01-15 02:37:39 mb61c1dbbb44788

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官方文檔

1.7 結束視頻錄制

結束視頻錄制也很簡單,只需要調用mediaRecorder.stop()方法即可。

mediaRecorder.stop();

此外,如果不再使用相機,也要注意釋放相機資源。

以上便是Camera的全部內容,還是比較簡單的,下面我們接著來講Camera2的相關內容,注意體會兩者的區別。

二 Camera2實踐指南

Camera2 API中主要涉及以下幾個關鍵類:

  • CameraManager:攝像頭管理器,用於打開和關閉系統攝像頭
  • CameraCharacteristics:描述攝像頭的各種特性,我們可以通過CameraManager的getCameraCharacteristics(@NonNull String cameraId)方法來獲取。
  • CameraDevice:描述系統攝像頭,類似於早期的Camera。
  • CameraCaptureSession:Session類,當需要拍照、預覽等功能時,需要先創建該類的實例,然後通過該實例裏的方法進行控制(例如:拍照 capture())。
  • CaptureRequest:描述了一次操作請求,拍照、預覽等操作都需要先傳入CaptureRequest參數,具體的參數控制也是通過CameraRequest的成員變量來設置。
  • CaptureResult:描述拍照完成後的結果。

Camera2拍照流程如下所示:

Android平臺Camera開發實踐指南,2021吊打面試官系列_Android

開發者通過創建CaptureRequest向攝像頭發起Capture請求,這些請求會排成一個隊列供攝像頭處理,攝像頭將結果包裝在CaptureMetadata中返回給開發者。整個流程建立在一個CameraCaptureSession的會話中。

2.1 打開相機

打開相機之前,我們首先要獲取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裏面的參數,主要用到的有以下幾個:

  • LENS_FACING:前置攝像頭(LENS_FACING_FRONT)還是後置攝像頭(LENS_FACING_BACK)。
  • SENSOR_ORIENTATION:攝像頭拍照方向。
  • FLASH_INFO_AVAILABLE:是否支持閃光燈。
  • CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:獲取當前設備支持的相機特性。

注:事實上,在各個廠商的的Android設備上,Camera2的各種特性並不都是可用的,需要通過characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)方法 來根據返回值來獲取支持的級別,具體說來:

  • INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方比特的硬件支持,允許手動控制全高清的攝像、支持連拍模式以及其他新特性。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,這個需要單獨查詢。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:所有設備都會支持,也就是和過時的Camera API支持的特性是一致的。

利用這個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)方法,如你所見,它有三個參數:

  • String cameraId:攝像頭的唯一ID。
  • CameraDevice.StateCallback callback:攝像頭打開的相關回調。
  • Handler handler:StateCallback需要調用的Handler,我們一般可以用當前線程的Handler。

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();
}
};

2.2 關閉相機

通過上面的描述,關閉就很簡單了。

//關閉CameraDevice
cameraDevice.close();

2.3 開啟預覽

Camera2都是通過創建請求會話的方式進行調用的,具體說來:

  1. 調用mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)方法創建CaptureRequest,調用
  2. mCameraDevice.createCaptureSession()方法創建CaptureSession。

CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)

createCaptureRequest()方法裏參數templateType代錶了請求類型,請求類型一共分為六種,分別為:

  • TEMPLATE_PREVIEW:創建預覽的請求
  • TEMPLATE_STILL_CAPTURE:創建一個適合於靜態圖像捕獲的請求,圖像質量優先於幀速率。
  • TEMPLATE_RECORD:創建視頻錄制的請求
  • TEMPLATE_VIDEO_SNAPSHOT:創建視視頻錄制時截屏的請求
  • TEMPLATE_ZERO_SHUTTER_LAG:創建一個適用於零快門延遲的請求。在不影響預覽幀率的情况下最大化圖像質量。
  • TEMPLATE_MANUAL:創建一個基本捕獲請求,這種請求中所有的自動控制都是禁用的(自動曝光,自動白平衡、自動焦點)。

createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)

createCaptureSession()方法一共包含三個參數:

  • List outputs:我們需要輸出到的Surface列錶。
  • CameraCaptureSession.StateCallback callback:會話狀態相關回調。
  • Handler handler:callback可以有多個(來自不同線程),這個handler用來區別那個callback應該被回調,一般寫當前線程的Handler即可。

關於CameraCaptureSession.StateCallback裏的回調方法:

  • onConfigured(@NonNull CameraCaptureSession session); 攝像頭完成配置,可以處理Capture請求了。
  • onConfigureFailed(@NonNull CameraCaptureSession session); 攝像頭配置失敗
  • onReady(@NonNull CameraCaptureSession session); 攝像頭處於就緒狀態,當前沒有請求需要處理。
  • onActive(@NonNull CameraCaptureSession session); 攝像頭正在處理請求。
  • onClosed(@NonNull CameraCaptureSession session); 會話被關閉
  • onSurfacePrepared(@NonNull CameraCaptureSession session, @NonNull Surface surface); Surface准備就緒

理解了這些東西,創建預覽請求就十分簡單了。

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));
}

};

2.4 關閉預覽

關閉預覽就是關閉當前預覽的會話,結合上面開啟預覽的內容,具體實現如下:

if (captureSession != null) {
captureSession.close();
try {
captureSession.abortCaptures();
} catch (Exception ignore) {
} finally {
captureSession = null;
}
}

2.5 拍照

拍照具體來說分為三步:

  1. 對焦

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();
}
}
}
};

  1. 拍照

我們定義了一個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”);
}
}

  1. 取消對焦

拍完照片後,我們還要解鎖相機焦點,讓相機恢複到預覽狀態。

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”);
}

2.6 開始視頻錄制

//先關閉預覽,因為需要添加一個預覽輸出的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使用,還是比較簡單的。

2.7 結束視頻錄制

結束視頻錄制主要也是關閉會話以及釋放一些資源,具體說來:

  1. 關閉預覽會話
  2. 停止mediaRecorder
  3. 釋放mediaRecorder

//關閉預覽會話
if (captureSession != null) {
captureSession.close();
try {
captureSession.abortCaptures();
} catch (Exception ignore) {
} finally {
captureSession = null;
}
}

最後為了幫助大家深刻理解Android相關知識點的原理以及面試相關知識,這裏放上相關的我搜集整理的24套騰訊、字節跳動、阿裏、百度2020-2021面試真題解析,我把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包知識脈絡 + 諸多細節

還有?高級架構技術進階腦圖、Android開發面試專題資料?幫助大家學習提昇進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習。

Android平臺Camera開發實踐指南,2021吊打面試官系列_程序員_02

Android平臺Camera開發實踐指南,2021吊打面試官系列_程序員_03

Android平臺Camera開發實踐指南,2021吊打面試官系列_移動開發_04

點擊:

 Android架構視頻+BAT面試專題PDF+學習筆記》即可免費獲取~

網上學習 Android的資料一大堆,但如果學到的知識不成體系,遇到問題時只是淺嘗輒止,不再深入研究,那麼很難做到真正的技術提昇。希望這份系統化的技術體系對大家有一個方向參考。

版权声明
本文为[mb61c1dbbb44788]所创,转载请带上原文链接,感谢
https://chowdera.com/2022/01/202201150232489574.html

随机推荐