diff --git a/core/api/current.txt b/core/api/current.txt index 28d89693a94cf..d0a2f0bb8fb22 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20147,6 +20147,14 @@ package android.media { method public android.media.AudioAttributes.Builder setUsage(int); } + public class AudioDescriptor { + method @NonNull public byte[] getDescriptor(); + method public int getEncapsulationType(); + method public int getStandard(); + field public static final int STANDARD_EDID = 1; // 0x1 + field public static final int STANDARD_NONE = 0; // 0x0 + } + public abstract class AudioDeviceCallback { ctor public AudioDeviceCallback(); method public void onAudioDevicesAdded(android.media.AudioDeviceInfo[]); @@ -20155,6 +20163,7 @@ package android.media { public final class AudioDeviceInfo { method @NonNull public String getAddress(); + method @NonNull public java.util.List getAudioDescriptors(); method @NonNull public java.util.List getAudioProfiles(); method @NonNull public int[] getChannelCounts(); method @NonNull public int[] getChannelIndexMasks(); @@ -20617,8 +20626,11 @@ package android.media { public class AudioProfile { method @NonNull public int[] getChannelIndexMasks(); method @NonNull public int[] getChannelMasks(); + method public int getEncapsulationType(); method public int getFormat(); method @NonNull public int[] getSampleRates(); + field public static final int AUDIO_ENCAPSULATION_TYPE_IEC61937 = 1; // 0x1 + field public static final int AUDIO_ENCAPSULATION_TYPE_NONE = 0; // 0x0 } public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { diff --git a/core/jni/android_media_AudioDescriptor.h b/core/jni/android_media_AudioDescriptor.h new file mode 100644 index 0000000000000..680ac3f7519ea --- /dev/null +++ b/core/jni/android_media_AudioDescriptor.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef ANDROID_MEDIA_EXTRAAUDIODESCRIPTOR_H +#define ANDROID_MEDIA_EXTRAAUDIODESCRIPTOR_H + +#include +#include + +namespace android { + +// keep these values in sync with ExtraAudioDescriptor.java +#define STANDARD_NONE 0 +#define STANDARD_EDID 1 + +static inline status_t audioStandardFromNative(audio_standard_t nStandard, int* standard) { + status_t result = NO_ERROR; + switch (nStandard) { + case AUDIO_STANDARD_NONE: + *standard = STANDARD_NONE; + break; + case AUDIO_STANDARD_EDID: + *standard = STANDARD_EDID; + break; + default: + result = BAD_VALUE; + } + return result; +} + +} // namespace android + +#endif // ANDROID_MEDIA_EXTRAAUDIODESCRIPTOR_H \ No newline at end of file diff --git a/core/jni/android_media_AudioProfile.h b/core/jni/android_media_AudioProfile.h new file mode 100644 index 0000000000000..446bd6494f959 --- /dev/null +++ b/core/jni/android_media_AudioProfile.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef ANDROID_MEDIA_AUDIOPROFILE_H +#define ANDROID_MEDIA_AUDIOPROFILE_H + +#include +#include + +namespace android { + +// keep these values in sync with AudioProfile.java +#define ENCAPSULATION_TYPE_NONE 0 +#define ENCAPSULATION_TYPE_IEC61937 1 + +static inline status_t audioEncapsulationTypeFromNative( + audio_encapsulation_type_t nEncapsulationType, int* encapsulationType) { + status_t result = NO_ERROR; + switch (nEncapsulationType) { + case AUDIO_ENCAPSULATION_TYPE_NONE: + *encapsulationType = ENCAPSULATION_TYPE_NONE; + break; + case AUDIO_ENCAPSULATION_TYPE_IEC61937: + *encapsulationType = ENCAPSULATION_TYPE_IEC61937; + break; + default: + result = BAD_VALUE; + } + return result; +} + +} // namespace android + +#endif // ANDROID_MEDIA_AUDIOPROFILE_H \ No newline at end of file diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index f102edc79e7f4..6f9a3815a302f 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -34,10 +34,12 @@ #include #include #include "android_media_AudioAttributes.h" +#include "android_media_AudioDescriptor.h" #include "android_media_AudioDeviceAttributes.h" #include "android_media_AudioEffectDescriptor.h" #include "android_media_AudioErrors.h" #include "android_media_AudioFormat.h" +#include "android_media_AudioProfile.h" #include "android_media_MicrophoneInfo.h" // ---------------------------------------------------------------------------- @@ -176,6 +178,9 @@ static struct { static struct { jmethodID add; } gListMethods; +static jclass gAudioDescriptorClass; +static jmethodID gAudiODescriptorCstor; + // // JNI Initialization for OpenSLES routing // @@ -1217,6 +1222,7 @@ static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort, jobject jAudioPortConfig = NULL; jstring jDeviceName = NULL; jobject jAudioProfiles = NULL; + jobject jAudioDescriptors = nullptr; bool useInMask; ALOGV("convertAudioPortFromNative id %d role %d type %d name %s", @@ -1293,13 +1299,21 @@ static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort, } } + int encapsulationType; + if (audioEncapsulationTypeFromNative(nAudioPort->audio_profiles[i].encapsulation_type, + &encapsulationType) != NO_ERROR) { + ALOGW("Unknown encapsualtion type for JAVA API: %u", + nAudioPort->audio_profiles[i].encapsulation_type); + continue; + } + ScopedLocalRef jAudioProfile(env, env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormatFromNative( nAudioPort->audio_profiles[i].format), jSamplingRates.get(), jChannelMasks.get(), - jChannelIndexMasks.get())); + jChannelIndexMasks.get(), encapsulationType)); if (jAudioProfile == nullptr) { jStatus = (jint)AUDIO_JAVA_ERROR; goto exit; @@ -1307,6 +1321,42 @@ static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort, env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile.get()); } + jAudioDescriptors = env->NewObject(gArrayListClass, gArrayListMethods.cstor); + if (jAudioDescriptors == nullptr) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + for (size_t i = 0; i < nAudioPort->num_extra_audio_descriptors; ++i) { + const auto &extraAudioDescriptor = nAudioPort->extra_audio_descriptors[i]; + ScopedLocalRef jAudioDescriptor(env); + if (extraAudioDescriptor.descriptor_length == 0) { + continue; + } + int standard; + if (audioStandardFromNative(extraAudioDescriptor.standard, &standard) != NO_ERROR) { + ALOGW("Unknown standard for JAVA API: %u", extraAudioDescriptor.standard); + continue; + } + int encapsulationType; + if (audioEncapsulationTypeFromNative(extraAudioDescriptor.encapsulation_type, + &encapsulationType) != NO_ERROR) { + ALOGW("Unknown encapsualtion type for JAVA API: %u", + extraAudioDescriptor.encapsulation_type); + continue; + } + ScopedLocalRef jDescriptor(env, + env->NewByteArray( + extraAudioDescriptor.descriptor_length)); + env->SetByteArrayRegion(jDescriptor.get(), 0, extraAudioDescriptor.descriptor_length, + reinterpret_cast(extraAudioDescriptor.descriptor)); + jAudioDescriptor = + ScopedLocalRef(env, + env->NewObject(gAudioDescriptorClass, gAudiODescriptorCstor, + standard, encapsulationType, + jDescriptor.get())); + env->CallBooleanMethod(jAudioDescriptors, gArrayListMethods.add, jAudioDescriptor.get()); + } + // gains jGains = env->NewObjectArray(nAudioPort->num_gains, gAudioGainClass, NULL); @@ -1365,7 +1415,7 @@ static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort, *jAudioPort = env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor, jHandle, jDeviceName, jAudioProfiles, jGains, nAudioPort->ext.device.type, jAddress, - jEncapsulationModes, jEncapsulationMetadataTypes); + jEncapsulationModes, jEncapsulationMetadataTypes, jAudioDescriptors); env->DeleteLocalRef(jAddress); } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) { ALOGV("convertAudioPortFromNative is a mix"); @@ -1414,6 +1464,9 @@ exit: if (jAudioPortConfig != NULL) { env->DeleteLocalRef(jAudioPortConfig); } + if (jAudioDescriptors != nullptr) { + env->DeleteLocalRef(jAudioDescriptors); + } return jStatus; } @@ -2790,7 +2843,8 @@ int register_android_media_AudioSystem(JNIEnv *env) gAudioDevicePortCstor = GetMethodIDOrDie(env, audioDevicePortClass, "", "(Landroid/media/AudioHandle;Ljava/lang/String;Ljava/util/List;" - "[Landroid/media/AudioGain;ILjava/lang/String;[I[I)V"); + "[Landroid/media/AudioGain;ILjava/lang/String;[I[I" + "Ljava/util/List;)V"); // When access AudioPort as AudioDevicePort gAudioPortFields.mType = GetFieldIDOrDie(env, audioDevicePortClass, "mType", "I"); @@ -2909,7 +2963,11 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioProfileClass = FindClassOrDie(env, "android/media/AudioProfile"); gAudioProfileClass = MakeGlobalRefOrDie(env, audioProfileClass); - gAudioProfileCstor = GetMethodIDOrDie(env, audioProfileClass, "", "(I[I[I[I)V"); + gAudioProfileCstor = GetMethodIDOrDie(env, audioProfileClass, "", "(I[I[I[II)V"); + + jclass audioDescriptorClass = FindClassOrDie(env, "android/media/AudioDescriptor"); + gAudioDescriptorClass = MakeGlobalRefOrDie(env, audioDescriptorClass); + gAudiODescriptorCstor = GetMethodIDOrDie(env, audioDescriptorClass, "", "(II[B)V"); AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback); diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java new file mode 100644 index 0000000000000..11371b11e9059 --- /dev/null +++ b/media/java/android/media/AudioDescriptor.java @@ -0,0 +1,90 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The AudioDescriptor contains the information to describe the audio playback/capture + * capabilities. The capabilities are described by a byte array, which is defined by a + * particular standard. This is used when the format is unrecognized to the platform. + */ +public class AudioDescriptor { + /** + * The audio standard is not specified. + */ + public static final int STANDARD_NONE = 0; + /** + * The Extended Display Identification Data (EDID) standard for a short audio descriptor. + */ + public static final int STANDARD_EDID = 1; + + /** @hide */ + @IntDef({ + STANDARD_NONE, + STANDARD_EDID, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AudioDescriptorStandard {} + + private final int mStandard; + private final byte[] mDescriptor; + private final int mEncapsulationType; + + AudioDescriptor(int standard, int encapsulationType, @NonNull byte[] descriptor) { + mStandard = standard; + mEncapsulationType = encapsulationType; + mDescriptor = descriptor; + } + + /** + * @return the standard that defines audio playback/capture capabilities. + */ + public @AudioDescriptorStandard int getStandard() { + return mStandard; + } + + /** + * @return a byte array that describes audio playback/capture capabilities as encoded by the + * standard for this AudioDescriptor. + */ + public @NonNull byte[] getDescriptor() { + return mDescriptor; + } + + /** + * The encapsulation type indicates what encapsulation type is required when the framework is + * using this extra audio descriptor for playing to a device exposing this audio profile. + * When encapsulation is required, only playback with {@link android.media.AudioTrack} API is + * supported. But playback with {@link android.media.MediaPlayer} is not. + * When an encapsulation type is required, the {@link AudioFormat} encoding selected when + * creating the {@link AudioTrack} must match the encapsulation type, e.g + * AudioFormat#ENCODING_IEC61937 for AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937. + * + * @return an integer representing the encapsulation type + * + * @see AudioProfile#AUDIO_ENCAPSULATION_TYPE_NONE + * @see AudioProfile#AUDIO_ENCAPSULATION_TYPE_IEC61937 + */ + public @AudioProfile.EncapsulationType int getEncapsulationType() { + return mEncapsulationType; + } +} diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 383c93d7716dc..9300f13e03cb9 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -518,6 +518,13 @@ public final class AudioDeviceInfo { return mPort.profiles(); } + /** + * @return A list of {@link AudioDescriptor} supported by the audio devices. + */ + public @NonNull List getAudioDescriptors() { + return mPort.audioDescriptors(); + } + /** * Returns an array of supported encapsulation modes for the device. * diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index 9c42bf7e56055..ebe08822a0c91 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -60,10 +60,11 @@ public class AudioDevicePort extends AudioPort { AudioDevicePort(AudioHandle handle, String deviceName, List profiles, AudioGain[] gains, int type, String address, int[] encapsulationModes, - @AudioTrack.EncapsulationMetadataType int[] encapsulationMetadataTypes) { + @AudioTrack.EncapsulationMetadataType int[] encapsulationMetadataTypes, + List descriptors) { super(handle, AudioManager.isInputDevice(type) ? AudioPort.ROLE_SOURCE : AudioPort.ROLE_SINK, - deviceName, profiles, gains); + deviceName, profiles, gains, descriptors); mType = type; mAddress = address; mEncapsulationModes = encapsulationModes; diff --git a/media/java/android/media/AudioMixPort.java b/media/java/android/media/AudioMixPort.java index 7f2249d05ec25..b24268ab03577 100644 --- a/media/java/android/media/AudioMixPort.java +++ b/media/java/android/media/AudioMixPort.java @@ -46,7 +46,7 @@ public class AudioMixPort extends AudioPort { AudioMixPort(AudioHandle handle, int ioHandle, int role, String deviceName, List profiles, AudioGain[] gains) { - super(handle, role, deviceName, profiles, gains); + super(handle, role, deviceName, profiles, gains, null); mIoHandle = ioHandle; } diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java index 03022501fbcae..8a2d096c31d39 100644 --- a/media/java/android/media/AudioPort.java +++ b/media/java/android/media/AudioPort.java @@ -86,6 +86,7 @@ public class AudioPort { private final int[] mChannelIndexMasks; private final int[] mFormats; private final List mProfiles; + private final List mDescriptors; @UnsupportedAppUsage private final AudioGain[] mGains; @UnsupportedAppUsage @@ -107,17 +108,21 @@ public class AudioPort { if (mFormats != null) { for (int format : mFormats) { mProfiles.add(new AudioProfile( - format, samplingRates, channelMasks, channelIndexMasks)); + format, samplingRates, channelMasks, channelIndexMasks, + AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE)); } } + mDescriptors = new ArrayList<>(); } AudioPort(AudioHandle handle, int role, String name, - List profiles, AudioGain[] gains) { + List profiles, AudioGain[] gains, + List descriptors) { mHandle = handle; mRole = role; mName = name; mProfiles = profiles; + mDescriptors = descriptors; mGains = gains; Set formats = new HashSet<>(); Set samplingRates = new HashSet<>(); @@ -209,6 +214,13 @@ public class AudioPort { return mProfiles; } + /** + * Get the list of audio descriptor + */ + public List audioDescriptors() { + return mDescriptors; + } + /** * Get the list of gain descriptors * Empty array if this port does not have gain control diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java index ac96e6f80a5ab..ae8d0a5ad4ab1 100644 --- a/media/java/android/media/AudioProfile.java +++ b/media/java/android/media/AudioProfile.java @@ -16,27 +16,55 @@ package android.media; +import android.annotation.IntDef; import android.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.stream.Collectors; /** * An AudioProfile is specific to an audio format and lists supported sampling rates and * channel masks for that format. An {@link AudioDeviceInfo} has a list of supported AudioProfiles. + * There can be multiple profiles whose encoding format is the same. This usually happens when + * an encoding format is only supported when it is encapsulated by some particular encapsulation + * types. If there are multiple encapsulation types that can carry this encoding format, they will + * be reported in different audio profiles. The application can choose any of the encapsulation + * types. */ public class AudioProfile { + /** + * No encapsulation type is specified. + */ + public static final int AUDIO_ENCAPSULATION_TYPE_NONE = 0; + /** + * Encapsulation format is defined in standard IEC 61937. + */ + public static final int AUDIO_ENCAPSULATION_TYPE_IEC61937 = 1; + + /** @hide */ + @IntDef({ + AUDIO_ENCAPSULATION_TYPE_NONE, + AUDIO_ENCAPSULATION_TYPE_IEC61937, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EncapsulationType {} + private final int mFormat; private final int[] mSamplingRates; private final int[] mChannelMasks; private final int[] mChannelIndexMasks; + private final int mEncapsulationType; AudioProfile(int format, @NonNull int[] samplingRates, @NonNull int[] channelMasks, - @NonNull int[] channelIndexMasks) { + @NonNull int[] channelIndexMasks, + int encapsulationType) { mFormat = format; mSamplingRates = samplingRates; mChannelMasks = channelMasks; mChannelIndexMasks = channelIndexMasks; + mEncapsulationType = encapsulationType; } /** @@ -67,6 +95,24 @@ public class AudioProfile { return mSamplingRates; } + /** + * The encapsulation type indicates what encapsulation type is required when the framework is + * using this format when playing to a device exposing this audio profile. + * When encapsulation is required, only playback with {@link android.media.AudioTrack} API is + * supported. But playback with {@link android.media.MediaPlayer} is not. + * When an encapsulation type is required, the {@link AudioFormat} encoding selected when + * creating the {@link AudioTrack} must match the encapsulation type, e.g + * AudioFormat.ENCODING_IEC61937 for AUDIO_ENCAPSULATION_TYPE_IEC61937. + * + * @return an integer representing the encapsulation type + * + * @see #AUDIO_ENCAPSULATION_TYPE_NONE + * @see #AUDIO_ENCAPSULATION_TYPE_IEC61937 + */ + public @EncapsulationType int getEncapsulationType() { + return mEncapsulationType; + } + @Override public String toString() { StringBuilder sb = new StringBuilder("{");