diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CamcorderProfiles.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CamcorderProfiles.java new file mode 100644 index 00000000..26147a20 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CamcorderProfiles.java @@ -0,0 +1,68 @@ +package com.otaliastudios.cameraview; + +import android.annotation.SuppressLint; +import android.media.CamcorderProfile; +import android.os.Build; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import androidx.annotation.NonNull; + +/** + * Wraps the {@link android.media.CamcorderProfile} static utilities. + */ +class CamcorderProfiles { + + @SuppressLint("UseSparseArrays") + private static Map sizeToProfileMap = new HashMap<>(); + + static { + sizeToProfileMap.put(new Size(176, 144), CamcorderProfile.QUALITY_QCIF); + sizeToProfileMap.put(new Size(320, 240), CamcorderProfile.QUALITY_QVGA); + sizeToProfileMap.put(new Size(352, 288), CamcorderProfile.QUALITY_CIF); + sizeToProfileMap.put(new Size(720, 480), CamcorderProfile.QUALITY_480P); + sizeToProfileMap.put(new Size(1280, 720), CamcorderProfile.QUALITY_720P); + sizeToProfileMap.put(new Size(1920, 1080), CamcorderProfile.QUALITY_1080P); + if (Build.VERSION.SDK_INT >= 21) { + sizeToProfileMap.put(new Size(3840, 2160), CamcorderProfile.QUALITY_2160P); + } + } + + /** + * Returns a CamcorderProfile that's somewhat coherent with the target size, + * to ensure we get acceptable video/audio parameters for MediaRecorders (most notably the bitrate). + * + * @param cameraId the camera id + * @param targetSize the target video size + * @return a profile + */ + @NonNull + static CamcorderProfile get(int cameraId, @NonNull Size targetSize) { + final int targetArea = targetSize.getWidth() * targetSize.getHeight(); + List sizes = new ArrayList<>(sizeToProfileMap.keySet()); + Collections.sort(sizes, new Comparator() { + @Override + public int compare(Size s1, Size s2) { + int a1 = Math.abs(s1.getWidth() * s1.getHeight() - targetArea); + int a2 = Math.abs(s2.getWidth() * s2.getHeight() - targetArea); + //noinspection UseCompareMethod + return (a1 < a2) ? -1 : ((a1 == a2) ? 0 : 1); + } + }); + while (sizes.size() > 0) { + Size candidate = sizes.remove(0); + //noinspection ConstantConditions + int quality = sizeToProfileMap.get(candidate); + if (CamcorderProfile.hasProfile(cameraId, quality)) { + return CamcorderProfile.get(cameraId, quality); + } + } + // Should never happen, but fallback to low. + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/FullVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/FullVideoRecorder.java index 4d26287d..7fdd3915 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/FullVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/FullVideoRecorder.java @@ -18,6 +18,7 @@ class FullVideoRecorder extends VideoRecorder { private CamcorderProfile mProfile; private Camera1 mController; private Camera mCamera; + private Size mSize; FullVideoRecorder(@NonNull VideoResult stub, @Nullable VideoResultListener listener, @NonNull Camera1 controller, @NonNull Camera camera, int cameraId) { @@ -27,11 +28,10 @@ class FullVideoRecorder extends VideoRecorder { mMediaRecorder = new MediaRecorder(); mMediaRecorder.setCamera(camera); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); - mProfile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); - // TODO: should get a profile of a quality compatible with the chosen size. - // Might do this by inspecting mResult.getSize(). However, it is not super important. - // We are only bound to respect the video size passed by the VideoSizeSelector, and - // we are doing that below. + + // Get a profile of quality compatible with the chosen size. + mSize = mResult.getRotation() % 180 != 0 ? mResult.getSize().flip() : mResult.getSize(); + mProfile = CamcorderProfiles.get(cameraId, mSize); } // Camera2 constructor here... @@ -43,7 +43,6 @@ class FullVideoRecorder extends VideoRecorder { mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); } - Size size = mResult.getRotation() % 180 != 0 ? mResult.getSize().flip() : mResult.getSize(); mMediaRecorder.setOutputFormat(mProfile.fileFormat); if (mResult.videoFrameRate <= 0) { mMediaRecorder.setVideoFrameRate(mProfile.videoFrameRate); @@ -51,7 +50,7 @@ class FullVideoRecorder extends VideoRecorder { } else { mMediaRecorder.setVideoFrameRate(mResult.videoFrameRate); } - mMediaRecorder.setVideoSize(size.getWidth(), size.getHeight()); + mMediaRecorder.setVideoSize(mSize.getWidth(), mSize.getHeight()); switch (mResult.getVideoCodec()) { case H_263: mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); break; case H_264: mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); break;