Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b279f15f authored by Songyue Han's avatar Songyue Han
Browse files

Java and JNI support for native AudioCapabilities.

Bug: b/306023029
Test: MediaCodecCapabilitiesTest
Change-Id: Ie96de1704c118567bea713891eaff48f873e7b6f
parent 1adb0dbc
Loading
Loading
Loading
Loading
+534 −364
Original line number Diff line number Diff line
@@ -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,6 +1411,28 @@ public final class MediaCodecInfo {
     */
    public static final class AudioCapabilities {
        private static final String TAG = "AudioCapabilities";

        /* package private */ interface AudioCapsIntf {
            public Range<Integer> getBitrateRange();

            public int[] getSupportedSampleRates();

            public Range<Integer>[] getSupportedSampleRateRanges();

            public int getMaxInputChannelCount();

            public int getMinInputChannelCount();

            public Range<Integer>[] getInputChannelCountRanges();

            public boolean isSampleRateSupported(int sampleRate);

            public void getDefaultFormat(MediaFormat format);

            public boolean supportsFormat(MediaFormat format);
        }

        /* package private */ static final class AudioCapsLegacyImpl implements AudioCapsIntf {
            private CodecCapabilities mParent;
            private Range<Integer> mBitrateRange;

@@ -1419,47 +1442,19 @@ public final class MediaCodecInfo {

            private static final int MAX_INPUT_CHANNEL_COUNT = 30;

        /**
         * Returns the range of supported bitrates in bits/second.
         */
            public Range<Integer> getBitrateRange() {
                return mBitrateRange;
            }

        /**
         * 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;
                return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length)
                        : null;
            }

        /**
         * 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);
            }

        /**
         * 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--) {
@@ -1471,14 +1466,6 @@ public final class MediaCodecInfo {
                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--) {
@@ -1490,27 +1477,20 @@ public final class MediaCodecInfo {
                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);
            }

            /* no public constructor */
        private AudioCapabilities() { }
            private AudioCapsLegacyImpl() { }

        /** @hide */
        public static AudioCapabilities create(
            public static AudioCapsLegacyImpl create(
                    MediaFormat info, CodecCapabilities parent) {
            AudioCapabilities caps = new AudioCapabilities();
                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
                    Log.d(TAG, "Legacy implementation is called while native flag is on.");
                }

                AudioCapsLegacyImpl caps = new AudioCapsLegacyImpl();
                caps.init(info, parent);
                return caps;
            }
@@ -1553,9 +1533,6 @@ public final class MediaCodecInfo {
                return true;
            }

        /**
         * Query whether the sample rate is supported by the codec.
         */
            public boolean isSampleRateSupported(int sampleRate) {
                return supports(sampleRate, null);
            }
@@ -1676,14 +1653,16 @@ public final class MediaCodecInfo {
                                break;
                            case CodecProfileLevel.DTS_HDProfileHRA:
                            case CodecProfileLevel.DTS_HDProfileMA:
                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
                                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 };
                                sampleRates
                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
                                bitRates = Range.create(96000, 24500000);
                        }
                    }
@@ -1697,7 +1676,8 @@ public final class MediaCodecInfo {
                                maxChannels = 10;
                                break;
                            case CodecProfileLevel.DTS_UHDProfileP1:
                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
                                sampleRates
                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
                                bitRates = Range.create(96000, 24500000);
                                maxChannels = 32;
                                break;
@@ -1705,7 +1685,8 @@ public final class MediaCodecInfo {
                                Log.w(TAG, "Unrecognized profile "
                                        + profileLevel.profile + " for " + mime);
                                mParent.mError |= ERROR_UNRECOGNIZED;
                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
                                sampleRates
                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
                                bitRates = Range.create(96000, 24500000);
                                maxChannels = 32;
                        }
@@ -1813,8 +1794,8 @@ public final class MediaCodecInfo {
            /* 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.
                    // 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,
@@ -1841,6 +1822,189 @@ public final class MediaCodecInfo {
            }
        }

        /* package private */ static final class AudioCapsNativeImpl implements AudioCapsIntf {
            private long mNativeContext; // accessed by native methods

            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;
            }

            /* no public constructor */
            private AudioCapsNativeImpl() { }

            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 Range<Integer>[] getInputChannelCountRanges() {
                return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
            }

            public int getMaxInputChannelCount() {
                return native_getMaxInputChannelCount();
            }

            public int getMinInputChannelCount() {
                return native_getMinInputChannelCount();
            }

            public boolean isSampleRateSupported(int sampleRate) {
                return native_isSampleRateSupported(sampleRate);
            }

            // 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();
            }
        }

        private AudioCapsIntf mImpl;

        /** @hide */
        public static AudioCapabilities create(
                MediaFormat info, CodecCapabilities parent) {
            AudioCapsLegacyImpl impl = AudioCapsLegacyImpl.create(info, parent);
            AudioCapabilities caps = new AudioCapabilities(impl);
            return caps;
        }

        /* package private */ AudioCapabilities(AudioCapsIntf impl) {
            mImpl = impl;
        }

        /* no public constructor */
        private AudioCapabilities() { }

        /**
         * 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);
        }
    }

    /** @hide */
    @IntDef(prefix = {"SECURITY_MODEL_"}, value = {
        SECURITY_MODEL_SANDBOXED,
@@ -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));
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -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",
+117 −0
Original line number Diff line number Diff line
/*
 * 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
+6 −0
Original line number Diff line number Diff line
@@ -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;