第29讲 SlowMotion实战 - Android Camera2 API

2024年10月25日 第29讲 SlowMotion实战 极客笔记

本讲是Android Camera专题系列的第29讲,我们介绍Android Camera2 API专题的SlowMotion实战。

更多资源:

资源 描述
在线课程 极客笔记在线课程
知识星球 星球名称:深入浅出Android Camera
星球ID: 17296815
Wechat 极客笔记圈

判断是否支持Slow Motion

Camera方面

Capability

  • 是否支持REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO

StreamConfigurationMap

  • getHighSpeedVideoSizes()不为NULL

  • 每一种Size对应的getHighSpeedVideoFpsRangesFor不为NULL

实战代码

GeekCamera2\app\src\main\java\com\lmcjl\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\lmcjl\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\lmcjl\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\lmcjl\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\lmcjl\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\lmcjl\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);
}

本文链接:http://so.lmcjl.com/news/16212/

展开阅读全文