diff options
author | 2024-06-12 21:13:44 +0000 | |
---|---|---|
committer | 2025-01-03 00:50:05 +0000 | |
commit | 50b7ad8af009a1cf585775b853e35e9d742b3dab (patch) | |
tree | d651b845925c43deba54c7489990b52ecd08fb27 | |
parent | 4514061228f8b93994377d60df6de99e3a29cf9d (diff) |
Java and JNI support for native AudioCapabilities.
Bug: b/306023029
Test: MediaCodecCapabilitiesTest
Change-Id: I2bf2d91e89b63186a708e9c4137d10527331cfdd
-rw-r--r-- | media/java/android/media/MediaCodecInfo.java | 898 | ||||
-rw-r--r-- | media/jni/Android.bp | 2 | ||||
-rw-r--r-- | media/jni/android_media_CodecCapabilities.cpp | 117 | ||||
-rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 6 |
4 files changed, 659 insertions, 364 deletions
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 782db358bf9f..87c82b02697a 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -1104,10 +1104,11 @@ public final class MediaCodecInfo { // does not contain features and bitrate specific keys, keep only keys relevant for // a level check. Map<String, Object> levelCriticalFormatMap = new HashMap<>(map); - final Set<String> criticalKeys = - isVideo() ? VideoCapabilities.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS : - isAudio() ? AudioCapabilities.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS : - null; + final Set<String> criticalKeys = isVideo() + ? VideoCapabilities.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS + : isAudio() + ? AudioCapabilities.AudioCapsLegacyImpl.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS + : null; // critical keys will always contain KEY_MIME, but should also contain others to be // meaningful @@ -1410,434 +1411,597 @@ public final class MediaCodecInfo { */ public static final class AudioCapabilities { private static final String TAG = "AudioCapabilities"; - private CodecCapabilities mParent; - private Range<Integer> mBitrateRange; - private int[] mSampleRates; - private Range<Integer>[] mSampleRateRanges; - private Range<Integer>[] mInputChannelRanges; + /* package private */ interface AudioCapsIntf { + public Range<Integer> getBitrateRange(); - private static final int MAX_INPUT_CHANNEL_COUNT = 30; + public int[] getSupportedSampleRates(); - /** - * Returns the range of supported bitrates in bits/second. - */ - public Range<Integer> getBitrateRange() { - return mBitrateRange; - } + public Range<Integer>[] getSupportedSampleRateRanges(); - /** - * Returns the array of supported sample rates if the codec - * supports only discrete values. Otherwise, it returns - * {@code null}. The array is sorted in ascending order. - */ - public int[] getSupportedSampleRates() { - return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) : null; - } + public int getMaxInputChannelCount(); - /** - * Returns the array of supported sample rate ranges. The - * array is sorted in ascending order, and the ranges are - * distinct. - */ - public Range<Integer>[] getSupportedSampleRateRanges() { - return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); + public int getMinInputChannelCount(); + + public Range<Integer>[] getInputChannelCountRanges(); + + public boolean isSampleRateSupported(int sampleRate); + + public void getDefaultFormat(MediaFormat format); + + public boolean supportsFormat(MediaFormat format); } - /** - * Returns the maximum number of input channels supported. - * - * Through {@link android.os.Build.VERSION_CODES#R}, this method indicated support - * for any number of input channels between 1 and this maximum value. - * - * As of {@link android.os.Build.VERSION_CODES#S}, - * the implied lower limit of 1 channel is no longer valid. - * As of {@link android.os.Build.VERSION_CODES#S}, {@link #getMaxInputChannelCount} is - * superseded by {@link #getInputChannelCountRanges}, - * which returns an array of ranges of channels. - * The {@link #getMaxInputChannelCount} method will return the highest value - * in the ranges returned by {@link #getInputChannelCountRanges} - * - */ - @IntRange(from = 1, to = 255) - public int getMaxInputChannelCount() { - int overall_max = 0; - for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { - int lmax = mInputChannelRanges[i].getUpper(); - if (lmax > overall_max) { - overall_max = lmax; + /* package private */ static final class AudioCapsLegacyImpl implements AudioCapsIntf { + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; + + private int[] mSampleRates; + private Range<Integer>[] mSampleRateRanges; + private Range<Integer>[] mInputChannelRanges; + + private static final int MAX_INPUT_CHANNEL_COUNT = 30; + + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } + + public int[] getSupportedSampleRates() { + return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) + : null; + } + + public Range<Integer>[] getSupportedSampleRateRanges() { + return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); + } + + public int getMaxInputChannelCount() { + int overall_max = 0; + for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { + int lmax = mInputChannelRanges[i].getUpper(); + if (lmax > overall_max) { + overall_max = lmax; + } } + return overall_max; } - return overall_max; - } - /** - * Returns the minimum number of input channels supported. - * This is often 1, but does vary for certain mime types. - * - * This returns the lowest channel count in the ranges returned by - * {@link #getInputChannelCountRanges}. - */ - @IntRange(from = 1, to = 255) - public int getMinInputChannelCount() { - int overall_min = MAX_INPUT_CHANNEL_COUNT; - for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { - int lmin = mInputChannelRanges[i].getLower(); - if (lmin < overall_min) { - overall_min = lmin; + public int getMinInputChannelCount() { + int overall_min = MAX_INPUT_CHANNEL_COUNT; + for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { + int lmin = mInputChannelRanges[i].getLower(); + if (lmin < overall_min) { + overall_min = lmin; + } } + return overall_min; } - return overall_min; - } - /* - * Returns an array of ranges representing the number of input channels supported. - * The codec supports any number of input channels within this range. - * - * This supersedes the {@link #getMaxInputChannelCount} method. - * - * For many codecs, this will be a single range [1..N], for some N. - */ - @SuppressLint("ArrayReturn") - @NonNull - public Range<Integer>[] getInputChannelCountRanges() { - return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length); - } + public Range<Integer>[] getInputChannelCountRanges() { + return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length); + } - /* no public constructor */ - private AudioCapabilities() { } + /* no public constructor */ + private AudioCapsLegacyImpl() { } - /** @hide */ - public static AudioCapabilities create( - MediaFormat info, CodecCapabilities parent) { - AudioCapabilities caps = new AudioCapabilities(); - caps.init(info, parent); - return caps; - } + public static AudioCapsLegacyImpl create( + MediaFormat info, CodecCapabilities parent) { + if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { + Log.d(TAG, "Legacy implementation is called while native flag is on."); + } - private void init(MediaFormat info, CodecCapabilities parent) { - mParent = parent; - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); - } + AudioCapsLegacyImpl caps = new AudioCapsLegacyImpl(); + caps.init(info, parent); + return caps; + } - private void initWithPlatformLimits() { - mBitrateRange = Range.create(0, Integer.MAX_VALUE); - mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)}; - // mBitrateRange = Range.create(1, 320000); - final int minSampleRate = SystemProperties. - getInt("ro.mediacodec.min_sample_rate", 7350); - final int maxSampleRate = SystemProperties. - getInt("ro.mediacodec.max_sample_rate", 192000); - mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) }; - mSampleRates = null; - } + private void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + } - private boolean supports(Integer sampleRate, Integer inputChannels) { - // channels and sample rates are checked orthogonally - if (inputChannels != null) { - int ix = Utils.binarySearchDistinctRanges( - mInputChannelRanges, inputChannels); - if (ix < 0) { - return false; + private void initWithPlatformLimits() { + mBitrateRange = Range.create(0, Integer.MAX_VALUE); + mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)}; + // mBitrateRange = Range.create(1, 320000); + final int minSampleRate = SystemProperties + .getInt("ro.mediacodec.min_sample_rate", 7350); + final int maxSampleRate = SystemProperties + .getInt("ro.mediacodec.max_sample_rate", 192000); + mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) }; + mSampleRates = null; + } + + private boolean supports(Integer sampleRate, Integer inputChannels) { + // channels and sample rates are checked orthogonally + if (inputChannels != null) { + int ix = Utils.binarySearchDistinctRanges( + mInputChannelRanges, inputChannels); + if (ix < 0) { + return false; + } + } + if (sampleRate != null) { + int ix = Utils.binarySearchDistinctRanges( + mSampleRateRanges, sampleRate); + if (ix < 0) { + return false; + } } + return true; } - if (sampleRate != null) { - int ix = Utils.binarySearchDistinctRanges( - mSampleRateRanges, sampleRate); - if (ix < 0) { - return false; + + public boolean isSampleRateSupported(int sampleRate) { + return supports(sampleRate, null); + } + + /** modifies rates */ + private void limitSampleRates(int[] rates) { + Arrays.sort(rates); + ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); + for (int rate: rates) { + if (supports(rate, null /* channels */)) { + ranges.add(Range.create(rate, rate)); + } } + mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); + createDiscreteSampleRates(); } - return true; - } - /** - * Query whether the sample rate is supported by the codec. - */ - public boolean isSampleRateSupported(int sampleRate) { - return supports(sampleRate, null); - } + private void createDiscreteSampleRates() { + mSampleRates = new int[mSampleRateRanges.length]; + for (int i = 0; i < mSampleRateRanges.length; i++) { + mSampleRates[i] = mSampleRateRanges[i].getLower(); + } + } - /** modifies rates */ - private void limitSampleRates(int[] rates) { - Arrays.sort(rates); - ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); - for (int rate: rates) { - if (supports(rate, null /* channels */)) { - ranges.add(Range.create(rate, rate)); + /** modifies rateRanges */ + private void limitSampleRates(Range<Integer>[] rateRanges) { + sortDistinctRanges(rateRanges); + mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); + + // check if all values are discrete + for (Range<Integer> range: mSampleRateRanges) { + if (!range.getLower().equals(range.getUpper())) { + mSampleRates = null; + return; + } } + createDiscreteSampleRates(); } - mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); - createDiscreteSampleRates(); - } - private void createDiscreteSampleRates() { - mSampleRates = new int[mSampleRateRanges.length]; - for (int i = 0; i < mSampleRateRanges.length; i++) { - mSampleRates[i] = mSampleRateRanges[i].getLower(); + private void applyLevelLimits() { + int[] sampleRates = null; + Range<Integer> sampleRateRange = null, bitRates = null; + int maxChannels = MAX_INPUT_CHANNEL_COUNT; + CodecProfileLevel[] profileLevels = mParent.profileLevels; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { + sampleRates = new int[] { + 8000, 11025, 12000, + 16000, 22050, 24000, + 32000, 44100, 48000 }; + bitRates = Range.create(8000, 320000); + maxChannels = 2; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(4750, 12200); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { + sampleRates = new int[] { 16000 }; + bitRates = Range.create(6600, 23850); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { + sampleRates = new int[] { + 7350, 8000, + 11025, 12000, 16000, + 22050, 24000, 32000, + 44100, 48000, 64000, + 88200, 96000 }; + bitRates = Range.create(8000, 510000); + maxChannels = 48; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { + bitRates = Range.create(32000, 500000); + sampleRateRange = Range.create(8000, 192000); + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { + bitRates = Range.create(6000, 510000); + sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { + sampleRateRange = Range.create(1, 192000); + bitRates = Range.create(1, 10000000); + maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + sampleRateRange = Range.create(1, 655350); + // lossless codec, so bitrate is ignored + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(64000, 64000); + // platform allows multiple channels for this format + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(13000, 13000); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) { + maxChannels = 6; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) { + maxChannels = 16; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) { + sampleRates = new int[] { 48000 }; + bitRates = Range.create(32000, 6144000); + maxChannels = 16; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) { + sampleRates = new int[] { 44100, 48000, 96000, 192000 }; + bitRates = Range.create(16000, 2688000); + maxChannels = 24; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) { + sampleRates = new int[] { 44100, 48000 }; + bitRates = Range.create(96000, 1524000); + maxChannels = 6; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) { + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.profile) { + case CodecProfileLevel.DTS_HDProfileLBR: + sampleRates = new int[]{ 22050, 24000, 44100, 48000 }; + bitRates = Range.create(32000, 768000); + break; + case CodecProfileLevel.DTS_HDProfileHRA: + case CodecProfileLevel.DTS_HDProfileMA: + sampleRates = + new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + mParent.mError |= ERROR_UNRECOGNIZED; + sampleRates = + new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + } + } + maxChannels = 8; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) { + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.profile) { + case CodecProfileLevel.DTS_UHDProfileP2: + sampleRates = new int[]{ 48000 }; + bitRates = Range.create(96000, 768000); + maxChannels = 10; + break; + case CodecProfileLevel.DTS_UHDProfileP1: + sampleRates = + new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + maxChannels = 32; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + mParent.mError |= ERROR_UNRECOGNIZED; + sampleRates = + new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + maxChannels = 32; + } + } + } else { + Log.w(TAG, "Unsupported mime " + mime); + mParent.mError |= ERROR_UNSUPPORTED; + } + + // restrict ranges + if (sampleRates != null) { + limitSampleRates(sampleRates); + } else if (sampleRateRange != null) { + limitSampleRates(new Range[] { sampleRateRange }); + } + + Range<Integer> channelRange = Range.create(1, maxChannels); + + applyLimits(new Range[] { channelRange }, bitRates); } - } - /** modifies rateRanges */ - private void limitSampleRates(Range<Integer>[] rateRanges) { - sortDistinctRanges(rateRanges); - mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); + private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) { - // check if all values are discrete - for (Range<Integer> range: mSampleRateRanges) { - if (!range.getLower().equals(range.getUpper())) { - mSampleRates = null; - return; + // clamp & make a local copy + Range<Integer>[] myInputChannels = new Range[inputChannels.length]; + for (int i = 0; i < inputChannels.length; i++) { + int lower = inputChannels[i].clamp(1); + int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT); + myInputChannels[i] = Range.create(lower, upper); + } + + // sort, intersect with existing, & save channel list + sortDistinctRanges(myInputChannels); + Range<Integer>[] joinedChannelList = + intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges); + mInputChannelRanges = joinedChannelList; + + if (bitRates != null) { + mBitrateRange = mBitrateRange.intersect(bitRates); } } - createDiscreteSampleRates(); - } - private void applyLevelLimits() { - int[] sampleRates = null; - Range<Integer> sampleRateRange = null, bitRates = null; - int maxChannels = MAX_INPUT_CHANNEL_COUNT; - CodecProfileLevel[] profileLevels = mParent.profileLevels; - String mime = mParent.getMimeType(); + private void parseFromInfo(MediaFormat info) { + int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; + Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)}; + Range<Integer> bitRates = POSITIVE_INTEGERS; - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { - sampleRates = new int[] { - 8000, 11025, 12000, - 16000, 22050, 24000, - 32000, 44100, 48000 }; - bitRates = Range.create(8000, 320000); - maxChannels = 2; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(4750, 12200); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { - sampleRates = new int[] { 16000 }; - bitRates = Range.create(6600, 23850); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { - sampleRates = new int[] { - 7350, 8000, - 11025, 12000, 16000, - 22050, 24000, 32000, - 44100, 48000, 64000, - 88200, 96000 }; - bitRates = Range.create(8000, 510000); - maxChannels = 48; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { - bitRates = Range.create(32000, 500000); - sampleRateRange = Range.create(8000, 192000); - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { - bitRates = Range.create(6000, 510000); - sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { - sampleRateRange = Range.create(1, 192000); - bitRates = Range.create(1, 10000000); - maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - sampleRateRange = Range.create(1, 655350); - // lossless codec, so bitrate is ignored - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(64000, 64000); - // platform allows multiple channels for this format - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(13000, 13000); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) { - maxChannels = 6; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) { - maxChannels = 16; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) { - sampleRates = new int[] { 48000 }; - bitRates = Range.create(32000, 6144000); - maxChannels = 16; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) { - sampleRates = new int[] { 44100, 48000, 96000, 192000 }; - bitRates = Range.create(16000, 2688000); - maxChannels = 24; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) { - sampleRates = new int[] { 44100, 48000 }; - bitRates = Range.create(96000, 1524000); - maxChannels = 6; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) { - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.profile) { - case CodecProfileLevel.DTS_HDProfileLBR: - sampleRates = new int[]{ 22050, 24000, 44100, 48000 }; - bitRates = Range.create(32000, 768000); - break; - case CodecProfileLevel.DTS_HDProfileHRA: - case CodecProfileLevel.DTS_HDProfileMA: - sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - mParent.mError |= ERROR_UNRECOGNIZED; - sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); + if (info.containsKey("sample-rate-ranges")) { + String[] rateStrings = info.getString("sample-rate-ranges").split(","); + Range<Integer>[] rateRanges = new Range[rateStrings.length]; + for (int i = 0; i < rateStrings.length; i++) { + rateRanges[i] = Utils.parseIntRange(rateStrings[i], null); } + limitSampleRates(rateRanges); } - maxChannels = 8; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) { - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.profile) { - case CodecProfileLevel.DTS_UHDProfileP2: - sampleRates = new int[]{ 48000 }; - bitRates = Range.create(96000, 768000); - maxChannels = 10; - break; - case CodecProfileLevel.DTS_UHDProfileP1: - sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); - maxChannels = 32; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - mParent.mError |= ERROR_UNRECOGNIZED; - sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); - maxChannels = 32; + + // we will prefer channel-ranges over max-channel-count + if (info.containsKey("channel-ranges")) { + String[] channelStrings = info.getString("channel-ranges").split(","); + Range<Integer>[] channelRanges = new Range[channelStrings.length]; + for (int i = 0; i < channelStrings.length; i++) { + channelRanges[i] = Utils.parseIntRange(channelStrings[i], null); } + channels = channelRanges; + } else if (info.containsKey("channel-range")) { + Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"), + null); + channels = new Range[] { oneRange }; + } else if (info.containsKey("max-channel-count")) { + maxInputChannels = Utils.parseIntSafely( + info.getString("max-channel-count"), maxInputChannels); + if (maxInputChannels == 0) { + channels = new Range[] {Range.create(0, 0)}; + } else { + channels = new Range[] {Range.create(1, maxInputChannels)}; + } + } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { + maxInputChannels = 0; + channels = new Range[] {Range.create(0, 0)}; } - } else { - Log.w(TAG, "Unsupported mime " + mime); - mParent.mError |= ERROR_UNSUPPORTED; + + if (info.containsKey("bitrate-range")) { + bitRates = bitRates.intersect( + Utils.parseIntRange(info.getString("bitrate-range"), bitRates)); + } + + applyLimits(channels, bitRates); } - // restrict ranges - if (sampleRates != null) { - limitSampleRates(sampleRates); - } else if (sampleRateRange != null) { - limitSampleRates(new Range[] { sampleRateRange }); + /** @hide */ + public void getDefaultFormat(MediaFormat format) { + // report settings that have only a single choice + if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { + format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); + } + if (getMaxInputChannelCount() == 1) { + // mono-only format + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + } + if (mSampleRates != null && mSampleRates.length == 1) { + format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + } } - Range<Integer> channelRange = Range.create(1, maxChannels); + /* package private */ + // must not contain KEY_PROFILE + static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( + // We don't set level-specific limits for audio codecs today. Key candidates + // would be sample rate, bit rate or channel count. + // MediaFormat.KEY_SAMPLE_RATE, + // MediaFormat.KEY_CHANNEL_COUNT, + // MediaFormat.KEY_BIT_RATE, + MediaFormat.KEY_MIME); + + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + Map<String, Object> map = format.getMap(); + Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE); + Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT); + + if (!supports(sampleRate, channels)) { + return false; + } - applyLimits(new Range[] { channelRange }, bitRates); + if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) { + return false; + } + + // nothing to do for: + // KEY_CHANNEL_MASK: codecs don't get this + // KEY_IS_ADTS: required feature for all AAC decoders + return true; + } } - private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) { + /* package private */ static final class AudioCapsNativeImpl implements AudioCapsIntf { + private long mNativeContext; // accessed by native methods - // clamp & make a local copy - Range<Integer>[] myInputChannels = new Range[inputChannels.length]; - for (int i = 0; i < inputChannels.length; i++) { - int lower = inputChannels[i].clamp(1); - int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT); - myInputChannels[i] = Range.create(lower, upper); + private Range<Integer> mBitrateRange; + private int[] mSampleRates; + private Range<Integer>[] mSampleRateRanges; + private Range<Integer>[] mInputChannelRanges; + + /** + * Constructor used by JNI. + * + * The Java AudioCapabilities object keeps these subobjects to avoid recontruction. + */ + /* package private */ AudioCapsNativeImpl(Range<Integer> bitrateRange, + int[] sampleRates, Range<Integer>[] sampleRateRanges, + Range<Integer>[] inputChannelRanges) { + mBitrateRange = bitrateRange; + mSampleRates = sampleRates; + mSampleRateRanges = sampleRateRanges; + mInputChannelRanges = inputChannelRanges; } - // sort, intersect with existing, & save channel list - sortDistinctRanges(myInputChannels); - Range<Integer>[] joinedChannelList = - intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges); - mInputChannelRanges = joinedChannelList; + /* no public constructor */ + private AudioCapsNativeImpl() { } - if (bitRates != null) { - mBitrateRange = mBitrateRange.intersect(bitRates); + public Range<Integer> getBitrateRange() { + return mBitrateRange; } - } - private void parseFromInfo(MediaFormat info) { - int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; - Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)}; - Range<Integer> bitRates = POSITIVE_INTEGERS; - - if (info.containsKey("sample-rate-ranges")) { - String[] rateStrings = info.getString("sample-rate-ranges").split(","); - Range<Integer>[] rateRanges = new Range[rateStrings.length]; - for (int i = 0; i < rateStrings.length; i++) { - rateRanges[i] = Utils.parseIntRange(rateStrings[i], null); - } - limitSampleRates(rateRanges); + public int[] getSupportedSampleRates() { + return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) + : null; } - // we will prefer channel-ranges over max-channel-count - if (info.containsKey("channel-ranges")) { - String[] channelStrings = info.getString("channel-ranges").split(","); - Range<Integer>[] channelRanges = new Range[channelStrings.length]; - for (int i = 0; i < channelStrings.length; i++) { - channelRanges[i] = Utils.parseIntRange(channelStrings[i], null); - } - channels = channelRanges; - } else if (info.containsKey("channel-range")) { - Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"), - null); - channels = new Range[] { oneRange }; - } else if (info.containsKey("max-channel-count")) { - maxInputChannels = Utils.parseIntSafely( - info.getString("max-channel-count"), maxInputChannels); - if (maxInputChannels == 0) { - channels = new Range[] {Range.create(0, 0)}; - } else { - channels = new Range[] {Range.create(1, maxInputChannels)}; - } - } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { - maxInputChannels = 0; - channels = new Range[] {Range.create(0, 0)}; + public Range<Integer>[] getSupportedSampleRateRanges() { + return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); } - if (info.containsKey("bitrate-range")) { - bitRates = bitRates.intersect( - Utils.parseIntRange(info.getString("bitrate-range"), bitRates)); + public Range<Integer>[] getInputChannelCountRanges() { + return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length); } - applyLimits(channels, bitRates); - } + public int getMaxInputChannelCount() { + return native_getMaxInputChannelCount(); + } - /** @hide */ - public void getDefaultFormat(MediaFormat format) { - // report settings that have only a single choice - if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { - format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); + public int getMinInputChannelCount() { + return native_getMinInputChannelCount(); } - if (getMaxInputChannelCount() == 1) { - // mono-only format - format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + + public boolean isSampleRateSupported(int sampleRate) { + return native_isSampleRateSupported(sampleRate); } - if (mSampleRates != null && mSampleRates.length == 1) { - format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + + // This API is for internal Java implementation only. Should not be called. + public void getDefaultFormat(MediaFormat format) { + throw new UnsupportedOperationException( + "Java Implementation should not call native implemenatation"); + } + + // This API is for internal Java implementation only. Should not be called. + public boolean supportsFormat(MediaFormat format) { + throw new UnsupportedOperationException( + "Java Implementation should not call native implemenatation"); + } + + private native int native_getMaxInputChannelCount(); + private native int native_getMinInputChannelCount(); + private native boolean native_isSampleRateSupported(int sampleRate); + private static native void native_init(); + + static { + System.loadLibrary("media_jni"); + native_init(); } } - /* package private */ - // must not contain KEY_PROFILE - static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( - // We don't set level-specific limits for audio codecs today. Key candidates would - // be sample rate, bit rate or channel count. - // MediaFormat.KEY_SAMPLE_RATE, - // MediaFormat.KEY_CHANNEL_COUNT, - // MediaFormat.KEY_BIT_RATE, - MediaFormat.KEY_MIME); + private AudioCapsIntf mImpl; /** @hide */ - public boolean supportsFormat(MediaFormat format) { - Map<String, Object> map = format.getMap(); - Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE); - Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT); + public static AudioCapabilities create( + MediaFormat info, CodecCapabilities parent) { + AudioCapsLegacyImpl impl = AudioCapsLegacyImpl.create(info, parent); + AudioCapabilities caps = new AudioCapabilities(impl); + return caps; + } - if (!supports(sampleRate, channels)) { - return false; - } + /* package private */ AudioCapabilities(AudioCapsIntf impl) { + mImpl = impl; + } - if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) { - return false; - } + /* no public constructor */ + private AudioCapabilities() { } - // nothing to do for: - // KEY_CHANNEL_MASK: codecs don't get this - // KEY_IS_ADTS: required feature for all AAC decoders - return true; + /** + * Returns the range of supported bitrates in bits/second. + */ + public Range<Integer> getBitrateRange() { + return mImpl.getBitrateRange(); + } + + /** + * Returns the array of supported sample rates if the codec + * supports only discrete values. Otherwise, it returns + * {@code null}. The array is sorted in ascending order. + */ + public int[] getSupportedSampleRates() { + return mImpl.getSupportedSampleRates(); + } + + /** + * Returns the array of supported sample rate ranges. The + * array is sorted in ascending order, and the ranges are + * distinct. + */ + public Range<Integer>[] getSupportedSampleRateRanges() { + return mImpl.getSupportedSampleRateRanges(); + } + + /* + * Returns an array of ranges representing the number of input channels supported. + * The codec supports any number of input channels within this range. + * + * This supersedes the {@link #getMaxInputChannelCount} method. + * + * For many codecs, this will be a single range [1..N], for some N. + */ + @SuppressLint("ArrayReturn") + @NonNull + public Range<Integer>[] getInputChannelCountRanges() { + return mImpl.getInputChannelCountRanges(); + } + + /** + * Returns the maximum number of input channels supported. + * + * Through {@link android.os.Build.VERSION_CODES#R}, this method indicated support + * for any number of input channels between 1 and this maximum value. + * + * As of {@link android.os.Build.VERSION_CODES#S}, + * the implied lower limit of 1 channel is no longer valid. + * As of {@link android.os.Build.VERSION_CODES#S}, {@link #getMaxInputChannelCount} is + * superseded by {@link #getInputChannelCountRanges}, + * which returns an array of ranges of channels. + * The {@link #getMaxInputChannelCount} method will return the highest value + * in the ranges returned by {@link #getInputChannelCountRanges} + * + */ + @IntRange(from = 1, to = 255) + public int getMaxInputChannelCount() { + return mImpl.getMaxInputChannelCount(); + } + + /** + * Returns the minimum number of input channels supported. + * This is often 1, but does vary for certain mime types. + * + * This returns the lowest channel count in the ranges returned by + * {@link #getInputChannelCountRanges}. + */ + @IntRange(from = 1, to = 255) + public int getMinInputChannelCount() { + return mImpl.getMinInputChannelCount(); + } + + /** + * Query whether the sample rate is supported by the codec. + */ + public boolean isSampleRateSupported(int sampleRate) { + return mImpl.isSampleRateSupported(sampleRate); + } + + /** @hide */ + public void getDefaultFormat(MediaFormat format) { + mImpl.getDefaultFormat(format); + } + + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + return mImpl.supportsFormat(format); } } @@ -4826,4 +4990,10 @@ public final class MediaCodecInfo { mName, mCanonicalName, mFlags, caps.toArray(new CodecCapabilities[caps.size()])); } + + /* package private */ class GenericHelper { + private static Range<Integer> constructIntegerRange(int lower, int upper) { + return Range.create(Integer.valueOf(lower), Integer.valueOf(upper)); + } + } } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index f09dc7218d7d..af545d5a4bc4 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -25,6 +25,7 @@ cc_library_shared { min_sdk_version: "", srcs: [ + "android_media_CodecCapabilities.cpp", "android_media_ImageWriter.cpp", "android_media_ImageReader.cpp", "android_media_JetPlayer.cpp", @@ -64,6 +65,7 @@ cc_library_shared { "libbinder", "libmedia", "libmedia_codeclist", + "libmedia_codeclist_capabilities", "libmedia_jni_utils", "libmedia_omx", "libmediametrics", diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp new file mode 100644 index 000000000000..d5e574764181 --- /dev/null +++ b/media/jni/android_media_CodecCapabilities.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2024, 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaCodec-JNI" + +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" + +#include <media/AudioCapabilities.h> +#include <media/stagefright/foundation/ADebug.h> +#include <nativehelper/JNIHelp.h> + +namespace android { + +struct fields_t { + jfieldID audioCapsContext; +}; +static fields_t fields; + +// Getters + +static AudioCapabilities* getAudioCapabilities(JNIEnv *env, jobject thiz) { + AudioCapabilities* const p = (AudioCapabilities*)env->GetLongField( + thiz, fields.audioCapsContext); + return p; +} + +} // namespace android + +// ---------------------------------------------------------------------------- + +using namespace android; + +// AudioCapabilities + +static void android_media_AudioCapabilities_native_init(JNIEnv *env, jobject /* thiz */) { + jclass audioCapsImplClazz + = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl"); + if (audioCapsImplClazz == NULL) { + return; + } + + fields.audioCapsContext = env->GetFieldID(audioCapsImplClazz, "mNativeContext", "J"); + if (fields.audioCapsContext == NULL) { + return; + } + + env->DeleteLocalRef(audioCapsImplClazz); +} + +static jint android_media_AudioCapabilities_getMaxInputChannelCount(JNIEnv *env, jobject thiz) { + AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz); + if (audioCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + int32_t maxInputChannelCount = audioCaps->getMaxInputChannelCount(); + return maxInputChannelCount; +} + +static jint android_media_AudioCapabilities_getMinInputChannelCount(JNIEnv *env, jobject thiz) { + AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz); + if (audioCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + int32_t minInputChannelCount = audioCaps->getMinInputChannelCount(); + return minInputChannelCount; +} + +static jboolean android_media_AudioCapabilities_isSampleRateSupported(JNIEnv *env, jobject thiz, + int sampleRate) { + AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz); + if (audioCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + bool res = audioCaps->isSampleRateSupported(sampleRate); + return res; +} + +// ---------------------------------------------------------------------------- + +static const JNINativeMethod gAudioCapsMethods[] = { + {"native_init", "()V", (void *)android_media_AudioCapabilities_native_init}, + {"native_getMaxInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMaxInputChannelCount}, + {"native_getMinInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMinInputChannelCount}, + {"native_isSampleRateSupported", "(I)Z", (void *)android_media_AudioCapabilities_isSampleRateSupported} +}; + +int register_android_media_CodecCapabilities(JNIEnv *env) { + int result = AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl", + gAudioCapsMethods, NELEM(gAudioCapsMethods)); + if (result != JNI_OK) { + return result; + } + + return result; +}
\ No newline at end of file diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index d05ee551c172..fcba150f0db5 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1465,6 +1465,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) extern int register_android_media_ImageReader(JNIEnv *env); extern int register_android_media_ImageWriter(JNIEnv *env); extern int register_android_media_JetPlayer(JNIEnv *env); +extern int register_android_media_CodecCapabilities(JNIEnv *env); extern int register_android_media_Crypto(JNIEnv *env); extern int register_android_media_Drm(JNIEnv *env); extern int register_android_media_Descrambler(JNIEnv *env); @@ -1579,6 +1580,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } + if (register_android_media_CodecCapabilities(env) < 0) { + ALOGE("ERROR: CodecCapabilities native registration failed"); + goto bail; + } + if (register_android_media_Crypto(env) < 0) { ALOGE("ERROR: MediaCodec native registration failed"); goto bail; |