第29讲 SlowMotion实战 - Android Camera2 API

本讲是Android Camera专题系列的第29讲,我们介绍Android Camera2 API专题的SlowMotion实战,包括如下内容:

  • 判断是否支持Slow Motion
  • Slow Motion支持的Size和FPS
  • Camera流程控制
  • MediaRecorder流程控制

视频在线观看:

加入知识星球与更多Camera同学交流

  • 星球名称:深入浅出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

    MediaRecorder流程控制

  • 举例240FPS的CaptureRequest List有8个Batch Size

    MediaRecorder流程控制

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);
}
赞(1)
未经允许不得转载:极客笔记 » 第29讲 SlowMotion实战

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
Android Camera2参考
Android Camera2参考概述
Android Camera2参数参考
Android Camera2参数参考概述
Android Camera2指南
Android Camera2指南概述
Android Camera HAL开发
Android Camera HAL开发概述
Android Camera2 API
第2讲 Android Camera 架构介绍第3讲 Camera2 API 概述第4讲 Open和Close Camera流程第5讲 GeekCamera2介绍第6讲 Open和Close Camera代码实现第7讲 Hardware Level详解第8讲 AVAILABLE_CAPABILITIES详解第9讲 StreamConfigurationMap详解一第10讲 StreamConfigurationMap详解二第11讲 StreamConfigurationMap实战一第12讲 StreamConfigurationMap实战二第13讲 为什么需要OutputConfiguration第14讲 OutputConfiguration API详解第15讲 Surface Sharing实战第16讲 Deferred Surface实战第17讲 Reprocessable Capture Session详解一第18讲 Reprocessable Capture Session详解二第19讲 App如何实现ZSL功能第20讲 SessionConfiguration详解第21讲 createCaptureSession详解第22讲 SessionParameter实战第23讲 CaptureRequest详解第24讲 如何操作VendorTag第25讲 获取Physical Camera数据流第26讲 CameraCaptureSession详解第27讲 CameraCaptureSession.CaptureCallback详解第28讲 GeekCamera2连拍实战第29讲 SlowMotion实战第30讲 CaptureResult详解第31讲 AE自动曝光 Part 1第32讲 AE自动曝光 Part 2第33讲 AE自动曝光实战第34讲 AE手动曝光第35讲 AE手动曝光实战第36讲 Flash闪光灯控制第37讲 拍照打闪实战第38讲 通过CropRegion控制Zoom缩放第39讲 通过ZoomRatio控制Zoom缩放第40讲 Digital Zoom缩放实战第41讲 Touch AE实战第42讲 AF自动对焦第43讲 AF自动对焦 第二部分第44讲 AF自动对焦实战第45讲 手动对焦实战第46讲 AWB自动白平衡第47讲 AWB自动白平衡实战第48讲 FD人脸检测第49讲 Android13 Camera2 New APIs介绍