❗️底部有示例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]
帧率范围Range
的Lower
和Upper
一致才认为支持,所以这个log结果说明它支持120fps
的预览.
支持高帧率的视频录制的条件
- 高帧率的视频录制和普通的
camera.createCaptureSession
要求不同,高帧率session的预览preview
大小和设置的MediaRecorder
的大小必须一致 - 上文获取出来的支持
120fps
,说明他支持预览,但是不意味着它支持录制视频.是否支持视频录制,需要用另一个方法来查询, 就是CamcorderProfile
的public 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
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那都是后事了.