本讲是Android Camera专题系列的第29讲,我们介绍Android Camera2 API专题的SlowMotion实战。
更多资源:
资源 | 描述 |
---|---|
在线课程 | 极客笔记在线课程 |
知识星球 | 星球名称:深入浅出Android Camera 星球ID: 17296815 |
极客笔记圈 |
判断是否支持Slow Motion
Camera方面
Capability
- 是否支持REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
StreamConfigurationMap
- getHighSpeedVideoSizes()不为NULL
-
每一种Size对应的getHighSpeedVideoFpsRangesFor不为NULL
实战代码
GeekCamera2\app\src\main\java\com\deepinout\geekcamera\cts\helpers\StaticMetadata.java#isHighSpeedVideoSupported
public boolean isHighSpeedVideoSupported() {
if (!isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO)) {
return false;
}
StreamConfigurationMap config =
getValueFromKeyNonNull(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (config == null) {
return false;
}
Size[] availableSizes = config.getHighSpeedVideoSizes();
if (availableSizes.length == 0) {
return false;
}
for (Size size : availableSizes) {
Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size);
if (availableFpsRanges.length == 0) {
return false;
}
}
return true;
}
Video Encoder方面
从CamcorderProfile判断是否支持High Speed Quality的Profile
实战代码
GeekCamera2\app\src\main\java\com\deepinout\geekcamera\cameracontroller\CameraController2.java#getFpsFromHighSpeedProfileForSize
private int getFpsFromHighSpeedProfileForSize(android.util.Size size) {
for (int quality = CamcorderProfile.QUALITY_HIGH_SPEED_LOW;
quality <= CamcorderProfile.QUALITY_HIGH_SPEED_2160P; quality++) {
if (CamcorderProfile.hasProfile(quality)) {
CamcorderProfile profile = CamcorderProfile.get(quality);
if (size.equals(new android.util.Size(profile.videoFrameWidth, profile.videoFrameHeight))){
return profile.videoFrameRate;
}
}
}
return 0;
}
Slow Motion支持的Size和FPS
遍历Camera支持的Size和FPS,对每一种Size和FPS的组合去检查CamcorderProfile是否支持
实战代码
GeekCamera2\app\src\main\java\com\deepinout\geekcamera\cameracontroller\CameraController2.java#doInitSupportedHighSpeedVideoSizes
private void doInitSupportedHighSpeedVideoSizes(CameraFeatures camera_features,
StreamConfigurationMap configs) {
Log.i(TAG, "[Slow_Motion] doInitSupportedHighSpeedVideoSizes");
hs_fps_ranges = new ArrayList<>();
camera_features.mSupportedVideoSizesHighSpeed = new ArrayList<>();
for (Range<Integer> r : configs.getHighSpeedVideoFpsRanges()) {
hs_fps_ranges.add(new int[] {r.getLower(), r.getUpper()});
}
Collections.sort(hs_fps_ranges, new CameraController.RangeSorter());
if( MyDebug.LOG ) {
Log.i(TAG, "[Slow_Motion] Camera Supported high speed video fps ranges: ");
for (int[] f : hs_fps_ranges) {
Log.i(TAG, "[Slow_Motion] camera hs range: [" + f[0] + "-" + f[1] + "]");
}
Log.i(TAG, "[Slow_Motion] Video Supported high speed video camcorder profiles: ");
for (int quality = CamcorderProfile.QUALITY_HIGH_SPEED_LOW;
quality <= CamcorderProfile.QUALITY_HIGH_SPEED_2160P; quality++) {
if (CamcorderProfile.hasProfile(quality)) {
CamcorderProfile profile = CamcorderProfile.get(quality);
Log.i(TAG, "[Slow_Motion] video hs camcorder profile:" + profile.videoFrameWidth
+ "x" + profile.videoFrameHeight + "@" + profile.videoFrameRate + " fps" +
",quality:" + quality);
}
}
}
android.util.Size[] camera_video_sizes_high_speed = configs.getHighSpeedVideoSizes();
for(android.util.Size camera_size : camera_video_sizes_high_speed) {
for (Range<Integer> r : configs.getHighSpeedVideoFpsRangesFor(camera_size)) {
int profile_fps = getFpsFromHighSpeedProfileForSize(camera_size);
if (r.getUpper() > r.getLower()) {
Log.w(TAG, "[Slow_Motion] skip " + camera_size + "@fps range:" + r.toString());
continue;
}
if (r.getUpper() != profile_fps) {
Log.w(TAG, "[Slow_Motion] high speed recording " + camera_size + "@" + r.getUpper() + "fps"
+ " is not supported by CamcorderProfile");
continue;
}
ArrayList<int[]> fr = new ArrayList<>();
fr.add(new int[] { r.getLower(), r.getUpper()});
CameraController.Size hs_video_size = new CameraController.Size(
camera_size.getWidth(),
camera_size.getHeight(),
fr,
true);
if (MyDebug.LOG) {
Log.i(TAG, "[Slow_Motion] added high speed video size: " +
hs_video_size +
", fps range:" + r.toString());
}
camera_features.mSupportedVideoSizesHighSpeed.add(hs_video_size);
}
}
Collections.sort(camera_features.mSupportedVideoSizesHighSpeed, new CameraController.SizeSorter());
}
Camera流程控制
Session创建
- 创建SessionConfiguration时,Session Type要设置为SessionConfiguration.SESSION_HIGH_SPEED
-
只配置一个Preview Surface + 一个Video Recording Surface,不支持Video Snapshot
实战代码
GeekCamera2\app\src\main\java\com\deepinout\geekcamera\cameracontroller\CameraController2.java#createCaptureSession
mSessionConfiguration = new SessionConfiguration(
is_video_high_speed ?
SessionConfiguration.SESSION_HIGH_SPEED : SessionConfiguration.SESSION_REGULAR,
outputConfigurations,
new CameraTestUtils.HandlerExecutor(mCameraBackgroundHandler),
myStateCallback
);
Repeating burst request创建
- 调用CameraConstrainedHighSpeedCaptureSession# createHighSpeedRequestList,根据一个request产生一组CaptureRequest
实战代码
GeekCamera2\app\src\main\java\com\deepinout\geekcamera\cameracontroller\CameraController2.java#setRepeatingRequest
if( is_video_high_speed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ) {
CameraConstrainedHighSpeedCaptureSession captureSessionHighSpeed = (CameraConstrainedHighSpeedCaptureSession) mCameraCaptureSession;
List<CaptureRequest> mPreviewBuilderBurst = captureSessionHighSpeed.createHighSpeedRequestList(request);
Log.i(TAG, "[Slow_Motion] setRepeatingBurst createHighSpeedRequestList size:" + mPreviewBuilderBurst.size());
captureSessionHighSpeed.setRepeatingBurst(mPreviewBuilderBurst, previewCaptureCallback, mCameraBackgroundHandler);
}
Repeating burst CaptureRequest的个数(Batch Size)是如何决定决定的
- 录像过程中预览一般30FPS就足够了,因此Batch Size的计算会想办法将预览帧率限制在30FPS
-
举例240FPS的CaptureRequest List有8个Batch Size
MediaRecorder流程控制
将CamcorderProfile中的值设置给MediaRecorder
setVideoFrameRate
- Slow Motion
- 建议不要录制Audio
-
通过setVideoFrameRate设置的video frame rate要小于capture rate,建议为30
-
高帧率视频
- Video frame rate与capture rate相等
实战代码
GeekCamera2\app\src\main\java\com\deepinout\geekcamera\preview\VideoProfile.java#copyToMediaRecorder
public void copyToMediaRecorder(MediaRecorder media_recorder, boolean slow_motion) {
if( MyDebug.LOG )
Log.d(TAG, "copyToMediaRecorder: " + media_recorder + toString());
if( record_audio && !slow_motion) {
if( MyDebug.LOG )
Log.d(TAG, "record audio");
media_recorder.setAudioSource(this.audioSource);
}
media_recorder.setVideoSource(this.videoSource);
// n.b., order may be important - output format should be first, at least
// also match order of MediaRecorder.setProfile() just to be safe, see https://stackoverflow.com/questions/5524672/is-it-possible-to-use-camcorderprofile-without-audio-source
media_recorder.setOutputFormat(this.fileFormat);
if (slow_motion) {
media_recorder.setVideoFrameRate(30);
} else {
media_recorder.setVideoFrameRate(this.videoFrameRate);
}
media_recorder.setCaptureRate(this.videoCaptureRate);
media_recorder.setVideoSize(this.videoFrameWidth, this.videoFrameHeight);
media_recorder.setVideoEncodingBitRate(this.videoBitRate);
media_recorder.setVideoEncoder(this.videoCodec);
if( record_audio && !slow_motion) {
media_recorder.setAudioEncodingBitRate(this.audioBitRate);
media_recorder.setAudioChannels(this.audioChannels);
media_recorder.setAudioSamplingRate(this.audioSampleRate);
media_recorder.setAudioEncoder(this.audioCodec);
}
if( MyDebug.LOG )
Log.d(TAG, "done: " + media_recorder);
}