From eb9bf64d069ba9844191e17ff7031cfd336edc2e Mon Sep 17 00:00:00 2001 From: Dorin Drimus Date: Mon, 3 Jan 2022 12:05:37 +0100 Subject: Add getDirectProfilesForAttributes Allows any app to query AudioManager on the available direct AudioProfiles for the specified AudioAttributes. Only the active paths that will be actually used to output sound are returned. go/audio-route-t Bug: 190810951 Test: atest android.media.audio.cts.DirectAudioProfilesForAttributesTest Change-Id: Ia5ce587addadb52725084bd32a6244577626f44d --- core/api/current.txt | 1 + core/jni/android_media_AudioSystem.cpp | 108 ++++++++++++++++++++++++++++- media/java/android/media/AudioManager.java | 27 ++++++++ media/java/android/media/AudioSystem.java | 9 +++ 4 files changed, 144 insertions(+), 1 deletion(-) diff --git a/core/api/current.txt b/core/api/current.txt index 0ce315adc310..71763a41d091 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -21110,6 +21110,7 @@ package android.media { method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice(); method public android.media.AudioDeviceInfo[] getDevices(int); method public static int getDirectPlaybackSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes); + method @NonNull public java.util.List getDirectProfilesForAttributes(@NonNull android.media.AudioAttributes); method public int getEncodedSurroundMode(); method public java.util.List getMicrophones() throws java.io.IOException; method public int getMode(); diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 3e2b25853a6a..c5e435b7ea8f 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -1236,6 +1236,65 @@ static bool isAudioPortArrayCountOutOfBounds(const struct audio_port_v7 *nAudioP return false; } +static jint convertAudioProfileFromNative(JNIEnv *env, jobject *jAudioProfile, + const audio_profile *nAudioProfile, bool useInMask) { + size_t numPositionMasks = 0; + size_t numIndexMasks = 0; + + // count up how many masks are positional and indexed + for (size_t index = 0; index < nAudioProfile->num_channel_masks; index++) { + const audio_channel_mask_t mask = nAudioProfile->channel_masks[index]; + if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) { + numIndexMasks++; + } else { + numPositionMasks++; + } + } + + ScopedLocalRef jSamplingRates(env, + env->NewIntArray(nAudioProfile->num_sample_rates)); + ScopedLocalRef jChannelMasks(env, env->NewIntArray(numPositionMasks)); + ScopedLocalRef jChannelIndexMasks(env, env->NewIntArray(numIndexMasks)); + if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) { + return AUDIO_JAVA_ERROR; + } + + if (nAudioProfile->num_sample_rates) { + env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/, nAudioProfile->num_sample_rates, + (jint *)nAudioProfile->sample_rates); + } + + // put the masks in the output arrays + for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0; + maskIndex < nAudioProfile->num_channel_masks; maskIndex++) { + const audio_channel_mask_t mask = nAudioProfile->channel_masks[maskIndex]; + if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) { + jint jMask = audio_channel_mask_get_bits(mask); + env->SetIntArrayRegion(jChannelIndexMasks.get(), indexedMaskIndex++, 1, &jMask); + } else { + jint jMask = useInMask ? inChannelMaskFromNative(mask) : outChannelMaskFromNative(mask); + env->SetIntArrayRegion(jChannelMasks.get(), posMaskIndex++, 1, &jMask); + } + } + + int encapsulationType; + if (audioEncapsulationTypeFromNative(nAudioProfile->encapsulation_type, &encapsulationType) != + NO_ERROR) { + ALOGW("Unknown encapsulation type for JAVA API: %u", nAudioProfile->encapsulation_type); + } + + *jAudioProfile = + env->NewObject(gAudioProfileClass, gAudioProfileCstor, + audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(), + jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType); + + if (jAudioProfile == nullptr) { + return AUDIO_JAVA_ERROR; + } + + return AUDIO_JAVA_SUCCESS; +} + static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort, const struct audio_port_v7 *nAudioPort) { jint jStatus = (jint)AUDIO_JAVA_SUCCESS; @@ -2814,6 +2873,50 @@ static jint android_media_AudioSystem_getDirectPlaybackSupport(JNIEnv *env, jobj return convertAudioDirectModeFromNative(directMode); } +static jint android_media_AudioSystem_getDirectProfilesForAttributes(JNIEnv *env, jobject thiz, + jobject jAudioAttributes, + jobject jAudioProfilesList) { + ALOGV("getDirectProfilesForAttributes"); + + if (jAudioAttributes == nullptr) { + ALOGE("jAudioAttributes is NULL"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (jAudioProfilesList == nullptr) { + ALOGE("jAudioProfilesList is NULL"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jAudioProfilesList, gArrayListClass)) { + ALOGE("jAudioProfilesList not an ArrayList"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + std::vector audioProfiles; + status_t status = AudioSystem::getDirectProfilesForAttributes(paa.get(), &audioProfiles); + if (status != NO_ERROR) { + ALOGE("AudioSystem::getDirectProfilesForAttributes error %d", status); + jStatus = nativeToJavaStatus(status); + return jStatus; + } + + for (const auto &audioProfile : audioProfiles) { + jobject jAudioProfile; + jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &audioProfile, false); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile); + env->DeleteLocalRef(jAudioProfile); + } + return jStatus; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = @@ -2960,7 +3063,10 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_canBeSpatialized}, {"getDirectPlaybackSupport", "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I", - (void *)android_media_AudioSystem_getDirectPlaybackSupport}}; + (void *)android_media_AudioSystem_getDirectPlaybackSupport}, + {"getDirectProfilesForAttributes", + "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I", + (void *)android_media_AudioSystem_getDirectProfilesForAttributes}}; static const JNINativeMethod gEventHandlerMethods[] = { {"native_setup", diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 68e5d94f7559..268db72b8f57 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -7670,6 +7670,33 @@ public class AudioManager { } } + /** + * Returns a list of direct {@link AudioProfile} that are supported for the specified + * {@link AudioAttributes}. This can be empty in case of an error or if no direct playback + * is possible. + * + *

Direct playback means that the audio stream is not resampled or downmixed + * by the framework. Checking for direct support can help the app select the representation + * of audio content that most closely matches the capabilities of the device and peripherals + * (e.g. A/V receiver) connected to it. Note that the provided stream can still be re-encoded + * or mixed with other streams, if needed. + *

When using this information to inform your application which audio format to play, + * query again whenever audio output devices change (see {@link AudioDeviceCallback}). + * @param attributes a non-null {@link AudioAttributes} instance. + * @return a list of {@link AudioProfile} + */ + @NonNull + public List getDirectProfilesForAttributes(@NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes); + ArrayList audioProfilesList = new ArrayList<>(); + int status = AudioSystem.getDirectProfilesForAttributes(attributes, audioProfilesList); + if (status != SUCCESS) { + Log.w(TAG, "getDirectProfilesForAttributes failed."); + return new ArrayList<>(); + } + return audioProfilesList; + } + /** * @hide * Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type provided. diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 50bf1e5e0a26..b60c9f7f915a 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -2108,6 +2108,15 @@ public class AudioSystem AudioFormat format, AudioDeviceAttributes[] devices); + /** + * @hide + * @param attributes audio attributes describing the playback use case + * @param audioProfilesList the list of AudioProfiles that can be played as direct output + * @return {@link #SUCCESS} if the list of AudioProfiles was successfully created (can be empty) + */ + public static native int getDirectProfilesForAttributes(@NonNull AudioAttributes attributes, + @NonNull ArrayList audioProfilesList); + // Items shared with audio service /** -- cgit v1.2.3-59-g8ed1b