diff --git a/core/api/current.txt b/core/api/current.txt index b40c8fdebb741..4fad67b3154e8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -19859,6 +19859,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 3e2b25853a6a2..c5e435b7ea8f6 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 c4cef4c778357..47dd40c9f42d4 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -7674,6 +7674,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 306479a5b8194..179ec8fb19791 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -2115,6 +2115,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 /**