視頻采集:Android平臺基于Camera 2的實現(xiàn)

前言

這篇文章簡單介紹下移動端Android系統(tǒng)下利用Camera2相關API進行視頻采集的方法。
Camera2是谷歌在Android 5.0新增的用來替代Camera1操作攝像頭的一個全新的API。
按照慣例先上一份源碼AndroidVideo
Camera2調(diào)用攝像頭采集視頻的核心實現(xiàn)在Camera2Capture.java。

權限配置

使用Android平臺提供的攝像頭,首先必須在配置文件中添加如下權限配置:

<uses-permission android:name="android.permission.CAMERA"/>

獲取攝像頭信息

打開攝像頭管理器
CameraManager是一個用于檢測、連接和描述攝像頭設備的一個系統(tǒng)服務,可以通過調(diào)用Context.getSystemService(java.lang.String)方法來獲取一個CameraManager的實例:

CameraManager mManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);

獲取攝像頭列表信息
通過調(diào)用CameraManager.getCameraIdList()方法,可以得到一個攝像頭id的列表:

String[] cameraIds = mCameraManager.getCameraIdList();
for (String id : cameraIds) {
    //TODO
}

可以通過相對應的ID從CameraManager獲取到對應攝像頭的屬性集合CameraCharacteristics。
CameraCharacteristics可以獲取到諸如前后置情況、支持的輸出size、支持的輸出格式等等之類的。

for (String id : cameraIds) {
    //傳入攝像頭id,獲取對應攝像頭的參數(shù)集
    CameraCharacteristics characteristics = mManager.getCameraCharacteristics(id);
    //獲取攝像頭的支持等級
    Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
    //如果是LEGACY等級,不建議使用該攝像頭
    if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY)
    {
        continue;
    }
    //獲取攝像頭的朝向
    Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
    //篩選出前置攝像頭
    if (facing != CameraCharacteristics.LENS_FACING_FRONT) {
        continue;
    }
    //StreamConfigurationMap包含了該攝像頭支持的size、format等信息
    StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    //獲取輸出格式為YUV_420_888時兼容的size
    Size[] size = map.getOutputSizes(ImageFormat.YUV_420_888);
    //獲取輸出View為SurfaceView時兼容的size
    //Size[] size = map.getOutputSizes(SurfaceHolder.class);
    //TODO 其他的參數(shù),例如輸出格式、輸出幀率上下限等
}

PS:對于Camera2采集系統(tǒng)來說,每個攝像頭都有一個支持等級:

  • INFO_SUPPORTED_HARDWARE_LEVEL_3 支持YUV再處理和原始數(shù)據(jù)采集功能,并且具備先進的功能。
  • INFO_SUPPORTED_HARDWARE_LEVEL_FULL支持先進的攝像頭功能。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED向后兼容模式,底層等同于Camera1的實現(xiàn)。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY 隨機贈送的功能支持,支持性不足。

PS:總的來說如果攝像頭等級是LEVEL_3LEVEL_FULL才建議使用Camera2進行采集,否則推薦采用兼容性更好的Camera1進行視頻采集。

打開攝像頭

通過攝像頭信息,我們可以找到所需要的CameraId,接下來就用這個ID去獲取我們的攝像頭設備CameraDevice。
函數(shù)原型是public void openCamera(String cameraId, final CameraDevice.StateCallback callback, Handler handler),
cameraId是需要打開的攝像頭的id,為了監(jiān)聽攝像頭的情況,需要傳入一個回調(diào),也就是第二個參數(shù)CameraDevice.StateCallback,當然如果我們不想讓open操作占用UI線程的時間的話,
我們可以通過構造一個HandlerThread的帶Looper的子線程,然后將其Handler傳入即可。

//打開攝像頭,正常打開會回調(diào)到CameraDeviceStateCallback的onOpened方法
mManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice camera) {
        //攝像頭成功連接
        //camera也就是我們需要獲取的攝像頭設備
        mCameraDevice = camera;                
    }
    @Override
    public void onDisconnected(@NonNull CameraDevice camera) {
        //攝像頭斷開連接
    }
    @Override
    public void onError(@NonNull CameraDevice camera, int error) {
        //打開錯誤
    }
}, mHandler);

創(chuàng)建采集會話

在成功打開攝像頭,獲取到相應的CameraDevice,我們需要創(chuàng)建一個采集會話來提供程序與攝像頭的交流。
其函數(shù)原型是public abstract void createCaptureSession(List<Surface> outputs,CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException
第一個參數(shù)傳入的是需要采集的Surface,為了監(jiān)聽會話創(chuàng)建情況,我們需要傳入一個CameraCaptureSession.StateCallback回調(diào),當然第三個參數(shù)也就是讓操作能在對應Handler所在的線程中進行。

//獲取一個采集Session會話,正常流程回回調(diào)到CameraCaptureSessionStateCallback的onConfigured方法
mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceView.getHolder().getSurface()), new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(@NonNull CameraCaptureSession session) {
        //會話創(chuàng)建成功
        //mCameraCaptureSession也就是新創(chuàng)建的會話
        mCameraCaptureSession = session;
    }
    @Override
    public void onConfigureFailed(@NonNull CameraCaptureSession session) {
        //會話創(chuàng)建失敗
    }
}, mHandler);

PS:對于一些業(yè)務需求需要提高采集幀率(120fps及以上),createConstrainedHighSpeedCaptureSession()這個會話能良好的支持該功能。

發(fā)送采集請求

當需要開始采集時,需要構造一個采集請求,然后將這個請求發(fā)送給采集會話。

//創(chuàng)建一個基于錄制的請求
mRequest = mDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
//將需要的目標Surface加入Target列表
mRequest.addTarget(surface);
//重復發(fā)送這個請求,進行持續(xù)的采集
mCameraCaptureSession.setRepeatingRequest(mRequest.build(), NULL, mHandler);

原始數(shù)據(jù)回調(diào)

在Camera1的采集中,我們一般通過設置setPreviewCallbackWithBuffer()addCallbackBuffer()來獲取到采集的原始數(shù)據(jù),那么在Camera2中將如何實現(xiàn)該功能呢?
我們可以用到ImageReader這個類:

//ImageReader是一個數(shù)據(jù)回調(diào)模塊,類似于Camera1的setPreviewCallbackWithBuffer
mReader = ImageReader.newInstance(mConfig.mWidth, mConfig.mHeight, mConfig.mFormat, 2);
mReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireNextImage();
        //數(shù)據(jù)處理
        image.close();
    }
}, mHandler);

我們需要在createCaptureSession()的第一個參數(shù)中將ImageReaderSurface傳進去:

//通過ImageReader.getSurface()獲取一個Surface并將其傳給Session中
mCameraDevice.createCaptureSession(Arrays.asList(mReader.getSurface())//....);

然后在CaptureRequest添加這個Target:

//當然,構造請求時,需要將該Surface同時加入到Request的Target列表中
mRequest.addTarget(mReader.getSurface());

參考資料

對于Camera2相關,我們一般可以參考如下幾個工程:
googlesamples/android-Camera2Basic
google/cameraview

結語

這篇文章簡單介紹了Android平臺基于Camera2的api進行攝像頭采集的功能。
Camera2雖然是谷歌當前建議使用的采集框架,但是由于廠商的兼容性問題導致Camera2的api功能相對不穩(wěn)定;
所以筆者還是建議開發(fā)以Camera1為主要采集、Camera2為輔助采集的架構實現(xiàn)比較靠譜。

本文同步發(fā)布于簡書、CSDN

End!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容