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
Merged-In: I5064d04961e48b530c49071ff84c2e0d2065f41b
Change-Id: I5064d04961e48b530c49071ff84c2e0d2065f41b
diff --git a/core/api/current.txt b/core/api/current.txt
index 2b5075d..0b1fa77 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19852,6 +19852,7 @@
     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);
@@ -19882,6 +19883,7 @@
     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 065c79b..452f55a 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1419,6 +1419,42 @@
     return nativeToJavaStatus(status);
 }
 
+static jint android_media_AudioTrack_getStartThresholdInFrames(JNIEnv *env, jobject thiz) {
+    sp<AudioTrack> 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<AudioTrack> 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[] = {
@@ -1496,6 +1532,10 @@
          (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},
+        {"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 1b05c3b..b265ebf 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2077,6 +2077,65 @@
     }
 
     /**
+     * Sets the streaming start threshold for an <code>AudioTrack</code>.
+     * <p> 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.
+     * <p> 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 <code>AudioTrack</code>.
+     * <p> 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 <code>AudioTrack</code> 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.
+     * <p> 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 <code>AudioTrack</code> buffer.
      *  @return current size in frames of the <code>AudioTrack</code> buffer.
      *  @throws IllegalStateException
@@ -4179,6 +4238,8 @@
     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);
+    private native int native_setStartThresholdInFrames(int startThresholdInFrames);
+    private native int native_getStartThresholdInFrames();
 
     //---------------------------------------------------------
     // Utility methods