Add PlaybackSettings for use with AudioTrack

Change-Id: Ie59686d46869558d489a7600170ddace00e548d5
diff --git a/api/current.txt b/api/current.txt
index baadf5d..b4f1931 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14987,6 +14987,7 @@
     method public int getPlayState();
     method public int getPlaybackHeadPosition();
     method public int getPlaybackRate();
+    method public android.media.PlaybackSettings getPlaybackSettings();
     method public int getPositionNotificationPeriod();
     method public android.media.AudioDeviceInfo getPreferredOutputDevice();
     method public int getSampleRate();
@@ -15004,6 +15005,7 @@
     method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener);
     method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener, android.os.Handler);
     method public int setPlaybackRate(int);
+    method public void setPlaybackSettings(android.media.PlaybackSettings);
     method public int setPositionNotificationPeriod(int);
     method public boolean setPreferredOutputDevice(android.media.AudioDeviceInfo);
     method protected deprecated void setState(int);
@@ -16367,6 +16369,24 @@
     method public abstract void onAudioDeviceConnection();
   }
 
+  public final class PlaybackSettings {
+    ctor public PlaybackSettings();
+    method public android.media.PlaybackSettings allowDefaults();
+    method public int getAudioFallbackMode();
+    method public int getAudioStretchMode();
+    method public float getPitch();
+    method public float getSpeed();
+    method public android.media.PlaybackSettings setAudioFallbackMode(int);
+    method public android.media.PlaybackSettings setAudioStretchMode(int);
+    method public android.media.PlaybackSettings setPitch(float);
+    method public android.media.PlaybackSettings setSpeed(float);
+    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
+    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
+    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
+    field public static final int AUDIO_STRETCH_MODE_DEFAULT = 0; // 0x0
+    field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1
+  }
+
   public final class Rating implements android.os.Parcelable {
     method public int describeContents();
     method public float getPercentRating();
diff --git a/api/system-current.txt b/api/system-current.txt
index d3be68b..da7932c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -16199,6 +16199,7 @@
     method public int getPlayState();
     method public int getPlaybackHeadPosition();
     method public int getPlaybackRate();
+    method public android.media.PlaybackSettings getPlaybackSettings();
     method public int getPositionNotificationPeriod();
     method public android.media.AudioDeviceInfo getPreferredOutputDevice();
     method public int getSampleRate();
@@ -16216,6 +16217,7 @@
     method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener);
     method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener, android.os.Handler);
     method public int setPlaybackRate(int);
+    method public void setPlaybackSettings(android.media.PlaybackSettings);
     method public int setPositionNotificationPeriod(int);
     method public boolean setPreferredOutputDevice(android.media.AudioDeviceInfo);
     method protected deprecated void setState(int);
@@ -17582,6 +17584,24 @@
     method public abstract void onAudioDeviceConnection();
   }
 
+  public final class PlaybackSettings {
+    ctor public PlaybackSettings();
+    method public android.media.PlaybackSettings allowDefaults();
+    method public int getAudioFallbackMode();
+    method public int getAudioStretchMode();
+    method public float getPitch();
+    method public float getSpeed();
+    method public android.media.PlaybackSettings setAudioFallbackMode(int);
+    method public android.media.PlaybackSettings setAudioStretchMode(int);
+    method public android.media.PlaybackSettings setPitch(float);
+    method public android.media.PlaybackSettings setSpeed(float);
+    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
+    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
+    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
+    field public static final int AUDIO_STRETCH_MODE_DEFAULT = 0; // 0x0
+    field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1
+  }
+
   public final class Rating implements android.os.Parcelable {
     method public int describeContents();
     method public float getPercentRating();
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 8d3a9aa..8b2c269 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -677,6 +677,63 @@
 
 
 // ----------------------------------------------------------------------------
+static void android_media_AudioTrack_set_playback_settings(JNIEnv *env,  jobject thiz,
+        jfloatArray floatArray, jintArray intArray) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "AudioTrack not initialized");
+        return;
+    }
+
+    // NOTE: Get<Primitive>ArrayRegion throws ArrayIndexOutOfBoundsException if not valid.
+    // TODO: consider the actual occupancy.
+    float farray[2];
+    int iarray[2];
+    if ((env->GetFloatArrayRegion(floatArray, 0, 2, farray), env->ExceptionCheck()) == JNI_FALSE
+            &&
+        (env->GetIntArrayRegion(intArray, 0, 2, iarray), env->ExceptionCheck()) == JNI_FALSE) {
+        // arrays retrieved OK
+        AudioPlaybackRate playbackRate;
+        playbackRate.mSpeed = farray[0];
+        playbackRate.mPitch = farray[1];
+        playbackRate.mFallbackMode = (AudioTimestretchFallbackMode)iarray[0];
+        playbackRate.mStretchMode = (AudioTimestretchStretchMode)iarray[1];
+        if (lpTrack->setPlaybackRate(playbackRate) != OK) {
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                    "arguments out of range");
+        }
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+static void android_media_AudioTrack_get_playback_settings(JNIEnv *env,  jobject thiz,
+        jfloatArray floatArray, jintArray intArray) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "AudioTrack not initialized");
+        return;
+    }
+
+    AudioPlaybackRate playbackRate = lpTrack->getPlaybackRate();
+
+    float farray[2] = {
+            playbackRate.mSpeed,
+            playbackRate.mPitch,
+    };
+    int iarray[2] = {
+            playbackRate.mFallbackMode,
+            playbackRate.mStretchMode,
+    };
+    // NOTE: Set<Primitive>ArrayRegion throws ArrayIndexOutOfBoundsException if not valid.
+    env->SetFloatArrayRegion(floatArray, 0, 2, farray);
+    env->SetIntArrayRegion(intArray, 0, 2, iarray);
+}
+
+
+// ----------------------------------------------------------------------------
 static jint android_media_AudioTrack_set_marker_pos(JNIEnv *env,  jobject thiz,
         jint markerPos) {
     sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
@@ -942,6 +999,10 @@
                              "(I)I",     (void *)android_media_AudioTrack_set_playback_rate},
     {"native_get_playback_rate",
                              "()I",      (void *)android_media_AudioTrack_get_playback_rate},
+    {"native_set_playback_settings",
+                             "([F[I)V",  (void *)android_media_AudioTrack_set_playback_settings},
+    {"native_get_playback_settings",
+                             "([F[I)V",  (void *)android_media_AudioTrack_get_playback_settings},
     {"native_set_marker_pos","(I)I",     (void *)android_media_AudioTrack_set_marker_pos},
     {"native_get_marker_pos","()I",      (void *)android_media_AudioTrack_get_marker_pos},
     {"native_set_pos_update_period",
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index ded9d31..093ff26 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -946,13 +946,30 @@
     }
 
     /**
-     * Returns the current playback rate in Hz.
+     * Returns the current playback sample rate rate in Hz.
      */
     public int getPlaybackRate() {
         return native_get_playback_rate();
     }
 
     /**
+     * Returns the current playback settings.
+     * See {@link #setPlaybackSettings(PlaybackSettings)} to set playback settings
+     * @return current {@link PlaybackSettings}.
+     * @throws IllegalStateException if track is not initialized.
+     */
+    public @NonNull PlaybackSettings getPlaybackSettings() {
+        float[] floatArray = new float[2];
+        int[] intArray = new int[2];
+        native_get_playback_settings(floatArray, intArray);
+        return new PlaybackSettings()
+                .setSpeed(floatArray[0])
+                .setPitch(floatArray[1])
+                .setAudioFallbackMode(intArray[0])
+                .setAudioStretchMode(intArray[1]);
+    }
+
+    /**
      * Returns the configured audio data format. See {@link AudioFormat#ENCODING_PCM_16BIT}
      * and {@link AudioFormat#ENCODING_PCM_8BIT}.
      */
@@ -1307,6 +1324,7 @@
      * playback to last twice as long, but will also result in a pitch shift down by one octave.
      * The valid sample rate range is from 1 Hz to twice the value returned by
      * {@link #getNativeOutputSampleRate(int)}.
+     * Use {@link #setPlaybackSettings(PlaybackSettings)} for speed control.
      * @param sampleRateInHz the sample rate expressed in Hz
      * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
      *    {@link #ERROR_INVALID_OPERATION}
@@ -1323,6 +1341,42 @@
 
 
     /**
+     * Sets the playback settings.
+     * This method returns failure if it cannot apply the playback settings.
+     * One possible cause is that the parameters for speed or pitch are out of range.
+     * Another possible cause is that the <code>AudioTrack</code> is streaming
+     * (see {@link #MODE_STREAM}) and the
+     * buffer size is too small. For speeds greater than 1.0f, the <code>AudioTrack</code> buffer
+     * on configuration must be larger than the speed multiplied by the minimum size
+     * {@link #getMinBufferSize(int, int, int)}) to allow proper playback.
+     * @param settings see {@link PlaybackSettings}. In particular,
+     * speed, pitch, and audio mode should be set.
+     * @throws IllegalArgumentException if the settings are invalid or not accepted.
+     * @throws IllegalStateException if track is not initialized.
+     */
+    public void setPlaybackSettings(@NonNull PlaybackSettings settings) {
+        if (settings == null) {
+            throw new IllegalArgumentException("settings is null");
+        }
+        float[] floatArray;
+        int[] intArray;
+        try {
+            floatArray = new float[] {
+                    settings.getSpeed(),
+                    settings.getPitch(),
+            };
+            intArray = new int[] {
+                    settings.getAudioFallbackMode(),
+                    settings.getAudioStretchMode(),
+            };
+        } catch (IllegalStateException e) {
+            throw new IllegalArgumentException(e);
+        }
+        native_set_playback_settings(floatArray, intArray);
+    }
+
+
+    /**
      * Sets the position of the notification marker.  At most one marker can be active.
      * @param markerInFrames marker position in wrapping frame units similar to
      * {@link #getPlaybackHeadPosition}, or zero to disable the marker.
@@ -2207,6 +2261,15 @@
     private native final int native_set_playback_rate(int sampleRateInHz);
     private native final int native_get_playback_rate();
 
+    // floatArray must be a non-null array of length >= 2
+    // [0] is speed
+    // [1] is pitch
+    // intArray must be a non-null array of length >= 2
+    // [0] is audio fallback mode
+    // [1] is audio stretch mode
+    private native final void native_set_playback_settings(float[] floatArray, int[] intArray);
+    private native final void native_get_playback_settings(float[] floatArray, int[] intArray);
+
     private native final int native_set_marker_pos(int marker);
     private native final int native_get_marker_pos();
 
diff --git a/media/java/android/media/PlaybackSettings.java b/media/java/android/media/PlaybackSettings.java
new file mode 100644
index 0000000..ceb6bb1
--- /dev/null
+++ b/media/java/android/media/PlaybackSettings.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2015 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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import android.annotation.IntDef;
+
+/**
+ * Structure for common playback settings.
+ *
+ * Used by {@link AudioTrack} {@link AudioTrack#getPlaybackSettings()} and
+ * {@link AudioTrack#setPlaybackSettings(PlaybackSettings)}
+ * to control playback behavior.
+ * <p> <strong>audio fallback mode:</strong>
+ * select out-of-range parameter handling.
+ * <ul>
+ * <li> {@link PlaybackSettings#AUDIO_FALLBACK_MODE_DEFAULT}:
+ *   System will determine best handling. </li>
+ * <li> {@link PlaybackSettings#AUDIO_FALLBACK_MODE_MUTE}:
+ *   Play silence for settings normally out of range.</li>
+ * <li> {@link PlaybackSettings#AUDIO_FALLBACK_MODE_FAIL}:
+ *   Return {@link java.lang.IllegalArgumentException} from
+ *   <code>AudioTrack.setPlaybackSettings(PlaybackSettings)</code>.</li>
+ * </ul>
+ * <p> <strong>audio stretch mode:</strong> select
+ * timestretch handling.
+ * <ul>
+ * <li> {@link PlaybackSettings#AUDIO_STRETCH_MODE_DEFAULT}:
+ *   System will determine best selection. </li>
+ * <li> {@link PlaybackSettings#AUDIO_STRETCH_MODE_VOICE}:
+ *   Content is primarily voice.</li>
+ * </ul>
+ * <p> <strong>pitch:</strong> increases or decreases the tonal frequency of the audio content.
+ * It is expressed as a multiplicative factor, where normal pitch is 1.0f.
+ * <p> <strong>speed:</strong> increases or decreases the time to
+ * play back a set of audio or video frames.
+ * It is expressed as a multiplicative factor, where normal speed is 1.0f.
+ * <p> Different combinations of speed and pitch may be used for audio playback;
+ * some common ones:
+ * <ul>
+ * <li> <em>Pitch equals 1.0f.</em> Speed change will be done with pitch preserved,
+ * often called <em>timestretching</em>.</li>
+ * <li> <em>Pitch equals speed.</em> Speed change will be done by <em>resampling</em>,
+ * similar to {@link AudioTrack#setPlaybackRate(int)}.</li>
+ * </ul>
+ */
+public final class PlaybackSettings {
+    /** @hide */
+    @IntDef(
+        value = {
+                AUDIO_FALLBACK_MODE_DEFAULT,
+                AUDIO_FALLBACK_MODE_MUTE,
+                AUDIO_FALLBACK_MODE_FAIL,
+        }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioFallbackMode {}
+    public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0;
+    public static final int AUDIO_FALLBACK_MODE_MUTE = 1;
+    public static final int AUDIO_FALLBACK_MODE_FAIL = 2;
+
+    /** @hide */
+    @IntDef(
+        value = {
+                AUDIO_STRETCH_MODE_DEFAULT,
+                AUDIO_STRETCH_MODE_VOICE,
+        }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioStretchMode {}
+    public static final int AUDIO_STRETCH_MODE_DEFAULT = 0;
+    public static final int AUDIO_STRETCH_MODE_VOICE = 1;
+
+    // flags to indicate which settings are actually set
+    private static final int SET_SPEED               = 1 << 0;
+    private static final int SET_PITCH               = 1 << 1;
+    private static final int SET_AUDIO_FALLBACK_MODE = 1 << 2;
+    private static final int SET_AUDIO_STRETCH_MODE  = 1 << 3;
+    private int mSet = 0;
+
+    // settings
+    private int mAudioFallbackMode = AUDIO_FALLBACK_MODE_DEFAULT;
+    private int mAudioStretchMode = AUDIO_STRETCH_MODE_DEFAULT;
+    private float mPitch = 1.0f;
+    private float mSpeed = 1.0f;
+
+    /**
+     * Allows defaults to be returned for properties not set.
+     * Otherwise a {@link java.lang.IllegalArgumentException} exception
+     * is raised when getting those properties
+     * which have defaults but have never been set.
+     * @return this <code>PlaybackSettings</code> instance.
+     */
+    public PlaybackSettings allowDefaults() {
+        mSet |= SET_AUDIO_FALLBACK_MODE | SET_AUDIO_STRETCH_MODE | SET_PITCH | SET_SPEED;
+        return this;
+    }
+
+    /**
+     * Sets the audio fallback mode.
+     * @param audioFallbackMode
+     * @return this <code>PlaybackSettings</code> instance.
+     */
+    public PlaybackSettings setAudioFallbackMode(@AudioFallbackMode int audioFallbackMode) {
+        mAudioFallbackMode = audioFallbackMode;
+        mSet |= SET_AUDIO_FALLBACK_MODE;
+        return this;
+    }
+
+    /**
+     * Retrieves the audio fallback mode.
+     * @return audio fallback mode
+     * @throws IllegalStateException if the audio fallback mode is not set.
+     */
+    public @AudioFallbackMode int getAudioFallbackMode() {
+        if ((mSet & SET_AUDIO_FALLBACK_MODE) == 0) {
+            throw new IllegalStateException("audio fallback mode not set");
+        }
+        return mAudioFallbackMode;
+    }
+
+    /**
+     * Sets the audio stretch mode.
+     * @param audioStretchMode
+     * @return this <code>PlaybackSettings</code> instance.
+     */
+    public PlaybackSettings setAudioStretchMode(@AudioStretchMode int audioStretchMode) {
+        mAudioStretchMode = audioStretchMode;
+        mSet |= SET_AUDIO_STRETCH_MODE;
+        return this;
+    }
+
+    /**
+     * Retrieves the audio stretch mode.
+     * @return audio stretch mode
+     * @throws IllegalStateException if the audio stretch mode is not set.
+     */
+    public @AudioStretchMode int getAudioStretchMode() {
+        if ((mSet & SET_AUDIO_STRETCH_MODE) == 0) {
+            throw new IllegalStateException("audio stretch mode not set");
+        }
+        return mAudioStretchMode;
+    }
+
+    /**
+     * Sets the pitch factor.
+     * @param pitch
+     * @return this <code>PlaybackSettings</code> instance.
+     */
+    public PlaybackSettings setPitch(float pitch) {
+        mPitch = pitch;
+        mSet |= SET_PITCH;
+        return this;
+    }
+
+    /**
+     * Retrieves the pitch factor.
+     * @return pitch
+     * @throws IllegalStateException if pitch is not set.
+     */
+    public float getPitch() {
+        if ((mSet & SET_PITCH) == 0) {
+            throw new IllegalStateException("pitch not set");
+        }
+        return mPitch;
+    }
+
+    /**
+     * Sets the speed factor.
+     * @param speed
+     * @return this <code>PlaybackSettings</code> instance.
+     */
+    public PlaybackSettings setSpeed(float speed) {
+        mSpeed = speed;
+        mSet |= SET_SPEED;
+        return this;
+    }
+
+    /**
+     * Retrieves the speed factor.
+     * @return speed
+     * @throws IllegalStateException if speed is not set.
+     */
+    public float getSpeed() {
+        if ((mSet & SET_SPEED) == 0) {
+            throw new IllegalStateException("speed not set");
+        }
+        return mSpeed;
+    }
+}