Android Camera2 HighSpeedCaptureSession高帧率预览录像

❗️底部有示例Apk和Github代码
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]

帧率范围RangeLowerUpper一致才认为支持,所以这个log结果说明它支持120fps的预览.

支持高帧率的视频录制的条件

  • 高帧率的视频录制和普通的camera.createCaptureSession要求不同,高帧率session的预览preview大小和设置的MediaRecorder的大小必须一致
  • 上文获取出来的支持120fps,说明他支持预览,但是不意味着它支持录制视频.是否支持视频录制,需要用另一个方法来查询, 就是CamcorderProfilepublic static boolean hasProfile(int cameraId, int quality)方法,只有他返回true才意味着当前预览的帧率支持被录制视频.

为什么一定要用CamcorderProfile来判断是否支持?

直接在MediaRecorder的方法里set设置各个属性是否可行?
答案是不行, 虽然我也不知道为什么, 我用手边的机器坚果Pro 2s测试, 查询到支持的预览帧率支持1080p240fps,但是在给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个目的地:

  1. 第一个是preview,需要输出到预览, 这样我们在屏幕上才看得到画面
  2. 第二个是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

打开手机文件列表

2.在文件列表中找到自己录制的视频文件, 并把它保存到本地,比如Downloads
保存文件到本地

保存到本地的视频文件

3.打开终端Terminal,然后运行命令ffmpeg -i 视频文件名, 如果提示ffmpeg命令不存在或者找不到,就先安装这个东西
视频信息

红线处是查询视频信息的命令,绿线是该视频的fps帧率信息. 虽然我们预览的是120fps,但是录制出来的不一定可以达到那么高, 比如图里面的就只有74.54fps.这是系统自己决定的.

最后

关键就是2点:
1.预览尺寸和录制尺寸要一直
2.CamcorderProfile说你支持你才支持.

所有Demo代码提交到Github.可以访问https://github.com/ZhengShang/HighSpeedVideoDemo/tree/master

Demo Apk 下载

点击下载DemoApk体验

如果运行apk后发现crash,ANR或者黑屏等杂七杂八的问题, 这都很正常, Android相机开发受制于各种不同的手机和rom的差异化,确实挺难做到完美兼容的, 尤其是这随便写的Demo,就更难保证了.
但是大体流程是这个样子, 具体解决Bug那都是后事了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342