diff --git a/api/current.txt b/api/current.txt index de7dbdc4b72b9..eb8ef8fa01906 100644 --- a/api/current.txt +++ b/api/current.txt @@ -24375,12 +24375,14 @@ package android.media { method protected void finalize(); method public void flush(); method @NonNull public android.media.AudioAttributes getAudioAttributes(); + method public float getAudioDescriptionMixLeveldB(); method public int getAudioFormat(); method public int getAudioSessionId(); method @IntRange(from=0) public int getBufferCapacityInFrames(); method @IntRange(from=0) public int getBufferSizeInFrames(); method public int getChannelConfiguration(); method public int getChannelCount(); + method public int getDualMonoMode(); method @NonNull public android.media.AudioFormat getFormat(); method public static float getMaxVolume(); method public android.os.PersistableBundle getMetrics(); @@ -24414,8 +24416,10 @@ package android.media { method public void removeOnCodecFormatChangedListener(@NonNull android.media.AudioTrack.OnCodecFormatChangedListener); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener); + method public boolean setAudioDescriptionMixLeveldB(@FloatRange(to=48.0f, toInclusive=true) float); method public int setAuxEffectSendLevel(@FloatRange(from=0.0) float); method public int setBufferSizeInFrames(@IntRange(from=0) int); + method public boolean setDualMonoMode(int); method public int setLoopPoints(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int); method public int setNotificationMarkerPosition(int); method public void setOffloadDelayPadding(@IntRange(from=0) int, @IntRange(from=0) int); @@ -24440,6 +24444,10 @@ package android.media { method public int write(@NonNull float[], int, int, int); method public int write(@NonNull java.nio.ByteBuffer, int, int); method public int write(@NonNull java.nio.ByteBuffer, int, int, long); + field public static final int DUAL_MONO_MODE_LL = 2; // 0x2 + field public static final int DUAL_MONO_MODE_LR = 1; // 0x1 + field public static final int DUAL_MONO_MODE_OFF = 0; // 0x0 + field public static final int DUAL_MONO_MODE_RR = 3; // 0x3 field public static final int ENCAPSULATION_MODE_ELEMENTARY_STREAM = 1; // 0x1 field public static final int ENCAPSULATION_MODE_HANDLE = 2; // 0x2 field public static final int ENCAPSULATION_MODE_NONE = 0; // 0x0 diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 041019ec48418..a888b43a4854d 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1355,6 +1355,71 @@ static void android_media_AudioTrack_set_delay_padding(JNIEnv *env, jobject thi lpTrack->setParameters(param.toString()); } +static jint android_media_AudioTrack_setAudioDescriptionMixLeveldB(JNIEnv *env, jobject thiz, + jfloat level) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level. + return (jint)AUDIO_JAVA_ERROR; +} + +static jint android_media_AudioTrack_getAudioDescriptionMixLeveldB(JNIEnv *env, jobject thiz, + jfloatArray level) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + ALOGE("%s: AudioTrack not initialized", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + jfloat *nativeLevel = (jfloat *)env->GetPrimitiveArrayCritical(level, NULL); + if (nativeLevel == nullptr) { + ALOGE("%s: Cannot retrieve level pointer", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level. + // By contract we can return -infinity if unsupported. + *nativeLevel = -std::numeric_limits::infinity(); + env->ReleasePrimitiveArrayCritical(level, nativeLevel, 0 /* mode */); + nativeLevel = nullptr; + return (jint)AUDIO_JAVA_SUCCESS; +} + +static jint android_media_AudioTrack_setDualMonoMode(JNIEnv *env, jobject thiz, jint dualMonoMode) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level. + return (jint)AUDIO_JAVA_ERROR; +} + +static jint android_media_AudioTrack_getDualMonoMode(JNIEnv *env, jobject thiz, + jintArray dualMonoMode) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + ALOGE("%s: AudioTrack not initialized", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + jfloat *nativeDualMonoMode = (jfloat *)env->GetPrimitiveArrayCritical(dualMonoMode, NULL); + if (nativeDualMonoMode == nullptr) { + ALOGE("%s: Cannot retrieve dualMonoMode pointer", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to select dual mono mode. + // By contract we can return DUAL_MONO_MODE_OFF if unsupported. + *nativeDualMonoMode = 0; // DUAL_MONO_MODE_OFF for now. + env->ReleasePrimitiveArrayCritical(dualMonoMode, nativeDualMonoMode, 0 /* mode */); + nativeDualMonoMode = nullptr; + return (jint)AUDIO_JAVA_SUCCESS; +} + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -1425,6 +1490,12 @@ static const JNINativeMethod gMethods[] = { {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation}, {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id}, {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding}, + {"native_set_audio_description_mix_level_db", "(F)I", + (void *)android_media_AudioTrack_setAudioDescriptionMixLeveldB}, + {"native_get_audio_description_mix_level_db", "([F)I", + (void *)android_media_AudioTrack_getAudioDescriptionMixLeveldB}, + {"native_set_dual_mono_mode", "(I)I", (void *)android_media_AudioTrack_setDualMonoMode}, + {"native_get_dual_mono_mode", "([I)I", (void *)android_media_AudioTrack_getDualMonoMode}, }; // field names found in android/media/AudioTrack.java diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index f566f646f1e0b..1f6159104d589 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -256,6 +256,71 @@ public class AudioTrack extends PlayerBase */ public static final int ENCAPSULATION_MODE_HANDLE = 2; + /* Dual Mono handling is used when a stereo audio stream + * contains separate audio content on the left and right channels. + * Such information about the content of the stream may be found, for example, in + * ITU T-REC-J.94-201610 A.6.2.3 Component descriptor. + */ + /** @hide */ + @IntDef({ + DUAL_MONO_MODE_OFF, + DUAL_MONO_MODE_LR, + DUAL_MONO_MODE_LL, + DUAL_MONO_MODE_RR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DualMonoMode {} + // Important: The DUAL_MONO_MODE values must be kept in sync with native header files. + /** + * This mode disables any Dual Mono presentation effect. + * + */ + public static final int DUAL_MONO_MODE_OFF = 0; + + /** + * This mode indicates that a stereo stream should be presented + * with the left and right audio channels blended together + * and delivered to both channels. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that the left-right stereo symmetric + * channels are pairwise blended; + * the other channels such as center are left alone. + * + * The Dual Mono effect occurs before volume scaling. + */ + public static final int DUAL_MONO_MODE_LR = 1; + + /** + * This mode indicates that a stereo stream should be presented + * with the left audio channel replicated into the right audio channel. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that all channels with left-right + * stereo symmetry will have the left channel position replicated + * into the right channel position. + * The center channels (with no left/right symmetry) or unbalanced + * channels are left alone. + * + * The Dual Mono effect occurs before volume scaling. + */ + public static final int DUAL_MONO_MODE_LL = 2; + + /** + * This mode indicates that a stereo stream should be presented + * with the right audio channel replicated into the left audio channel. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that all channels with left-right + * stereo symmetry will have the right channel position replicated + * into the left channel position. + * The center channels (with no left/right symmetry) or unbalanced + * channels are left alone. + * + * The Dual Mono effect occurs before volume scaling. + */ + public static final int DUAL_MONO_MODE_RR = 3; + /** @hide */ @IntDef({ WRITE_BLOCKING, @@ -1355,6 +1420,140 @@ public class AudioTrack extends PlayerBase attributes.getContentType(), attributes.getUsage(), attributes.getFlags()); } + /* + * The MAX_LEVEL should be exactly representable by an IEEE 754-2008 base32 float. + * This means fractions must be divisible by a power of 2. For example, + * 10.25f is OK as 0.25 is 1/4, but 10.1f is NOT OK as 1/10 is not expressable by + * a finite binary fraction. + * + * 48.f is the nominal max for API level {@link android os.Build.VERSION_CODES#R}. + * We use this to suggest a baseline range for implementation. + * + * The API contract specification allows increasing this value in a future + * API release, but not decreasing this value. + */ + private static final float MAX_AUDIO_DESCRIPTION_MIX_LEVEL = 48.f; + + private static boolean isValidAudioDescriptionMixLevel(float level) { + return !(Float.isNaN(level) || level > MAX_AUDIO_DESCRIPTION_MIX_LEVEL); + } + + /** + * Sets the Audio Description mix level in dB. + * + * For AudioTracks incorporating a secondary Audio Description stream + * (where such contents may be sent through an Encapsulation Mode + * {@link #ENCAPSULATION_MODE_ELEMENTARY_STREAM} or {@link #ENCAPSULATION_MODE_HANDLE} + * or internally by a HW channel), + * the level of mixing of the Audio Description to the Main Audio stream + * is controlled by this method. + * + * Such mixing occurs prior to overall volume scaling. + * + * @param level a floating point value between + * {@code Float.NEGATIVE_INFINITY} to {@code +48.f}, + * where {@code Float.NEGATIVE_INFINITY} means the Audio Description is not mixed + * and a level of {@code 0.f} means the Audio Description is mixed without scaling. + * @return true on success, false on failure. + */ + public boolean setAudioDescriptionMixLeveldB( + @FloatRange(to = 48.f, toInclusive = true) float level) { + if (!isValidAudioDescriptionMixLevel(level)) { + throw new IllegalArgumentException("level is out of range" + level); + } + return native_set_audio_description_mix_level_db(level) == SUCCESS; + } + + /** + * Returns the Audio Description mix level in dB. + * + * If Audio Description mixing is unavailable from the hardware device, + * a value of {@code Float.NEGATIVE_INFINITY} is returned. + * + * @return the current Audio Description Mix Level in dB. + * A value of {@code Float.NEGATIVE_INFINITY} means + * that the audio description is not mixed or + * the hardware is not available. + * This should reflect the true internal device mix level; + * hence the application might receive any floating value + * except {@code Float.NaN}. + */ + public float getAudioDescriptionMixLeveldB() { + float[] level = { Float.NEGATIVE_INFINITY }; + try { + final int status = native_get_audio_description_mix_level_db(level); + if (status != SUCCESS || Float.isNaN(level[0])) { + return Float.NEGATIVE_INFINITY; + } + } catch (Exception e) { + return Float.NEGATIVE_INFINITY; + } + return level[0]; + } + + private static boolean isValidDualMonoMode(@DualMonoMode int dualMonoMode) { + switch (dualMonoMode) { + case DUAL_MONO_MODE_OFF: + case DUAL_MONO_MODE_LR: + case DUAL_MONO_MODE_LL: + case DUAL_MONO_MODE_RR: + return true; + default: + return false; + } + } + + /** + * Sets the Dual Mono mode presentation on the output device. + * + * The Dual Mono mode is generally applied to stereo audio streams + * where the left and right channels come from separate sources. + * + * For compressed audio, where the decoding is done in hardware, + * Dual Mono presentation needs to be performed + * by the hardware output device + * as the PCM audio is not available to the framework. + * + * @param dualMonoMode one of {@link #DUAL_MONO_MODE_OFF}, + * {@link #DUAL_MONO_MODE_LR}, + * {@link #DUAL_MONO_MODE_LL}, + * {@link #DUAL_MONO_MODE_RR}. + * + * @return true on success, false on failure if the output device + * does not support Dual Mono mode. + */ + public boolean setDualMonoMode(@DualMonoMode int dualMonoMode) { + if (!isValidDualMonoMode(dualMonoMode)) { + throw new IllegalArgumentException( + "Invalid Dual Mono mode " + dualMonoMode); + } + return native_set_dual_mono_mode(dualMonoMode) == SUCCESS; + } + + /** + * Returns the Dual Mono mode presentation setting. + * + * If no Dual Mono presentation is available for the output device, + * then {@link #DUAL_MONO_MODE_OFF} is returned. + * + * @return one of {@link #DUAL_MONO_MODE_OFF}, + * {@link #DUAL_MONO_MODE_LR}, + * {@link #DUAL_MONO_MODE_LL}, + * {@link #DUAL_MONO_MODE_RR}. + */ + public @DualMonoMode int getDualMonoMode() { + int[] dualMonoMode = { DUAL_MONO_MODE_OFF }; + try { + final int status = native_get_dual_mono_mode(dualMonoMode); + if (status != SUCCESS || !isValidDualMonoMode(dualMonoMode[0])) { + return DUAL_MONO_MODE_OFF; + } + } catch (Exception e) { + return DUAL_MONO_MODE_OFF; + } + return dualMonoMode[0]; + } + // mask of all the positional channels supported, however the allowed combinations // are further restricted by the matching left/right rule and // AudioSystem.OUT_CHANNEL_COUNT_MAX @@ -3947,6 +4146,11 @@ public class AudioTrack extends PlayerBase private native void native_set_delay_padding(int delayInFrames, int paddingInFrames); + private native int native_set_audio_description_mix_level_db(float level); + private native int native_get_audio_description_mix_level_db(float[] level); + private native int native_set_dual_mono_mode(int dualMonoMode); + private native int native_get_dual_mono_mode(int[] dualMonoMode); + //--------------------------------------------------------- // Utility methods //------------------