media: use PlaybackSettings in MediaSync and MediaPlayer

Bug: 19666434
Change-Id: Ic5e517534a26f4e3b0294205e0b93f95338bf072
diff --git a/api/current.txt b/api/current.txt
index e509a00..8a4d0b6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15988,6 +15988,7 @@
     method public int getAudioSessionId();
     method public int getCurrentPosition();
     method public int getDuration();
+    method public android.media.PlaybackSettings getPlaybackSettings();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
     method public android.media.MediaPlayer.TrackInfo[] getTrackInfo() throws java.lang.IllegalStateException;
     method public int getVideoHeight();
@@ -16024,6 +16025,7 @@
     method public void setOnTimedTextListener(android.media.MediaPlayer.OnTimedTextListener);
     method public void setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener);
     method public void setPlaybackRate(float, int);
+    method public void setPlaybackSettings(android.media.PlaybackSettings);
     method public void setScreenOnWhilePlaying(boolean);
     method public void setSurface(android.view.Surface);
     method public void setVideoScalingMode(int);
@@ -16049,7 +16051,9 @@
     field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
     field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
     field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
-    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0; // 0x0
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; // 0x0
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; // 0x2
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; // 0x1
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
   }
@@ -16342,12 +16346,16 @@
     method public void configureAudioTrack(android.media.AudioTrack);
     method public void configureSurface(android.view.Surface);
     method public final android.view.Surface createInputSurface();
+    method public android.media.PlaybackSettings getPlaybackSettings();
     method public boolean getTimestamp(android.media.MediaTimestamp);
     method public void queueAudio(java.nio.ByteBuffer, int, int, long);
     method public final void release();
     method public void setCallback(android.media.MediaSync.Callback, android.os.Handler);
     method public void setPlaybackRate(float, int);
-    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0; // 0x0
+    method public void setPlaybackSettings(android.media.PlaybackSettings);
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; // 0x0
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; // 0x2
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; // 0x1
   }
 
   public static abstract class MediaSync.Callback {
diff --git a/api/system-current.txt b/api/system-current.txt
index 8b4052b..14e9563 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -17201,6 +17201,7 @@
     method public int getAudioSessionId();
     method public int getCurrentPosition();
     method public int getDuration();
+    method public android.media.PlaybackSettings getPlaybackSettings();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
     method public android.media.MediaPlayer.TrackInfo[] getTrackInfo() throws java.lang.IllegalStateException;
     method public int getVideoHeight();
@@ -17237,6 +17238,7 @@
     method public void setOnTimedTextListener(android.media.MediaPlayer.OnTimedTextListener);
     method public void setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener);
     method public void setPlaybackRate(float, int);
+    method public void setPlaybackSettings(android.media.PlaybackSettings);
     method public void setScreenOnWhilePlaying(boolean);
     method public void setSurface(android.view.Surface);
     method public void setVideoScalingMode(int);
@@ -17262,7 +17264,9 @@
     field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
     field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
     field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
-    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0; // 0x0
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; // 0x0
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; // 0x2
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; // 0x1
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
   }
@@ -17557,12 +17561,16 @@
     method public void configureAudioTrack(android.media.AudioTrack);
     method public void configureSurface(android.view.Surface);
     method public final android.view.Surface createInputSurface();
+    method public android.media.PlaybackSettings getPlaybackSettings();
     method public boolean getTimestamp(android.media.MediaTimestamp);
     method public void queueAudio(java.nio.ByteBuffer, int, int, long);
     method public final void release();
     method public void setCallback(android.media.MediaSync.Callback, android.os.Handler);
     method public void setPlaybackRate(float, int);
-    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0; // 0x0
+    method public void setPlaybackSettings(android.media.PlaybackSettings);
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; // 0x0
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; // 0x2
+    field public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; // 0x1
   }
 
   public static abstract class MediaSync.Callback {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 210d08f..85c6c67 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.content.ContentResolver;
@@ -44,6 +45,7 @@
 import android.media.AudioManager;
 import android.media.MediaFormat;
 import android.media.MediaTimeProvider;
+import android.media.PlaybackSettings;
 import android.media.SubtitleController;
 import android.media.SubtitleController.Anchor;
 import android.media.SubtitleData;
@@ -471,16 +473,21 @@
  *     <td>{} </p></td>
  *     <td>This method can be called in any state and calling it does not change
  *         the object state. </p></td></tr>
- * <tr><td>setScreenOnWhilePlaying</></td>
- *     <td>any </p></td>
- *     <td>{} </p></td>
- *     <td>This method can be called in any state and calling it does not change
- *         the object state.  </p></td></tr>
  * <tr><td>setPlaybackRate</p></td>
  *     <td>any </p></td>
  *     <td>{} </p></td>
  *     <td>This method can be called in any state and calling it does not change
  *         the object state. </p></td></tr>
+ * <tr><td>setPlaybackSettings</p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setScreenOnWhilePlaying</></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state.  </p></td></tr>
  * <tr><td>setVolume </p></td>
  *     <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
  *          PlaybackCompleted}</p></td>
@@ -1342,6 +1349,8 @@
     public native boolean isPlaying();
 
     /**
+     * Change playback speed of audio by resampling the audio.
+     * <p>
      * Specifies resampling as audio mode for variable rate playback, i.e.,
      * resample the waveform based on the requested playback rate to get
      * a new waveform, and play back the new waveform at the original sampling
@@ -1349,33 +1358,44 @@
      * When rate is larger than 1.0, pitch becomes higher.
      * When rate is smaller than 1.0, pitch becomes lower.
      */
-    public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0;
+    public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2;
 
     /**
+     * Change playback speed of audio without changing its pitch.
+     * <p>
      * Specifies time stretching as audio mode for variable rate playback.
      * Time stretching changes the duration of the audio samples without
      * affecting its pitch.
-     * FIXME: implement time strectching.
-     * @hide
+     * <p>
+     * This mode is only supported for a limited range of playback speed factors,
+     * e.g. between 1/2x and 2x.
      */
     public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1;
 
+    /**
+     * Change playback speed of audio without changing its pitch, and
+     * possibly mute audio if time stretching is not supported for the playback
+     * speed.
+     * <p>
+     * Try to keep audio pitch when changing the playback rate, but allow the
+     * system to determine how to change audio playback if the rate is out
+     * of range.
+     */
+    public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0;
+
     /** @hide */
     @IntDef(
         value = {
+            PLAYBACK_RATE_AUDIO_MODE_DEFAULT,
+            PLAYBACK_RATE_AUDIO_MODE_STRETCH,
             PLAYBACK_RATE_AUDIO_MODE_RESAMPLE,
-            PLAYBACK_RATE_AUDIO_MODE_STRETCH })
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlaybackRateAudioMode {}
 
     /**
      * Sets playback rate and audio mode.
      *
-     * <p> The supported audio modes are:
-     * <ul>
-     * <li> {@link #PLAYBACK_RATE_AUDIO_MODE_RESAMPLE}
-     * </ul>
-     *
      * @param rate the ratio between desired playback rate and normal one.
      * @param audioMode audio playback mode. Must be one of the supported
      * audio modes.
@@ -1385,14 +1405,46 @@
      * @throws IllegalArgumentException if audioMode is not supported.
      */
     public void setPlaybackRate(float rate, @PlaybackRateAudioMode int audioMode) {
-        if (!isAudioPlaybackModeSupported(audioMode)) {
+        PlaybackSettings settings = new PlaybackSettings();
+        settings.allowDefaults();
+        switch (audioMode) {
+        case PLAYBACK_RATE_AUDIO_MODE_DEFAULT:
+            settings.setSpeed(rate).setPitch(1.0f);
+            break;
+        case PLAYBACK_RATE_AUDIO_MODE_STRETCH:
+            settings.setSpeed(rate).setPitch(1.0f)
+                    .setAudioFallbackMode(settings.AUDIO_FALLBACK_MODE_FAIL);
+            break;
+        case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE:
+            settings.setSpeed(rate).setPitch(rate);
+            break;
+        default:
             final String msg = "Audio playback mode " + audioMode + " is not supported";
             throw new IllegalArgumentException(msg);
         }
-        _setPlaybackRate(rate);
+        setPlaybackSettings(settings);
     }
 
-    private native void _setPlaybackRate(float rate) throws IllegalStateException;
+    /**
+     * Sets playback rate using {@link PlaybackSettings}.
+     *
+     * @param settings the playback settings.
+     *
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized.
+     * @throws IllegalArgumentException if settings is not supported.
+     */
+    public native void setPlaybackSettings(@NonNull PlaybackSettings settings);
+
+    /**
+     * Gets the playback settings, containing the current playback rate.
+     *
+     * @return the playback settings.
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized.
+     */
+    @NonNull
+    public native PlaybackSettings getPlaybackSettings();
 
     /**
      * Seeks to specified time position.
@@ -3219,14 +3271,6 @@
                 mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
     }
 
-    /*
-     * Test whether a given audio playback mode is supported.
-     * TODO query supported AudioPlaybackMode from player.
-     */
-    private boolean isAudioPlaybackModeSupported(int mode) {
-        return (mode == PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
-    }
-
     /** @hide */
     static class TimeProvider implements MediaPlayer.OnSeekCompleteListener,
             MediaTimeProvider {
diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java
index 74a2fb2..cc894cb 100644
--- a/media/java/android/media/MediaSync.java
+++ b/media/java/android/media/MediaSync.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.AudioTrack;
+import android.media.PlaybackSettings;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -282,60 +283,58 @@
     public native final Surface createInputSurface();
 
     /**
-     * Specifies resampling as audio mode for variable rate playback, i.e.,
-     * resample the waveform based on the requested playback rate to get
+     * Resample audio data when changing playback speed.
+     * <p>
+     * Resample the waveform based on the requested playback rate to get
      * a new waveform, and play back the new waveform at the original sampling
      * frequency.
-     * When rate is larger than 1.0, pitch becomes higher.
-     * When rate is smaller than 1.0, pitch becomes lower.
+     * <p><ul>
+     * <li>When rate is larger than 1.0, pitch becomes higher.
+     * <li>When rate is smaller than 1.0, pitch becomes lower.
+     * </ul>
      */
-    public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0;
+    public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2;
 
     /**
-     * Specifies time stretching as audio mode for variable rate playback.
+     * Time stretch audio when changing playback speed.
+     * <p>
      * Time stretching changes the duration of the audio samples without
-     * affecting its pitch.
-     * FIXME: implement time strectching.
-     * @hide
+     * affecting their pitch. This is only supported for a limited range
+     * of playback speeds, e.g. from 1/2x to 2x. If the rate is adjusted
+     * beyond this limit, the rate change will fail.
      */
     public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1;
 
+    /**
+     * Time stretch audio when changing playback speed, and may mute if
+     * stretching is no longer supported.
+     * <p>
+     * Time stretching changes the duration of the audio samples without
+     * affecting their pitch. This is only supported for a limited range
+     * of playback speeds, e.g. from 1/2x to 2x. When it is no longer
+     * supported, the audio may be muted.  Using this mode will not fail
+     * for non-negative playback rates.
+     */
+    public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0;
+
     /** @hide */
     @IntDef(
         value = {
+            PLAYBACK_RATE_AUDIO_MODE_DEFAULT,
+            PLAYBACK_RATE_AUDIO_MODE_STRETCH,
             PLAYBACK_RATE_AUDIO_MODE_RESAMPLE,
-            PLAYBACK_RATE_AUDIO_MODE_STRETCH })
+        })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlaybackRateAudioMode {}
 
     /**
-     * Sets playback rate. It does same as {@link #setPlaybackRate(float, int)},
-     * except that it always uses {@link #PLAYBACK_RATE_AUDIO_MODE_STRETCH} for audioMode.
-     *
-     * @param rate the ratio between desired playback rate and normal one. 1.0 means normal
-     *     playback speed. 0.0 means stop or pause. Value larger than 1.0 means faster playback,
-     *     while value between 0.0 and 1.0 for slower playback.
-     *
-     * @throws IllegalStateException if the internal sync engine or the audio track has not
-     *     been initialized.
-     * TODO: unhide when PLAYBACK_RATE_AUDIO_MODE_STRETCH is supported.
-     * @hide
-     */
-    public void setPlaybackRate(float rate) {
-        setPlaybackRate(rate, PLAYBACK_RATE_AUDIO_MODE_STRETCH);
-    }
-
-    /**
      * Sets playback rate and audio mode.
      *
-     * <p> The supported audio modes are:
-     * <ul>
-     * <li> {@link #PLAYBACK_RATE_AUDIO_MODE_RESAMPLE}
-     * </ul>
-     *
      * @param rate the ratio between desired playback rate and normal one. 1.0 means normal
-     *     playback speed. 0.0 means stop or pause. Value larger than 1.0 means faster playback,
-     *     while value between 0.0 and 1.0 for slower playback.
+     *     playback speed. 0.0 means pause. Value larger than 1.0 means faster playback,
+     *     while value between 0.0 and 1.0 for slower playback. <b>Note:</b> the normal rate
+     *     does not change as a result of this call. To restore the original rate at any time,
+     *     use 1.0.
      * @param audioMode audio playback mode. Must be one of the supported
      *     audio modes.
      *
@@ -344,52 +343,104 @@
      * @throws IllegalArgumentException if audioMode is not supported.
      */
     public void setPlaybackRate(float rate, @PlaybackRateAudioMode int audioMode) {
-        if (!isAudioPlaybackModeSupported(audioMode)) {
-            final String msg = "Audio playback mode " + audioMode + " is not supported";
-            throw new IllegalArgumentException(msg);
-        }
-
-        int status = AudioTrack.SUCCESS;
-        if (mAudioTrack != null) {
-            int nativeSampleRateInHz = mAudioTrack.getSampleRate();
-            int playbackSampleRate = (int)(rate * nativeSampleRateInHz + 0.5);
-            rate = playbackSampleRate / (float)nativeSampleRateInHz;
-
-            try {
-                if (rate == 0.0) {
-                    mAudioTrack.pause();
-                } else {
-                    status = mAudioTrack.setPlaybackRate(playbackSampleRate);
-                    mAudioTrack.play();
-                }
-            } catch (IllegalStateException e) {
-                throw e;
+        PlaybackSettings rateSettings = new PlaybackSettings();
+        rateSettings.allowDefaults();
+        switch (audioMode) {
+            case PLAYBACK_RATE_AUDIO_MODE_DEFAULT:
+                rateSettings.setSpeed(rate).setPitch(1.0f);
+                break;
+            case PLAYBACK_RATE_AUDIO_MODE_STRETCH:
+                rateSettings.setSpeed(rate).setPitch(1.0f)
+                        .setAudioFallbackMode(rateSettings.AUDIO_FALLBACK_MODE_FAIL);
+                break;
+            case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE:
+                rateSettings.setSpeed(rate).setPitch(rate);
+                break;
+            default:
+            {
+                final String msg = "Audio playback mode " + audioMode + " is not supported";
+                throw new IllegalArgumentException(msg);
             }
         }
+        setPlaybackSettings(rateSettings);
+    }
 
-        if (status != AudioTrack.SUCCESS) {
-            throw new IllegalArgumentException("Fail to set playback rate in audio track");
-        }
+    /**
+     * Sets playback rate using {@link PlaybackSettings}.
+     * <p>
+     * When using MediaSync with {@link AudioTrack}, set playback settings using this
+     * call instead of calling it directly on the track, so that the sync is aware of
+     * the settings change.
+     * <p>
+     * This call also works if there is no audio track.
+     *
+     * @param settings the playback settings to use. {@link PlaybackSettings#getSpeed
+     *     Speed} is the ratio between desired playback rate and normal one. 1.0 means
+     *     normal playback speed. 0.0 means pause. Value larger than 1.0 means faster playback,
+     *     while value between 0.0 and 1.0 for slower playback. <b>Note:</b> the normal rate
+     *     does not change as a result of this call. To restore the original rate at any time,
+     *     use speed of 1.0.
+     *
+     * @throws IllegalStateException if the internal sync engine or the audio track has not
+     *     been initialized.
+     * @throws IllegalArgumentException if the settings are not supported.
+     */
+    public void setPlaybackSettings(@NonNull PlaybackSettings settings) {
+        float rate;
+        try {
+            rate = settings.getSpeed();
 
-        synchronized(mAudioLock) {
-            mPlaybackRate = rate;
+            // rate is specified
+            if (mAudioTrack != null) {
+                try {
+                    if (rate == 0.0) {
+                        mAudioTrack.pause();
+                    } else {
+                        mAudioTrack.setPlaybackSettings(settings);
+                        mAudioTrack.play();
+                    }
+                } catch (IllegalStateException e) {
+                    throw e;
+                }
+            }
+
+            synchronized(mAudioLock) {
+                mPlaybackRate = rate;
+            }
+            if (mPlaybackRate != 0.0 && mAudioThread != null) {
+                postRenderAudio(0);
+            }
+            native_setPlaybackRate(mPlaybackRate);
+        } catch (IllegalStateException e) {
+            // rate is not specified; still, propagate settings to audio track
+            if (mAudioTrack != null) {
+                mAudioTrack.setPlaybackSettings(settings);
+            }
         }
-        if (mPlaybackRate != 0.0 && mAudioThread != null) {
-            postRenderAudio(0);
+    }
+
+    /**
+     * Gets the playback rate using {@link PlaybackSettings}.
+     *
+     * @return the playback rate being used.
+     *
+     * @throws IllegalStateException if the internal sync engine or the audio track has not
+     *     been initialized.
+     */
+    @NonNull
+    public PlaybackSettings getPlaybackSettings() {
+        if (mAudioTrack != null) {
+            return mAudioTrack.getPlaybackSettings();
+        } else {
+            PlaybackSettings settings = new PlaybackSettings();
+            settings.allowDefaults();
+            settings.setSpeed(mPlaybackRate);
+            return settings;
         }
-        native_setPlaybackRate(mPlaybackRate);
     }
 
     private native final void native_setPlaybackRate(float rate);
 
-    /*
-     * Test whether a given audio playback mode is supported.
-     * TODO query supported AudioPlaybackMode from audio track.
-     */
-    private boolean isAudioPlaybackModeSupported(int mode) {
-        return (mode == PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
-    }
-
    /**
     * Get current playback position.
     * <p>
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index c247220..b79a6bb 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -20,6 +20,7 @@
 #include "utils/Log.h"
 
 #include <media/mediaplayer.h>
+#include <media/AudioResamplerPublic.h>
 #include <media/IMediaHTTPService.h>
 #include <media/MediaPlayerInterface.h>
 #include <stdio.h>
@@ -37,6 +38,7 @@
 #include "utils/KeyedVector.h"
 #include "utils/String8.h"
 #include "android_media_MediaDataSource.h"
+#include "android_media_PlaybackSettings.h"
 #include "android_media_Utils.h"
 
 #include "android_os_Parcel.h"
@@ -66,6 +68,8 @@
 };
 static fields_t fields;
 
+static PlaybackSettings::fields_t gPlaybackSettingsFields;
+
 static Mutex sLock;
 
 // ----------------------------------------------------------------------------
@@ -420,15 +424,55 @@
 }
 
 static void
-android_media_MediaPlayer_setPlaybackRate(JNIEnv *env, jobject thiz, jfloat rate)
+android_media_MediaPlayer_setPlaybackSettings(JNIEnv *env, jobject thiz, jobject settings)
 {
     sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
     if (mp == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException", NULL);
         return;
     }
-    ALOGV("setPlaybackRate: %f", rate);
-    process_media_player_call(env, thiz, mp->setPlaybackRate(rate), NULL, NULL);
+
+    PlaybackSettings pbs;
+    pbs.fillFromJobject(env, gPlaybackSettingsFields, settings);
+    ALOGV("setPlaybackSettings: %d:%f %d:%f %d:%u %d:%u",
+            pbs.speedSet, pbs.audioRate.mSpeed,
+            pbs.pitchSet, pbs.audioRate.mPitch,
+            pbs.audioFallbackModeSet, pbs.audioRate.mFallbackMode,
+            pbs.audioStretchModeSet, pbs.audioRate.mStretchMode);
+
+    // TODO: pass playback settings to mediaplayer when audiotrack supports it
+    process_media_player_call(env, thiz, mp->setPlaybackRate(pbs.audioRate.mSpeed), NULL, NULL);
+}
+
+static jobject
+android_media_MediaPlayer_getPlaybackSettings(JNIEnv *env, jobject thiz)
+{
+    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+    if (mp == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    PlaybackSettings pbs;
+    AudioPlaybackRate &audioRate = pbs.audioRate;
+
+    audioRate.mSpeed = 1.0f;
+    audioRate.mPitch = 1.0f;
+    audioRate.mFallbackMode = AUDIO_TIMESTRETCH_FALLBACK_DEFAULT;
+    audioRate.mStretchMode = AUDIO_TIMESTRETCH_STRETCH_DEFAULT;
+
+    // TODO: get this from mediaplayer when audiotrack supports it
+    // process_media_player_call(
+    //        env, thiz, mp->getPlaybackSettings(&audioRate), NULL, NULL);
+    ALOGV("getPlaybackSettings: %f %f %d %d",
+            audioRate.mSpeed, audioRate.mPitch, audioRate.mFallbackMode, audioRate.mStretchMode);
+
+    pbs.speedSet = true;
+    pbs.pitchSet = true;
+    pbs.audioFallbackModeSet = true;
+    pbs.audioStretchModeSet = true;
+
+    return pbs.asJobject(env, gPlaybackSettingsFields);
 }
 
 static void
@@ -697,6 +741,8 @@
         return;
     }
 
+    env->DeleteLocalRef(clazz);
+
     clazz = env->FindClass("android/net/ProxyInfo");
     if (clazz == NULL) {
         return;
@@ -710,6 +756,10 @@
 
     fields.proxyConfigGetExclusionList =
         env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
+
+    env->DeleteLocalRef(clazz);
+
+    gPlaybackSettingsFields.init(env);
 }
 
 static void
@@ -898,7 +948,8 @@
     {"_stop",               "()V",                              (void *)android_media_MediaPlayer_stop},
     {"getVideoWidth",       "()I",                              (void *)android_media_MediaPlayer_getVideoWidth},
     {"getVideoHeight",      "()I",                              (void *)android_media_MediaPlayer_getVideoHeight},
-    {"_setPlaybackRate",    "(F)V",                             (void *)android_media_MediaPlayer_setPlaybackRate},
+    {"setPlaybackSettings", "(Landroid/media/PlaybackSettings;)V", (void *)android_media_MediaPlayer_setPlaybackSettings},
+    {"getPlaybackSettings", "()Landroid/media/PlaybackSettings;", (void *)android_media_MediaPlayer_getPlaybackSettings},
     {"seekTo",              "(I)V",                             (void *)android_media_MediaPlayer_seekTo},
     {"_pause",              "()V",                              (void *)android_media_MediaPlayer_pause},
     {"isPlaying",           "()Z",                              (void *)android_media_MediaPlayer_isPlaying},
diff --git a/media/jni/android_media_PlaybackSettings.h b/media/jni/android_media_PlaybackSettings.h
new file mode 100644
index 0000000..1f4f256
--- /dev/null
+++ b/media/jni/android_media_PlaybackSettings.h
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_MEDIA_PLAYBACK_SETTINGS_H_
+#define _ANDROID_MEDIA_PLAYBACK_SETTINGS_H_
+
+#include <media/AudioResamplerPublic.h>
+
+namespace android {
+
+// This entire class is inline as it is used from both core and media
+struct PlaybackSettings {
+    AudioPlaybackRate audioRate;
+    bool speedSet;
+    bool pitchSet;
+    bool audioFallbackModeSet;
+    bool audioStretchModeSet;
+
+    struct fields_t {
+        jclass      clazz;
+        jmethodID   constructID;
+
+        jfieldID    speed;
+        jfieldID    pitch;
+        jfieldID    audio_fallback_mode;
+        jfieldID    audio_stretch_mode;
+        jfieldID    set;
+        jint        set_speed;
+        jint        set_pitch;
+        jint        set_audio_fallback_mode;
+        jint        set_audio_stretch_mode;
+
+        void init(JNIEnv *env) {
+            jclass lclazz = env->FindClass("android/media/PlaybackSettings");
+            if (lclazz == NULL) {
+                return;
+            }
+
+            clazz = (jclass)env->NewGlobalRef(lclazz);
+            if (clazz == NULL) {
+                return;
+            }
+
+            constructID = env->GetMethodID(clazz, "<init>", "()V");
+
+            speed = env->GetFieldID(clazz, "mSpeed", "F");
+            pitch = env->GetFieldID(clazz, "mPitch", "F");
+            audio_fallback_mode = env->GetFieldID(clazz, "mAudioFallbackMode", "I");
+            audio_stretch_mode = env->GetFieldID(clazz, "mAudioStretchMode", "I");
+            set = env->GetFieldID(clazz, "mSet", "I");
+
+            set_speed =
+                env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "SET_SPEED", "I"));
+            set_pitch =
+                env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "SET_PITCH", "I"));
+            set_audio_fallback_mode = env->GetStaticIntField(
+                    clazz, env->GetStaticFieldID(clazz, "SET_AUDIO_FALLBACK_MODE", "I"));
+            set_audio_stretch_mode = env->GetStaticIntField(
+                    clazz, env->GetStaticFieldID(clazz, "SET_AUDIO_STRETCH_MODE", "I"));
+
+            env->DeleteLocalRef(lclazz);
+        }
+
+        void exit(JNIEnv *env) {
+            env->DeleteGlobalRef(clazz);
+            clazz = NULL;
+        }
+    };
+
+    void fillFromJobject(JNIEnv *env, const fields_t& fields, jobject settings) {
+        audioRate.mSpeed = env->GetFloatField(settings, fields.speed);
+        audioRate.mPitch = env->GetFloatField(settings, fields.pitch);
+        audioRate.mFallbackMode =
+            (AudioTimestretchFallbackMode)env->GetIntField(settings, fields.audio_fallback_mode);
+        audioRate.mStretchMode =
+            (AudioTimestretchStretchMode)env->GetIntField(settings, fields.audio_stretch_mode);
+        int set = env->GetIntField(settings, fields.set);
+
+        speedSet = set & fields.set_speed;
+        pitchSet = set & fields.set_pitch;
+        audioFallbackModeSet = set & fields.set_audio_fallback_mode;
+        audioStretchModeSet = set & fields.set_audio_stretch_mode;
+    }
+
+    jobject asJobject(JNIEnv *env, const fields_t& fields) {
+        jobject settings = env->NewObject(fields.clazz, fields.constructID);
+        if (settings == NULL) {
+            return NULL;
+        }
+        env->SetFloatField(settings, fields.speed, (jfloat)audioRate.mSpeed);
+        env->SetFloatField(settings, fields.pitch, (jfloat)audioRate.mPitch);
+        env->SetIntField(settings, fields.audio_fallback_mode, (jint)audioRate.mFallbackMode);
+        env->SetIntField(settings, fields.audio_stretch_mode, (jint)audioRate.mStretchMode);
+        env->SetIntField(
+                settings, fields.set,
+                (speedSet ? fields.set_speed : 0)
+                        | (pitchSet ? fields.set_pitch : 0)
+                        | (audioFallbackModeSet ? fields.set_audio_fallback_mode : 0)
+                        | (audioStretchModeSet  ? fields.set_audio_stretch_mode : 0));
+
+        return settings;
+    }
+};
+
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_PLAYBACK_SETTINGS_H_