diff --git a/core/api/current.txt b/core/api/current.txt index d50920c1aaef9..b3ce06b70c9bd 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20898,6 +20898,7 @@ package android.media { public class CamcorderProfile { method public static android.media.CamcorderProfile get(int); method public static android.media.CamcorderProfile get(int, int); + method @Nullable public static android.media.EncoderProfiles getAll(@NonNull String, int); method public static boolean hasProfile(int); method public static boolean hasProfile(int, int); field public static final int QUALITY_1080P = 6; // 0x6 @@ -20978,6 +20979,32 @@ package android.media { field @NonNull public final java.util.UUID uuid; } + public class EncoderProfiles { + method @NonNull public java.util.List getAudioProfiles(); + method public int getDurationSeconds(); + method public int getFileFormat(); + method @NonNull public java.util.List getVideoProfiles(); + } + + public static class EncoderProfiles.AudioProfile { + method public int getBitrate(); + method public int getChannels(); + method public int getCodec(); + method @NonNull public String getMediaType(); + method public int getProfile(); + method public int getSampleRate(); + } + + public static class EncoderProfiles.VideoProfile { + method public int getBitrate(); + method public int getCodec(); + method public int getFrameRate(); + method public int getHeight(); + method @NonNull public String getMediaType(); + method public int getProfile(); + method public int getWidth(); + } + public class ExifInterface { ctor public ExifInterface(@NonNull java.io.File) throws java.io.IOException; ctor public ExifInterface(@NonNull String) throws java.io.IOException; @@ -22904,6 +22931,7 @@ package android.media { method public void setAudioChannels(int); method public void setAudioEncoder(int) throws java.lang.IllegalStateException; method public void setAudioEncodingBitRate(int); + method public void setAudioProfile(@NonNull android.media.EncoderProfiles.AudioProfile); method public void setAudioSamplingRate(int); method public void setAudioSource(int) throws java.lang.IllegalStateException; method @Deprecated public void setCamera(android.hardware.Camera); @@ -22932,6 +22960,7 @@ package android.media { method public void setVideoEncodingBitRate(int); method public void setVideoEncodingProfileLevel(int, int); method public void setVideoFrameRate(int) throws java.lang.IllegalStateException; + method public void setVideoProfile(@NonNull android.media.EncoderProfiles.VideoProfile); method public void setVideoSize(int, int) throws java.lang.IllegalStateException; method public void setVideoSource(int) throws java.lang.IllegalStateException; method public void start() throws java.lang.IllegalStateException; diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java index 9c6b276e7b200..2059f02c697cb 100644 --- a/media/java/android/media/CamcorderProfile.java +++ b/media/java/android/media/CamcorderProfile.java @@ -16,6 +16,9 @@ package android.media; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; @@ -23,6 +26,9 @@ import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.os.Build; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Retrieves the * predefined camcorder profile settings for camcorder applications. @@ -275,6 +281,53 @@ public class CamcorderProfile private static final int QUALITY_HIGH_SPEED_LIST_START = QUALITY_HIGH_SPEED_LOW; private static final int QUALITY_HIGH_SPEED_LIST_END = QUALITY_HIGH_SPEED_4KDCI; + /** + * @hide + */ + @IntDef({ + QUALITY_LOW, + QUALITY_HIGH, + QUALITY_QCIF, + QUALITY_CIF, + QUALITY_480P, + QUALITY_720P, + QUALITY_1080P, + QUALITY_QVGA, + QUALITY_2160P, + QUALITY_VGA, + QUALITY_4KDCI, + QUALITY_QHD, + QUALITY_2K, + QUALITY_8KUHD, + + QUALITY_TIME_LAPSE_LOW , + QUALITY_TIME_LAPSE_HIGH, + QUALITY_TIME_LAPSE_QCIF, + QUALITY_TIME_LAPSE_CIF, + QUALITY_TIME_LAPSE_480P, + QUALITY_TIME_LAPSE_720P, + QUALITY_TIME_LAPSE_1080P, + QUALITY_TIME_LAPSE_QVGA, + QUALITY_TIME_LAPSE_2160P, + QUALITY_TIME_LAPSE_VGA, + QUALITY_TIME_LAPSE_4KDCI, + QUALITY_TIME_LAPSE_QHD, + QUALITY_TIME_LAPSE_2K, + QUALITY_TIME_LAPSE_8KUHD, + + QUALITY_HIGH_SPEED_LOW, + QUALITY_HIGH_SPEED_HIGH, + QUALITY_HIGH_SPEED_480P, + QUALITY_HIGH_SPEED_720P, + QUALITY_HIGH_SPEED_1080P, + QUALITY_HIGH_SPEED_2160P, + QUALITY_HIGH_SPEED_CIF, + QUALITY_HIGH_SPEED_VGA, + QUALITY_HIGH_SPEED_4KDCI, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Quality {} + /** * Default recording duration in seconds before the session is terminated. * This is useful for applications like MMS has limited file size requirement. @@ -385,9 +438,8 @@ public class CamcorderProfile public int audioChannels; /** - * Returns the camcorder profile for the first back-facing camera on the - * device at the given quality level. If the device has no back-facing - * camera, this returns null. + * Returns the default camcorder profile at the given quality level for the first back-facing + * camera on the device. If the device has no back-facing camera, this returns null. * @param quality the target quality level for the camcorder profile * @see #get(int, int) */ @@ -404,8 +456,7 @@ public class CamcorderProfile } /** - * Returns the camcorder profile for the given camera at the given - * quality level. + * Returns the default camcorder profile for the given camera at the given quality level. * * Quality levels QUALITY_LOW, QUALITY_HIGH are guaranteed to be supported, while * other levels may or may not be supported. The supported levels can be checked using @@ -457,6 +508,7 @@ public class CamcorderProfile * @see #QUALITY_HIGH_SPEED_720P * @see #QUALITY_HIGH_SPEED_1080P * @see #QUALITY_HIGH_SPEED_2160P + * @throws IllegalArgumentException if quality is not one of the defined QUALITY_ values. */ public static CamcorderProfile get(int cameraId, int quality) { if (!((quality >= QUALITY_LIST_START && @@ -472,7 +524,119 @@ public class CamcorderProfile } /** - * Returns true if camcorder profile exists for the first back-facing + * Returns all encoder profiles of a camcorder profile for the given camera at + * the given quality level. + * + * Quality levels QUALITY_LOW, QUALITY_HIGH are guaranteed to be supported, while + * other levels may or may not be supported. The supported levels can be checked using + * {@link #hasProfile(int, int)}. + * QUALITY_LOW refers to the lowest quality available, while QUALITY_HIGH refers to + * the highest quality available. + * QUALITY_LOW/QUALITY_HIGH have to match one of qcif, cif, 480p, 720p, 1080p or 2160p. + * E.g. if the device supports 480p, 720p, 1080p and 2160p, then low is 480p and high is + * 2160p. + * + * The same is true for time lapse quality levels, i.e. QUALITY_TIME_LAPSE_LOW, + * QUALITY_TIME_LAPSE_HIGH are guaranteed to be supported and have to match one of + * qcif, cif, 480p, 720p, 1080p, or 2160p. + * + * For high speed quality levels, they may or may not be supported. If a subset of the levels + * are supported, QUALITY_HIGH_SPEED_LOW and QUALITY_HIGH_SPEED_HIGH are guaranteed to be + * supported and have to match one of 480p, 720p, or 1080p. + * + * A camcorder recording session with higher quality level usually has higher output + * bit rate, better video and/or audio recording quality, larger video frame + * resolution and higher audio sampling rate, etc, than those with lower quality + * level. + * + * @param cameraId the id for the camera. Numeric camera ids parsed from the list received by + * invoking {@link CameraManager#getCameraIdList} can be used as long as they + * are {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE} + * and not + * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL EXTERNAL}. + * @param quality the target quality level for the camcorder profile. + * @return null if there are no encoder profiles defined for the quality level for the + * given camera. + * @throws IllegalArgumentException if quality is not one of the defined QUALITY_ values. + * @see #QUALITY_LOW + * @see #QUALITY_HIGH + * @see #QUALITY_QCIF + * @see #QUALITY_CIF + * @see #QUALITY_480P + * @see #QUALITY_720P + * @see #QUALITY_1080P + * @see #QUALITY_2160P + * @see #QUALITY_TIME_LAPSE_LOW + * @see #QUALITY_TIME_LAPSE_HIGH + * @see #QUALITY_TIME_LAPSE_QCIF + * @see #QUALITY_TIME_LAPSE_CIF + * @see #QUALITY_TIME_LAPSE_480P + * @see #QUALITY_TIME_LAPSE_720P + * @see #QUALITY_TIME_LAPSE_1080P + * @see #QUALITY_TIME_LAPSE_2160P + * @see #QUALITY_HIGH_SPEED_LOW + * @see #QUALITY_HIGH_SPEED_HIGH + * @see #QUALITY_HIGH_SPEED_480P + * @see #QUALITY_HIGH_SPEED_720P + * @see #QUALITY_HIGH_SPEED_1080P + * @see #QUALITY_HIGH_SPEED_2160P + */ + @Nullable public static EncoderProfiles getAll( + @NonNull String cameraId, @Quality int quality) { + if (!((quality >= QUALITY_LIST_START && + quality <= QUALITY_LIST_END) || + (quality >= QUALITY_TIME_LAPSE_LIST_START && + quality <= QUALITY_TIME_LAPSE_LIST_END) || + (quality >= QUALITY_HIGH_SPEED_LIST_START && + quality <= QUALITY_HIGH_SPEED_LIST_END))) { + String errMessage = "Unsupported quality level: " + quality; + throw new IllegalArgumentException(errMessage); + } + + // TODO: get all profiles + int id; + try { + id = Integer.valueOf(cameraId); + } catch (NumberFormatException e) { + return null; + } + CamcorderProfile cp = native_get_camcorder_profile(id, quality); + if (cp == null) { + return null; + }; + + EncoderProfiles.AudioProfile[] audioProfiles; + // timelapse profiles do not list audio profiles + if (cp.quality >= QUALITY_TIME_LAPSE_LIST_START + && cp.quality <= QUALITY_TIME_LAPSE_LIST_END) { + audioProfiles = new EncoderProfiles.AudioProfile[] { }; + } else { + audioProfiles = new EncoderProfiles.AudioProfile[] { + new EncoderProfiles.AudioProfile( + cp.audioCodec, + cp.audioChannels, + cp.audioSampleRate, + cp.audioBitRate) + }; + } + + return new EncoderProfiles( + cp.duration, + cp.fileFormat, + new EncoderProfiles.VideoProfile[] { + new EncoderProfiles.VideoProfile( + cp.videoCodec, + cp.videoFrameWidth, + cp.videoFrameHeight, + cp.videoFrameRate, + cp.videoBitRate, + 0 /* TODO: get profile */) + }, + audioProfiles); + } + + /** + * Returns true if a camcorder profile exists for the first back-facing * camera at the given quality level. * *

@@ -507,7 +671,7 @@ public class CamcorderProfile } /** - * Returns true if camcorder profile exists for the given camera at + * Returns true if a camcorder profile exists for the given camera at * the given quality level. * *

diff --git a/media/java/android/media/EncoderProfiles.java b/media/java/android/media/EncoderProfiles.java new file mode 100644 index 0000000000000..ca3daefbdd53e --- /dev/null +++ b/media/java/android/media/EncoderProfiles.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.NonNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Describes a set of encoding profiles for a given media (audio and/or video) profile. + * These settings are read-only. + * + *

Currently, this is used to describe camera recording profile with more detail than {@link + * CamcorderProfile}, by providing encoding parameters for more than just the default audio + * and/or video codec. + * + *

The compressed output from a camera recording session contains two tracks: + * one for audio and one for video. + *

In the future audio-only recording profiles may be defined. + * + *

Each media profile specifies a set of audio and a set of video specific settings. + *

+ */ +public class EncoderProfiles +{ + /** + * Default recording duration in seconds before the session is terminated. + * This is useful for applications like MMS has limited file size requirement. + * This could be 0 if there is no default recording duration. + */ + public int getDurationSeconds() { + return durationSecs; + } + + /** + * Recommended output file format + * @see android.media.MediaRecorder.OutputFormat + */ + public int getFileFormat() { + return fileFormat; + } + + /** + * Configuration for a video encoder. + */ + public static class VideoProfile { + /** + * The video encoder being used for the video track + * @see android.media.MediaRecorder.VideoEncoder + */ + public int getCodec() { + return codec; + } + + /** + * The media type of the video encoder being used for the video track + * @see android.media.MediaFormat#KEY_MIME + */ + public @NonNull String getMediaType() { + if (codec == MediaRecorder.VideoEncoder.H263) { + return MediaFormat.MIMETYPE_VIDEO_H263; + } else if (codec == MediaRecorder.VideoEncoder.H264) { + return MediaFormat.MIMETYPE_VIDEO_AVC; + } else if (codec == MediaRecorder.VideoEncoder.MPEG_4_SP) { + return MediaFormat.MIMETYPE_VIDEO_MPEG4; + } else if (codec == MediaRecorder.VideoEncoder.VP8) { + return MediaFormat.MIMETYPE_VIDEO_VP8; + } else if (codec == MediaRecorder.VideoEncoder.HEVC) { + return MediaFormat.MIMETYPE_VIDEO_HEVC; + } + // we should never be here + throw new RuntimeException("Unknown codec"); + } + + /** + * The target video output bitrate in bits per second + *

+ * This is the target recorded video output bitrate if the application configures the video + * recording via {@link MediaRecorder#setProfile} without specifying any other + * {@link MediaRecorder} encoding parameters. For example, for high speed quality profiles + * (from {@link CamcorderProfile#QUALITY_HIGH_SPEED_LOW} to {@link + * CamcorderProfile#QUALITY_HIGH_SPEED_2160P}), this is the bitrate where the video is + * recorded with. If the application intends to record slow motion videos with the high + * speed quality profiles, it must set a different video bitrate that is corresponding to + * the desired recording output bit rate (i.e., the encoded video bitrate during normal + * playback) via {@link MediaRecorder#setVideoEncodingBitRate}. For example, if {@link + * CamcorderProfile#QUALITY_HIGH_SPEED_720P} advertises 240fps {@link #getFrameRate} and + * 64Mbps {@link #getBitrate} in the high speed VideoProfile, and the application + * intends to record 1/8 factor slow motion recording videos, the application must set 30fps + * via {@link MediaRecorder#setVideoFrameRate} and 8Mbps ( {@link #getBitrate} * slow motion + * factor) via {@link MediaRecorder#setVideoEncodingBitRate}. Failing to do so will result + * in videos with unexpected frame rate and bit rate, or {@link MediaRecorder} error if the + * output bit rate exceeds the encoder limit. If the application intends to do the video + * recording with {@link MediaCodec} encoder, it must set each individual field of {@link + * MediaFormat} similarly according to this VideoProfile. + *

+ * + * @see #getFrameRate + * @see MediaRecorder + * @see MediaCodec + * @see MediaFormat + */ + public int getBitrate() { + return bitrate; + } + + /** + * The target video frame rate in frames per second. + *

+ * This is the target recorded video output frame rate per second if the application + * configures the video recording via {@link MediaRecorder#setProfile} without specifying + * any other {@link MediaRecorder} encoding parameters. For example, for high speed quality + * profiles (from {@link CamcorderProfile#QUALITY_HIGH_SPEED_LOW} to {@link + * CamcorderProfile#QUALITY_HIGH_SPEED_2160P}), this is the frame rate where the video is + * recorded and played back with. If the application intends to create slow motion use case + * with the high speed quality profiles, it must set a different video frame rate that is + * corresponding to the desired output (playback) frame rate via {@link + * MediaRecorder#setVideoFrameRate}. For example, if {@link + * CamcorderProfile#QUALITY_HIGH_SPEED_720P} advertises 240fps {@link #getFrameRate} + * in the VideoProfile, and the application intends to create 1/8 factor slow motion + * recording videos, the application must set 30fps via {@link + * MediaRecorder#setVideoFrameRate}. Failing to do so will result in high speed videos with + * normal speed playback frame rate (240fps for above example). If the application intends + * to do the video recording with {@link MediaCodec} encoder, it must set each individual + * field of {@link MediaFormat} similarly according to this VideoProfile. + *

+ * + * @see #getBitrate + * @see MediaRecorder + * @see MediaCodec + * @see MediaFormat + */ + public int getFrameRate() { + return frameRate; + } + + /** + * The target video frame width in pixels + */ + public int getWidth() { + return width; + } + + /** + * The target video frame height in pixels + */ + public int getHeight() { + return height; + } + + /** + * The video encoder profile being used for the video track. + *

+ * This value is 0 if there is no profile defined for the video codec. + * + * @see MediaRecorder#setVideoEncodingProfileLevel + * @see MediaFormat#KEY_PROFILE + */ + public int getProfile() { + return profile; + } + + // Constructor called by JNI and CamcorderProfile + /* package private */ VideoProfile(int codec, + int width, + int height, + int frameRate, + int bitrate, + int profile) { + this.codec = codec; + this.width = width; + this.height = height; + this.frameRate = frameRate; + this.bitrate = bitrate; + this.profile = profile; + } + + private int codec; + private int width; + private int height; + private int frameRate; + private int bitrate; + private int profile; + } + + /** + * Returns the defined audio encoder profiles. + *

+ * The list may be empty. This means there are no audio encoder + * profiles defined. Otherwise, the first profile is the default + * audio profile. + */ + public @NonNull List getAudioProfiles() { + return audioProfiles; + } + + /** + * Returns the defined video encoder profiles. + *

+ * The list may be empty. This means there are no video encoder + * profiles defined. Otherwise, the first profile is the default + * video profile. + */ + public @NonNull List getVideoProfiles() { + return videoProfiles; + } + + /** + * Configuration for an audio encoder. + */ + public static class AudioProfile { + /** + * The audio encoder being used for the audio track. + * @see android.media.MediaRecorder.AudioEncoder + */ + public int getCodec() { + return codec; + } + + /** + * The media type of the audio encoder being used for the video track + * @see android.media.MediaFormat#KEY_MIME + */ + public @NonNull String getMediaType() { + if (codec == MediaRecorder.AudioEncoder.AMR_NB) { + return MediaFormat.MIMETYPE_AUDIO_AMR_NB; + } else if (codec == MediaRecorder.AudioEncoder.AMR_WB) { + return MediaFormat.MIMETYPE_AUDIO_AMR_WB; + } else if (codec == MediaRecorder.AudioEncoder.AAC + || codec == MediaRecorder.AudioEncoder.HE_AAC + || codec == MediaRecorder.AudioEncoder.AAC_ELD) { + return MediaFormat.MIMETYPE_AUDIO_AAC; + } else if (codec == MediaRecorder.AudioEncoder.VORBIS) { + return MediaFormat.MIMETYPE_AUDIO_VORBIS; + } else if (codec == MediaRecorder.AudioEncoder.OPUS) { + return MediaFormat.MIMETYPE_AUDIO_OPUS; + } + // we should never be here + throw new RuntimeException("Unknown codec"); + } + + /** + * The target audio output bitrate in bits per second + */ + public int getBitrate() { + return bitrate; + } + + /** + * The audio sampling rate used for the audio track + */ + public int getSampleRate() { + return sampleRate; + } + + /** + * The number of audio channels used for the audio track + */ + public int getChannels() { + return channels; + } + + /** + * The audio encoder profile being used for the audio track + *

+ * This value is 0 if there is no profile defined for the audio codec. + * @see MediaFormat#KEY_PROFILE + */ + public int getProfile() { + if (codec == MediaRecorder.AudioEncoder.AAC) { + return MediaCodecInfo.CodecProfileLevel.AACObjectMain; + } else if (codec == MediaRecorder.AudioEncoder.HE_AAC) { + return MediaCodecInfo.CodecProfileLevel.AACObjectHE; + } else if (codec == MediaRecorder.AudioEncoder.AAC_ELD) { + return MediaCodecInfo.CodecProfileLevel.AACObjectELD; + } + return 0; + } + + + // Constructor called by JNI and CamcorderProfile + /* package private */ AudioProfile( + int codec, + int channels, + int sampleRate, + int bitrate) { + this.codec = codec; + this.channels = channels; + this.sampleRate = sampleRate; + this.bitrate = bitrate; + } + + private int codec; + private int channels; + private int sampleRate; + private int bitrate; + } + + //static { + // System.loadLibrary("media_jni"); + //native_init(); + //} + + private int durationSecs; + private int fileFormat; + // non-modifiable lists + private @NonNull List audioProfiles; + private @NonNull List videoProfiles; + + // Constructor called by JNI and CamcorderProfile + /* package private */ EncoderProfiles( + int duration, + int fileFormat, + VideoProfile[] videoProfiles, + AudioProfile[] audioProfiles) { + this.durationSecs = duration; + this.fileFormat = fileFormat; + this.videoProfiles = Collections.unmodifiableList(Arrays.asList(videoProfiles)); + this.audioProfiles = Collections.unmodifiableList(Arrays.asList(audioProfiles)); + } +} diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index f960ff2b1a7e7..5eb57dae1edc2 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -752,6 +752,47 @@ public class MediaRecorder implements AudioRouting, } } + /** + * Uses the settings from an AudioProfile for recording. + *

+ * This method should be called after the video AND audio sources are set, and before + * setOutputFile(). + *

+ * This method can be used instead of {@link #setProfile} when using EncoderProfiles. + * + * @param profile the AudioProfile to use + * @see android.media.EncoderProfiles + * @see android.media.CamcorderProfile#getAll + */ + public void setAudioProfile(@NonNull EncoderProfiles.AudioProfile profile) { + setAudioEncodingBitRate(profile.getBitrate()); + setAudioChannels(profile.getChannels()); + setAudioSamplingRate(profile.getSampleRate()); + setAudioEncoder(profile.getCodec()); + } + + /** + * Uses the settings from a VideoProfile object for recording. + *

+ * This method should be called after the video AND audio sources are set, and before + * setOutputFile(). + *

+ * This method can be used instead of {@link #setProfile} when using EncoderProfiles. + * + * @param profile the VideoProfile to use + * @see android.media.EncoderProfiles + * @see android.media.CamcorderProfile#getAll + */ + public void setVideoProfile(@NonNull EncoderProfiles.VideoProfile profile) { + setVideoFrameRate(profile.getFrameRate()); + setVideoSize(profile.getWidth(), profile.getHeight()); + setVideoEncodingBitRate(profile.getBitrate()); + setVideoEncoder(profile.getCodec()); + if (profile.getProfile() > 0) { + setVideoEncodingProfileLevel(profile.getProfile(), 0 /* level */); + } + } + /** * Set video frame capture rate. This can be used to set a different video frame capture * rate than the recorded video's playback rate. This method also sets the recording mode @@ -1961,4 +2002,3 @@ public class MediaRecorder implements AudioRouting, } } -