Android Camera2 實現高幀率預覽錄製(附源碼)

公衆號回覆:666,領取學習資源大禮包

來源:svvvvvvvL

地址:https://www.jianshu.com/p/0d2f200ab374

Android的相機 Camera2 在 6.0M 的時候,出了一個支持高幀率預覽和錄像的功能。

就是創建一個新的 session,叫做 mCameraDevice.createConstrainedHighSpeedCaptureSession,通過這個,可以實現相機的高幀率(>120fps)的預覽和錄像(需要相機本身支持)。

根據相機的不同,實現的幀率也不同, 比如我手上這個華爲v10的手機就是下圖這個樣子:

  

下面說一下大概步驟.

第一步, 獲取權限

相機部分肯定得請求相關權限才能操作的,這裏需要3個,分別爲

  • 相機 Manifest.permission.CAMERA

  • 錄音 Manifest.permission.RECORD_AUDIO 這個是爲了錄視頻的時候錄音用的

  • 寫入文件 Manifest.permission.WRITE_EXTERNAL_STORAGE 這個是拍好了視屏,存入到手機相冊用的

除了運行時請求這些權限之外, 還需要在AndroidManifest.xml文件裏面加入這3個權限的註冊,否則請求權限的時候,不能觸發Dialog提示

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

第二步,打開相機

正確獲取到權限了之後,按照常規方式打開相機.

先獲取到

CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);

然後用這個manager打開相機,即

manager.openCamera(cameraId, mStateCallback, mBackgroundHandler);

在這中間,我們可以獲取到相機的支持的各種參數信息, 也就是在這裏纔可以知道,當前的相機支不支持高幀率錄製

讀取到的支持預覽高幀率

使用 manager.getCameraCharacteristics(cameraId) 獲取 CameraCharacteristics

然後使用 characteristics .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) 這個值可以獲取到向前攝像頭支持的參數 StreamConfigurationMap

最後用 map 調用 map.getHighSpeedVideoFpsRanges() 來獲取支持搞幀率預覽範圍,代碼部分爲

            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
            StreamConfigurationMap map = characteristics
                    .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

            if (map == null) {
                ErrorDialog.newInstance(getString(R.string.open_failed_of_map_null))
                        .show(getFragmentManager(), "TAG");
                return;
            }

           for (Range<Integer> fpsRange : map.getHighSpeedVideoFpsRanges()) {
                Log.d(TAG, "openCamera: [width, height] = "+ fpsRange.toString());
            }
            

比如我手上的這個華爲V10測試機,獲取到的參數就是

openCamera: [width, height] = [120, 120]
openCamera: [width, height] = [30, 120]

幀率範圍Range的 LowerUpper一致才認爲支持,所以這個log結果說明它支持120fps的預覽.

支持高幀率的視頻錄製的條件

  • 高幀率的視頻錄製和普通的camera.createCaptureSession要求不同,高幀率session的預覽preview大小和設置的MediaRecorder的大小必須一致

  • 上文獲取出來的支持120fps,說明他支持預覽,但是不意味着它支持錄製視頻。

  • 是否支持視頻錄製,需要用另一個方法來查詢, 就是 CamcorderProfilepublic static boolean hasProfile(int cameraId, int quality) 方法,只有他返回true才意味着當前預覽的幀率支持被錄製視頻。

爲什麼一定要用CamcorderProfile來判斷是否支持?

直接在MediaRecorder的方法裏set設置各個屬性是否可行?

答案是不行, 雖然我也不知道爲什麼, 我用手邊的機器堅果Pro 2s測試, 查詢到支持的預覽幀率支持1080p的240fps,但是在給MediaRecorder設置好各種參數後,比如mMediaRecorder.setVideoFrameRate(240),點擊錄製直接crash.

配置MediaRecorder

在得出手機支持高幀率錄製後, 就需要配置MediaRecorder給下一步打開預覽使用了. 具體的步驟如下:

        CamcorderProfile profile = mVideoSize.getCamcorderProfile();
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setProfile(profile);
        mNextVideoFilePath = getVideoFile();
        mMediaRecorder.setOutputFile(mNextVideoFilePath);

        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int orientation = ORIENTATIONS.get(rotation);
        mMediaRecorder.setOrientationHint(orientation);
        mMediaRecorder.prepare();
        

這裏面的關鍵就是setVideoFrameRate這一個屬性, 這個只能由CamcorderProfile.videoFrameRate來設置, 如果CamcorderProfile說你不支持, 那麼你手動設置了這個數值, 也不能正常錄製.

其他的只需要將CamcorderProfile不包含的配置設置進去就好了. 其他的比如setOutputFile輸出文件路徑,setOrientationHint旋轉方向這些, 根據需要配置就好了.

第三步, 開啓預覽

使用CameraDevice的如下方法開啓預覽

    /*
     * @param outputs The new set of Surfaces that should be made available as
     *                targets for captured high speed image data.
     * @param callback The callback to notify about the status of the new capture session.
     * @param handler The handler on which the callback should be invoked, or {@code null} to use
     *                the current thread's {@link android.os.Looper looper}.
     *
     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
     *                                  the callback is null, or the handler is null but the current
     *                                  thread has no looper, or the camera device doesn't support
     *                                  high speed video capability.
     * @throws CameraAccessException if the camera device is no longer connected or has
     *                               encountered a fatal error
     * @throws IllegalStateException if the camera device has been closed
     *
     * @see #createCaptureSession
     * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
     * @see StreamConfigurationMap#getHighSpeedVideoSizes
     * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
     * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
     * @see CameraCaptureSession#captureBurst
     * @see CameraCaptureSession#setRepeatingBurst
     * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
     */
 public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
            @NonNull CameraCaptureSession.StateCallback callback,
            @Nullable Handler handler)
            throws CameraAccessException;
            

可以看到, 第一個參數是一個surface的List,代表着相機攝像頭拍得到畫面要輸出到哪裏去, 這裏我們有2個目的地:

第一個是preview,需要輸出到預覽, 這樣我們在屏幕上纔看得到畫面 第二個是mMediaRecorder.getSurface(), 這個是輸出到MediaRecorder裏面去,用來錄製視頻

所以這個地方的代碼就類似

            Surface previewSurface = new Surface(texture);
             //添加預覽輸出
            surfaces.add(previewSurface);
            mPreviewBuilder.addTarget(previewSurface);

            //配置MediaRecorder
            setUpMediaRecorder();
            //添加MediaRecorder輸出
            surfaces.add(mMediaRecorder.getSurface());
            mPreviewBuilder.addTarget(mMediaRecorder.getSurface());
            //開啓預覽
            mCameraDevice.createConstrainedHighSpeedCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {

                @Override
                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                    //保存引用
                    mPreviewSessionHighSpeed = (CameraConstrainedHighSpeedCaptureSession) cameraCaptureSession;
                    //刷新預覽, 使屏幕動起來
                    updatePreview();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    Activity activity = getActivity();
                    if (null != activity) {
                        Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
                    }
                }
            }, mBackgroundHandler);

沒什麼錯誤的話,這樣就可以啓動預覽了.

第四步, 開始錄像和結束錄像

因爲MediaRecorder在之前已經準備就緒了, 所以開啓錄像只需要調用一下開始錄像就好了

mMediaRecorder.start();

其他的一些UI操作看着辦.

結束錄製也是幾句話:

//停止錄製
mMediaRecorder.stop();
//重置狀態,便於重用, 不需要每次都new MediaRecorder了
mMediaRecorder.reset();
//因爲MediaRecorder被銷燬了, 所以需要重新配置MediaRecorder,重新打開預覽
startPreview();

第五步, 驗證視頻的幀率

對於錄製生成的視頻, 需要查看他的具體參數是不是真的是我們所設置的值. 我這裏是macos環境, 就只演示我自己的操作:

  1. 用USB線連接手機和電腦,點擊右下角的 Device File Explorer 

  

  1. 在文件列表中找到自己錄製的視頻文件, 並把它保存到本地,比如Downloads 

  

保存到本地的視頻文件

  

  1. 打開終端Terminal,然後運行命令ffmpeg -i 視頻文件名, 如果提示ffmpeg命令不存在或者找不到,就先安裝這個東西

  

紅線處是查詢視頻信息的命令,綠線是該視頻的fps幀率信息. 雖然我們預覽的是120fps,但是錄製出來的不一定可以達到那麼高, 比如圖裏面的就只有74.54fps.這是系統自己決定的.

最後

關鍵就是2點:

  1. 預覽尺寸和錄製尺寸要一直

  2. CamcorderProfile 說你支持你才支持.

所有Demo代碼提交到Github.可以訪問

https://github.com/ZhengShang/HighSpeedVideoDemo/tree/master

如果運行apk後發現crash,ANR或者黑屏等雜七雜八的問題, 這都很正常, Android相機開發受制於各種不同的手機和rom的差異化,確實挺難做到完美兼容的, 尤其是這隨便寫的Demo,就更難保證了.

但是大體流程是這個樣子, 具體解決Bug那都是後事了.

推薦閱讀:

音視頻開發入門必備之基礎知識

移動端技術交流喊你入羣啦~~~

推薦幾個堪稱教科書級別的 Android 音視頻入門項目

Android OpenGL ES 實現 3D 阿凡達效果

沒想到,快手成了“生產力”

覺得不錯,點個在看唄~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章