序
想了下還是擠擠時間,把相機這基礎流程寫完吧,前面每篇寫的都還是挺耗時的(就是累了,想偷偷懶,哈哈哈哈),那接著前面的幾篇文章,給這一些列寫上一個中規中矩的結局吧~
APP層
以下是相機openCamera,configStream,startPreview,capture全過程的示例代碼,不用再分段演示了,注釋也寫的比較詳細了。
package com.example.simplecamera;import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceTexture;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.FrameLayout;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;public class MainActivity extends AppCompatActivity {// 權限請求碼private static final int REQUEST_CAMERA_PERMISSION = 100;// 相機相關變量private String mCameraId;private CameraDevice mCameraDevice;private CameraCaptureSession mCaptureSession;private CaptureRequest.Builder mPreviewRequestBuilder;private Size mPreviewSize;private ImageReader mImageReader;// UI組件private TextureView mTextureView;private Button mCaptureButton;private TextView mStatusText;// 后臺線程private HandlerThread mBackgroundThread;private Handler mBackgroundHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化UImTextureView = new TextureView(this);FrameLayout previewContainer = findViewById(R.id.preview_container);previewContainer.addView(mTextureView);mCaptureButton = findViewById(R.id.capture_button);mStatusText = findViewById(R.id.status_text);// 設置拍照按鈕點擊事件mCaptureButton.setOnClickListener(v -> takePicture());mCaptureButton.setEnabled(false);// 設置TextureView監聽mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}// SurfaceTexture監聽,用于處理預覽準備就緒private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {// 檢查權限if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},REQUEST_CAMERA_PERMISSION);return;}// 權限已授予,打開相機openCamera(width, height);}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return true;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};// 打開相機private void openCamera(int width, int height) {startBackgroundThread();// 獲取相機管理器CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);try {// 遍歷所有相機,選擇后置攝像頭for (String cameraId : manager.getCameraIdList()) {CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);// 只使用后置攝像頭Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {continue;}// 獲取可用的輸出尺寸StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {continue;}// 選擇合適的預覽尺寸mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),width, height);// 初始化ImageReader用于拍照mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);mCameraId = cameraId;break;}// 打開相機if (mCameraId != null) {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {return;}manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);} else {showStatus("未找到可用相機");}} catch (CameraAccessException e) {showStatus("打開相機失敗: " + e.getMessage());e.printStackTrace();}}// 相機狀態回調private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;createCameraPreviewSession();runOnUiThread(() -> {mCaptureButton.setEnabled(true);showStatus("相機已就緒");});}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {camera.close();mCameraDevice = null;runOnUiThread(() -> {mCaptureButton.setEnabled(false);showStatus("相機已斷開");});}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {camera.close();mCameraDevice = null;runOnUiThread(() -> {mCaptureButton.setEnabled(false);showStatus("相機錯誤: " + error);});}};// 創建相機預覽會話private void createCameraPreviewSession() {try {SurfaceTexture texture = mTextureView.getSurfaceTexture();if (texture == null) {throw new NullPointerException("TextureView為null");}// 設置預覽尺寸texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());// 創建預覽的SurfaceSurface surface = new Surface(texture);// 創建預覽請求mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewRequestBuilder.addTarget(surface);// 創建相機捕獲會話mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCaptureSession = session;try {// 設置自動對焦和自動曝光mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 開始預覽mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),null, mBackgroundHandler);} catch (CameraAccessException e) {showStatus("預覽失敗: " + e.getMessage());e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {showStatus("預覽配置失敗");}}, null);} catch (CameraAccessException e) {showStatus("創建會話失敗: " + e.getMessage());e.printStackTrace();}}// 拍照private void takePicture() {if (mCameraDevice == null || mCaptureSession == null) {Log.w(TAG, "Cannot take picture - camera not ready");return;}try {// 創建拍照請求final CaptureRequest.Builder captureBuilder =mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mImageReader.getSurface());// 設置自動對焦和曝光captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_START);captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 設置照片方向int rotation = getWindowManager().getDefaultDisplay().getRotation();captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);} catch (CameraAccessException e) {showStatus("拍照失敗: " + e.getMessage());e.printStackTrace();}}// 處理拍攝的圖片private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {// 在后臺處理圖片mBackgroundHandler.post(() -> {Image image = reader.acquireNextImage();if (image != null) {File imageFile = saveImageToFile(image);String message = imageFile != null ? "照片已保存: " + imageFile.getAbsolutePath() : "保存照片失敗";runOnUiThread(() -> showStatus(message));image.close();}});}};// 保存圖片到文件private File saveImageToFile(Image image) {ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);// 創建圖片文件String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());String fileName = "IMG_" + timeStamp + ".jpg";File storageDir = getExternalFilesDir(null);File imageFile = new File(storageDir, fileName);FileOutputStream output = null;try {output = new FileOutputStream(imageFile);output.write(bytes);return imageFile;} catch (IOException e) {e.printStackTrace();return null;} finally {if (output != null) {try {output.close();} catch (IOException e) {e.printStackTrace();}}}}// 選擇最佳的預覽尺寸private Size chooseOptimalSize(Size[] choices, int width, int height) {List<Size> bigEnough = new ArrayList<>();for (Size option : choices) {if (option.getHeight() == option.getWidth() * height / width &&option.getWidth() >= width && option.getHeight() >= height) {bigEnough.add(option);}}if (bigEnough.size() > 0) {return Collections.min(bigEnough, new CompareSizesByArea());} else {return choices[0];}}// 按面積比較尺寸的比較器private static class CompareSizesByArea implements Comparator<Size> {@Overridepublic int compare(Size lhs, Size rhs) {return Long.signum((long) lhs.getWidth() * lhs.getHeight() -(long) rhs.getWidth() * rhs.getHeight());}}// 獲取照片方向private int getOrientation(int rotation) {switch (rotation) {case Surface.ROTATION_0: return 90;case Surface.ROTATION_90: return 0;case Surface.ROTATION_180: return 270;case Surface.ROTATION_270: return 180;default: return 90;}}// 啟動后臺線程private void startBackgroundThread() {mBackgroundThread = new HandlerThread("CameraBackground");mBackgroundThread.start();mBackgroundHandler = new Handler(mBackgroundThread.getLooper());}// 停止后臺線程private void stopBackgroundThread() {mBackgroundThread.quitSafely();try {mBackgroundThread.join();mBackgroundThread = null;mBackgroundHandler = null;} catch (InterruptedException e) {e.printStackTrace();}}// 顯示狀態信息private void showStatus(String message) {runOnUiThread(() -> {mStatusText.setText(message);Toast.makeText(this, message, Toast.LENGTH_SHORT).show();});}// 權限請求結果處理@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUEST_CAMERA_PERMISSION) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 權限已授予,重新嘗試打開相機if (mTextureView.isAvailable()) {openCamera(mTextureView.getWidth(), mTextureView.getHeight());}} else {showStatus("需要相機權限才能使用應用");}}}@Overrideprotected void onResume() {super.onResume();startBackgroundThread();if (mTextureView.isAvailable()) {openCamera(mTextureView.getWidth(), mTextureView.getHeight());} else {mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}}@Overrideprotected void onPause() {closeCamera();stopBackgroundThread();super.onPause();}// 關閉相機private void closeCamera() {if (mCaptureSession != null) {mCaptureSession.close();mCaptureSession = null;}if (mCameraDevice != null) {mCameraDevice.close();mCameraDevice = null;}if (mImageReader != null) {mImageReader.close();mImageReader = null;}}
}
需要明確拍照過程是基于打開相機,配流之后的一個流程,所以點擊拍照按鈕進行拍照流程的時候需要判斷相機連接,配流過程是否正常,即需要判斷
if (mCameraDevice == null || mCaptureSession == null) {Log.w(TAG, "Cannot take picture - camera not ready");return;}
如果都正常了,那就開始本文的關鍵流程分析,如下:
mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);
Framework
由前面的文章介紹,我們就知道mCaptureSession其實就是CameraCaptureSessionImpl實例化對象
,所以繼續看
frameworks/base/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@Overridepublic int capture(CaptureRequest request, CaptureCallback callback,Handler handler) throws CameraAccessException {checkCaptureRequest(request);synchronized (mDeviceImpl.mInterfaceLock) {checkNotClosed();handler = checkHandler(handler, callback);if (DEBUG) {Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback +" handler " + handler);}//看到這里是不是很熟悉,是不是跟上一篇講解setRepeatingRequest過程很像//即主要看參數的函數調用mDeviceImpl.capturereturn addPendingSequence(mDeviceImpl.capture(request,createCaptureCallbackProxy(handler, callback), mDeviceExecutor));}}
這個時候就接著跳轉到CameraDeviceImpl類看capture()實現了
frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
public int capture(CaptureRequest request, CaptureCallback callback, Executor executor)throws CameraAccessException {if (DEBUG) {Log.d(TAG, "calling capture");}List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();requestList.add(request);return submitCaptureRequest(requestList, callback, executor, /*streaming*/false);}
然后直接看下一步,關鍵流程已加注釋
private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,Executor executor, boolean repeating) throws CameraAccessException {//這里帶的參數是false,說明了拍照的時候是不需要停預覽的,上一篇起預覽的時候//這里是true,是必定要停止上一次預覽連接的if (repeating) {stopRepeating();}//以下是關鍵函數requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);return requestInfo.getRequestId();}
通過binder調用到這里了
frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
binder::Status CameraDeviceClient::submitRequestList(const std::vector<hardware::camera2::CaptureRequest>& requests,bool streaming,/*out*/hardware::camera2::utils::SubmitInfo *submitInfo) {if (streaming) {//預覽環節,上一章跟蹤的這個流程err = mDevice->setStreamingRequestList(metadataRequestList, surfaceMapList,&(submitInfo->mLastFrameNumber));} else {//拍照環節,這章跟蹤這一個流程err = mDevice->captureList(metadataRequestList, surfaceMapList,&(submitInfo->mLastFrameNumber));}return res;
}
繼續
frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp
status_t Camera3Device::captureList(const List<const PhysicalCameraSettingsList> &requestsList,const std::list<const SurfaceMap> &surfaceMaps,int64_t *lastFrameNumber) {ATRACE_CALL();return submitRequestsHelper(requestsList, surfaceMaps, /*repeating*/false, lastFrameNumber);
}
注意這個時候傳的repeating是false了
status_t Camera3Device::submitRequestsHelper(const List<const PhysicalCameraSettingsList> &requests,const std::list<const SurfaceMap> &surfaceMaps,bool repeating,/*out*/int64_t *lastFrameNumber) {if (repeating) {//預覽流程res = mRequestThread->setRepeatingRequests(requestList, lastFrameNumber);} else {//拍照流程res = mRequestThread->queueRequestList(requestList, lastFrameNumber);}return res;}
status_t Camera3Device::RequestThread::queueRequestList(List<sp<CaptureRequest> > &requests,/*out*/int64_t *lastFrameNumber) {ATRACE_CALL();Mutex::Autolock l(mRequestLock);for (List<sp<CaptureRequest> >::iterator it = requests.begin(); it != requests.end();++it) {//添加到請求隊列mRequestQueue.push_back(*it);}if (lastFrameNumber != NULL) {*lastFrameNumber = mFrameNumber + mRequestQueue.size() - 1;ALOGV("%s: requestId %d, mFrameNumber %" PRId32 ", lastFrameNumber %" PRId64 ".",__FUNCTION__, (*(requests.begin()))->mResultExtras.requestId, mFrameNumber,*lastFrameNumber);}unpauseForNewRequests();return OK;
}
然后回到RequestThread
bool Camera3Device::RequestThread::threadLoop() //省略n行代碼,以下流程跟預覽流程一致,只提出來關鍵函數waitForNextRequestBatch();if (mNextRequests.size() == 0) {return true;}res = prepareHalRequests();submitRequestSuccess = sendRequestsBatch();return submitRequestSuccess;
}
hardware/qcom/camera/msm8998/QCamera2/HAL3/QCamera3HWI.cpp
int QCamera3HardwareInterface::processCaptureRequest(camera3_capture_request_t *request,List<InternalRequest> &internallyRequestedStreams)
{
//省略內容
}
這咋跟預覽一樣一樣的,很明顯,我!跟!丟!了!
回去!
frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp
status_t Camera3Device::submitRequestsHelper(const List<const PhysicalCameraSettingsList> &requests,const std::list<const SurfaceMap> &surfaceMaps,bool repeating,/*out*/int64_t *lastFrameNumber) {if (repeating) {//預覽流程res = mRequestThread->setRepeatingRequests(requestList, lastFrameNumber);} else {//拍照流程res = mRequestThread->queueRequestList(requestList, lastFrameNumber);}return res;}
仔細觀察一下這里的實現有什么不一樣呢
從上面代碼可以發現capture流程會被放在mRequestQueue里;repeating 流程會放在mRepeatingRequests,在threadLoop() 的waitForNextRequestBatch() 可以知道,capture流程會先塞進請求隊列里面
但好像只知道這些也不足以hal層區分拍照和預覽啊,其實主要還有一個點漏了,就是APP請求拍照時候,還設置了請求的參數,其實這個就是區分的關鍵
拍照:
// 創建拍照請求
final CaptureRequest.Builder captureBuilder =mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
預覽:
// 創建預覽請求
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
下發鏈路可概括為:
應用層設置意圖 → Framework 層(CameraService)校驗、打包為 camera3_capture_request_t → 通過 HIDL 接口跨進程傳遞到 HAL → HAL 層解析元數據識別預覽,拍照意圖。
整個過程中,Framework 層負責元數據的序列化和跨進程傳遞,HAL 層通過解析元數據中的具體值(1 對應 PREVIEW)來區分請求類型,最終路由到對應的處理邏輯,本章就說這么多了,其他的如驅動控制,數據編碼,數據回傳等后面有空再搞了,困了。
大概總結一下拍照全流程:
應用層(發起拍照請求)→ 框架層(校驗/封裝請求)→ HAL層(解析/配置硬件)→ 驅動層(控制傳感器)→ 硬件(采集/處理數據)
→ 驅動層(傳輸數據)→ HAL層(寫入緩沖區)→ 框架層(通知應用)→ 應用層(讀取/保存數據)
簡單看了下應該沒啥大問題,睡覺!