From 1c342099693c46e0f897e5f1c4de6451e0b9a35c Mon Sep 17 00:00:00 2001 From: Andy Hung Date: Tue, 16 Mar 2021 11:30:01 -0700 Subject: [PATCH] AudioTrack: get/setStartThresholdInFrames setStartThresholdInFrames is used to set the start threshold in frames for streaming AudioTrack playback. Normally this is the entire buffer capacity in frames but may be reduced for low latency playback, compressed formats, and direct tracks. See CTS test AudioTrackTest#testStartThresholdInFrames for example calling details and behavior. Test: atest AudioTrackTest#testStartThresholdInFrames Test: atest AudioTrackTest#testStartThresholdInFramesExceptions Bug: 183003720 Change-Id: I5064d04961e48b530c49071ff84c2e0d2065f41b --- core/api/current.txt | 2 + core/jni/android_media_AudioTrack.cpp | 40 ++++++++++++++++ media/java/android/media/AudioTrack.java | 61 ++++++++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/core/api/current.txt b/core/api/current.txt index 4aa38d64ecf98..4a7ce686ebabc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20677,6 +20677,7 @@ package android.media { method public android.media.AudioDeviceInfo getPreferredDevice(); method public android.media.AudioDeviceInfo getRoutedDevice(); method public int getSampleRate(); + method @IntRange(from=1) public int getStartThresholdInFrames(); method public int getState(); method public int getStreamType(); method public boolean getTimestamp(android.media.AudioTimestamp); @@ -20707,6 +20708,7 @@ package android.media { method public int setPositionNotificationPeriod(int); method public boolean setPreferredDevice(android.media.AudioDeviceInfo); method public int setPresentation(@NonNull android.media.AudioPresentation); + method @IntRange(from=1) public int setStartThresholdInFrames(@IntRange(from=1) int); method @Deprecated protected void setState(int); method @Deprecated public int setStereoVolume(float, float); method public int setVolume(float); diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 62767a676e8b5..de5df202ba4e1 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1450,6 +1450,42 @@ static void android_media_AudioTrack_setPlayerIId(JNIEnv *env, jobject thiz, jin track->setPlayerIId(playerIId); } +static jint android_media_AudioTrack_getStartThresholdInFrames(JNIEnv *env, jobject thiz) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve AudioTrack pointer for getStartThresholdInFrames()"); + return (jint)AUDIO_JAVA_ERROR; + } + const ssize_t result = lpTrack->getStartThresholdInFrames(); + if (result <= 0) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Internal error detected in getStartThresholdInFrames() = %zd", + result); + return (jint)AUDIO_JAVA_ERROR; + } + return (jint)result; // this should be a positive value. +} + +static jint android_media_AudioTrack_setStartThresholdInFrames(JNIEnv *env, jobject thiz, + jint startThresholdInFrames) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve AudioTrack pointer for setStartThresholdInFrames()"); + return (jint)AUDIO_JAVA_ERROR; + } + // non-positive values of startThresholdInFrames are not allowed by the Java layer. + const ssize_t result = lpTrack->setStartThresholdInFrames(startThresholdInFrames); + if (result <= 0) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Internal error detected in setStartThresholdInFrames() = %zd", + result); + return (jint)AUDIO_JAVA_ERROR; + } + return (jint)result; // this should be a positive value. +} + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -1530,6 +1566,10 @@ static const JNINativeMethod gMethods[] = { {"native_setLogSessionId", "(Ljava/lang/String;)V", (void *)android_media_AudioTrack_setLogSessionId}, {"native_setPlayerIId", "(I)V", (void *)android_media_AudioTrack_setPlayerIId}, + {"native_setStartThresholdInFrames", "(I)I", + (void *)android_media_AudioTrack_setStartThresholdInFrames}, + {"native_getStartThresholdInFrames", "()I", + (void *)android_media_AudioTrack_getStartThresholdInFrames}, }; // 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 88731d25706c6..bccefdfd7b969 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2085,6 +2085,65 @@ public class AudioTrack extends PlayerBase return native_get_buffer_capacity_frames(); } + /** + * Sets the streaming start threshold for an AudioTrack. + *

The streaming start threshold is the buffer level that the written audio + * data must reach for audio streaming to start after {@link #play()} is called. + *

For compressed streams, the size of a frame is considered to be exactly one byte. + * + * @param startThresholdInFrames the desired start threshold. + * @return the actual start threshold in frames value. This is + * an integer between 1 to the buffer capacity + * (see {@link #getBufferCapacityInFrames()}), + * and might change if the output sink changes after track creation. + * @throws IllegalStateException if the track is not initialized or the + * track transfer mode is not {@link #MODE_STREAM}. + * @throws IllegalArgumentException if startThresholdInFrames is not positive. + * @see #getStartThresholdInFrames() + */ + public @IntRange(from = 1) int setStartThresholdInFrames( + @IntRange (from = 1) int startThresholdInFrames) { + if (mState != STATE_INITIALIZED) { + throw new IllegalStateException("AudioTrack is not initialized"); + } + if (mDataLoadMode != MODE_STREAM) { + throw new IllegalStateException("AudioTrack must be a streaming track"); + } + if (startThresholdInFrames < 1) { + throw new IllegalArgumentException("startThresholdInFrames " + + startThresholdInFrames + " must be positive"); + } + return native_setStartThresholdInFrames(startThresholdInFrames); + } + + /** + * Returns the streaming start threshold of the AudioTrack. + *

The streaming start threshold is the buffer level that the written audio + * data must reach for audio streaming to start after {@link #play()} is called. + * When an AudioTrack is created, the streaming start threshold + * is the buffer capacity in frames. If the buffer size in frames is reduced + * by {@link #setBufferSizeInFrames(int)} to a value smaller than the start threshold + * then that value will be used instead for the streaming start threshold. + *

For compressed streams, the size of a frame is considered to be exactly one byte. + * + * @return the current start threshold in frames value. This is + * an integer between 1 to the buffer capacity + * (see {@link #getBufferCapacityInFrames()}), + * and might change if the output sink changes after track creation. + * @throws IllegalStateException if the track is not initialized or the + * track is not {@link #MODE_STREAM}. + * @see #setStartThresholdInFrames(int) + */ + public @IntRange (from = 1) int getStartThresholdInFrames() { + if (mState != STATE_INITIALIZED) { + throw new IllegalStateException("AudioTrack is not initialized"); + } + if (mDataLoadMode != MODE_STREAM) { + throw new IllegalStateException("AudioTrack must be a streaming track"); + } + return native_getStartThresholdInFrames(); + } + /** * Returns the frame count of the native AudioTrack buffer. * @return current size in frames of the AudioTrack buffer. @@ -4239,6 +4298,8 @@ public class AudioTrack extends PlayerBase private native int native_set_dual_mono_mode(int dualMonoMode); private native int native_get_dual_mono_mode(int[] dualMonoMode); private native void native_setLogSessionId(@Nullable String logSessionId); + private native int native_setStartThresholdInFrames(int startThresholdInFrames); + private native int native_getStartThresholdInFrames(); /** * Sets the audio service Player Interface Id.