mediaplayer: support dynamic playback rate

Bug: 19196501

Change-Id: I856b1507d5fa2cedfb645706d2435683a7d3e050
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index 7f3e5cc..dcd5670 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -39,6 +39,7 @@
     START,
     STOP,
     IS_PLAYING,
+    SET_PLAYBACK_RATE,
     PAUSE,
     SEEK_TO,
     GET_CURRENT_POSITION,
@@ -164,6 +165,15 @@
         return reply.readInt32();
     }
 
+    status_t setPlaybackRate(float rate)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+        data.writeFloat(rate);
+        remote()->transact(SET_PLAYBACK_RATE, data, &reply);
+        return reply.readInt32();
+    }
+
     status_t pause()
     {
         Parcel data, reply;
@@ -426,6 +436,11 @@
             reply->writeInt32(ret);
             return NO_ERROR;
         } break;
+        case SET_PLAYBACK_RATE: {
+            CHECK_INTERFACE(IMediaPlayer, data, reply);
+            reply->writeInt32(setPlaybackRate(data.readFloat()));
+            return NO_ERROR;
+        } break;
         case PAUSE: {
             CHECK_INTERFACE(IMediaPlayer, data, reply);
             reply->writeInt32(pause());
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 432ecda..d1d51cc 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -59,6 +59,7 @@
     mLoop = false;
     mLeftVolume = mRightVolume = 1.0;
     mVideoWidth = mVideoHeight = 0;
+    mPlaybackRate = 1.0;
     mLockThreadId = 0;
     mAudioSessionId = AudioSystem::newAudioUniqueId();
     AudioSystem::acquireAudioSessionId(mAudioSessionId, -1);
@@ -378,6 +379,24 @@
     return false;
 }
 
+status_t MediaPlayer::setPlaybackRate(float rate)
+{
+    ALOGV("setPlaybackRate: %f", rate);
+    if (rate <= 0.0) {
+        return BAD_VALUE;
+    }
+    Mutex::Autolock _l(mLock);
+    if (mPlayer != 0) {
+        if (mPlaybackRate == rate) {
+            return NO_ERROR;
+        }
+        mPlaybackRate = rate;
+        return mPlayer->setPlaybackRate(rate);
+    }
+    ALOGV("setPlaybackRate: no active player");
+    return INVALID_OPERATION;
+}
+
 status_t MediaPlayer::getVideoWidth(int *w)
 {
     ALOGV("getVideoWidth");
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 694f1a4..0b18ae0 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -961,6 +961,14 @@
     return NO_ERROR;
 }
 
+status_t MediaPlayerService::Client::setPlaybackRate(float rate)
+{
+    ALOGV("[%d] setPlaybackRate(%f)", mConnId, rate);
+    sp<MediaPlayerBase> p = getPlayer();
+    if (p == 0) return UNKNOWN_ERROR;
+    return p->setPlaybackRate(rate);
+}
+
 status_t MediaPlayerService::Client::getCurrentPosition(int *msec)
 {
     ALOGV("getCurrentPosition");
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index fad3447..7320311 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -261,6 +261,7 @@
         virtual status_t        stop();
         virtual status_t        pause();
         virtual status_t        isPlaying(bool* state);
+        virtual status_t        setPlaybackRate(float rate);
         virtual status_t        seekTo(int msec);
         virtual status_t        getCurrentPosition(int* msec);
         virtual status_t        getDuration(int* msec);
diff --git a/media/libmediaplayerservice/nuplayer/MediaClock.cpp b/media/libmediaplayerservice/nuplayer/MediaClock.cpp
index 7bfff13..9152da1 100644
--- a/media/libmediaplayerservice/nuplayer/MediaClock.cpp
+++ b/media/libmediaplayerservice/nuplayer/MediaClock.cpp
@@ -20,19 +20,17 @@
 
 #include "MediaClock.h"
 
+#include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/ALooper.h>
 
 namespace android {
 
-// Maximum time change between two updates.
-static const int64_t kMaxAnchorFluctuationUs = 1000ll;
-
 MediaClock::MediaClock()
     : mAnchorTimeMediaUs(-1),
       mAnchorTimeRealUs(-1),
       mMaxTimeMediaUs(INT64_MAX),
       mStartingTimeMediaUs(-1),
-      mPaused(false) {
+      mPlaybackRate(1.0) {
 }
 
 MediaClock::~MediaClock() {
@@ -58,14 +56,14 @@
         return;
     }
 
+    Mutex::Autolock autoLock(mLock);
     int64_t nowUs = ALooper::GetNowUs();
-    int64_t nowMediaUs = anchorTimeMediaUs + nowUs - anchorTimeRealUs;
+    int64_t nowMediaUs =
+        anchorTimeMediaUs + (nowUs - anchorTimeRealUs) * (double)mPlaybackRate;
     if (nowMediaUs < 0) {
         ALOGW("reject anchor time since it leads to negative media time.");
         return;
     }
-
-    Mutex::Autolock autoLock(mLock);
     mAnchorTimeRealUs = nowUs;
     mAnchorTimeMediaUs = nowMediaUs;
     mMaxTimeMediaUs = maxTimeMediaUs;
@@ -76,60 +74,66 @@
     mMaxTimeMediaUs = maxTimeMediaUs;
 }
 
-void MediaClock::pause() {
+void MediaClock::setPlaybackRate(float rate) {
+    CHECK_GE(rate, 0.0);
     Mutex::Autolock autoLock(mLock);
-    if (mPaused) {
-        return;
-    }
-
-    mPaused = true;
     if (mAnchorTimeRealUs == -1) {
+        mPlaybackRate = rate;
         return;
     }
 
     int64_t nowUs = ALooper::GetNowUs();
-    mAnchorTimeMediaUs += nowUs - mAnchorTimeRealUs;
+    mAnchorTimeMediaUs += (nowUs - mAnchorTimeRealUs) * (double)mPlaybackRate;
     if (mAnchorTimeMediaUs < 0) {
-        ALOGW("anchor time should not be negative, set to 0.");
+        ALOGW("setRate: anchor time should not be negative, set to 0.");
         mAnchorTimeMediaUs = 0;
     }
     mAnchorTimeRealUs = nowUs;
+    mPlaybackRate = rate;
 }
 
-void MediaClock::resume() {
+status_t MediaClock::getMediaTime(
+        int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) {
     Mutex::Autolock autoLock(mLock);
-    if (!mPaused) {
-        return;
-    }
-
-    mPaused = false;
-    if (mAnchorTimeRealUs == -1) {
-        return;
-    }
-
-    mAnchorTimeRealUs = ALooper::GetNowUs();
+    return getMediaTime_l(realUs, outMediaUs, allowPastMaxTime);
 }
 
-int64_t MediaClock::getTimeMedia(int64_t realUs, bool allowPastMaxTime) {
-    Mutex::Autolock autoLock(mLock);
+status_t MediaClock::getMediaTime_l(
+        int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) {
     if (mAnchorTimeRealUs == -1) {
-        return -1ll;
+        return NO_INIT;
     }
 
-    if (mPaused) {
-        realUs = mAnchorTimeRealUs;
+    int64_t mediaUs = mAnchorTimeMediaUs
+            + (realUs - mAnchorTimeRealUs) * (double)mPlaybackRate;
+    if (mediaUs > mMaxTimeMediaUs && !allowPastMaxTime) {
+        mediaUs = mMaxTimeMediaUs;
     }
-    int64_t currentMediaUs = mAnchorTimeMediaUs + realUs - mAnchorTimeRealUs;
-    if (currentMediaUs > mMaxTimeMediaUs && !allowPastMaxTime) {
-        currentMediaUs = mMaxTimeMediaUs;
+    if (mediaUs < mStartingTimeMediaUs) {
+        mediaUs = mStartingTimeMediaUs;
     }
-    if (currentMediaUs < mStartingTimeMediaUs) {
-        currentMediaUs = mStartingTimeMediaUs;
+    if (mediaUs < 0) {
+        mediaUs = 0;
     }
-    if (currentMediaUs < 0) {
-        currentMediaUs = 0;
+    *outMediaUs = mediaUs;
+    return OK;
+}
+
+status_t MediaClock::getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs) {
+    Mutex::Autolock autoLock(mLock);
+    if (mPlaybackRate == 0.0) {
+        return NO_INIT;
     }
-    return currentMediaUs;
+
+    int64_t nowUs = ALooper::GetNowUs();
+    int64_t nowMediaUs;
+    status_t status =
+            getMediaTime_l(nowUs, &nowMediaUs, true /* allowPastMaxTime */);
+    if (status != OK) {
+        return status;
+    }
+    *outRealUs = (targetMediaUs - nowMediaUs) / (double)mPlaybackRate + nowUs;
+    return OK;
 }
 
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/MediaClock.h b/media/libmediaplayerservice/nuplayer/MediaClock.h
index d005993..660764f 100644
--- a/media/libmediaplayerservice/nuplayer/MediaClock.h
+++ b/media/libmediaplayerservice/nuplayer/MediaClock.h
@@ -32,9 +32,8 @@
     void setStartingTimeMedia(int64_t startingTimeMediaUs);
 
     void clearAnchor();
-    // It's highly recommended to use timestamp of just rendered frame as
-    // anchor time, especially in paused state. Such restriction will be
-    // required when dynamic playback rate is supported in the future.
+    // It's required to use timestamp of just rendered frame as
+    // anchor time in paused state.
     void updateAnchor(
         int64_t anchorTimeMediaUs,
         int64_t anchorTimeRealUs,
@@ -42,15 +41,25 @@
 
     void updateMaxTimeMedia(int64_t maxTimeMediaUs);
 
-    void pause();
-    void resume();
+    void setPlaybackRate(float rate);
 
-    int64_t getTimeMedia(int64_t realUs, bool allowPastMaxTime = false);
+    // query media time corresponding to real time |realUs|, and save the
+    // result in |outMediaUs|.
+    status_t getMediaTime(int64_t realUs,
+                          int64_t *outMediaUs,
+                          bool allowPastMaxTime = false);
+    // query real time corresponding to media time |targetMediaUs|.
+    // The result is saved in |outRealUs|.
+    status_t getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs);
 
 protected:
     virtual ~MediaClock();
 
 private:
+    status_t getMediaTime_l(int64_t realUs,
+                            int64_t *outMediaUs,
+                            bool allowPastMaxTime);
+
     Mutex mLock;
 
     int64_t mAnchorTimeMediaUs;
@@ -58,7 +67,7 @@
     int64_t mMaxTimeMediaUs;
     int64_t mStartingTimeMediaUs;
 
-    bool mPaused;
+    float mPlaybackRate;
 
     DISALLOW_EVIL_CONSTRUCTORS(MediaClock);
 };
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index fb8dbce..0d19fe9 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -180,6 +180,7 @@
       mFlushingVideo(NONE),
       mResumePending(false),
       mVideoScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW),
+      mPlaybackRate(1.0),
       mStarted(false),
       mPaused(false),
       mPausedByClient(false) {
@@ -314,6 +315,12 @@
     (new AMessage(kWhatStart, id()))->post();
 }
 
+void NuPlayer::setPlaybackRate(float rate) {
+    sp<AMessage> msg = new AMessage(kWhatSetRate, id());
+    msg->setFloat("rate", rate);
+    msg->post();
+}
+
 void NuPlayer::pause() {
     (new AMessage(kWhatPause, id()))->post();
 }
@@ -604,6 +611,16 @@
             break;
         }
 
+        case kWhatSetRate:
+        {
+            ALOGV("kWhatSetRate");
+            CHECK(msg->findFloat("rate", &mPlaybackRate));
+            if (mRenderer != NULL) {
+                mRenderer->setPlaybackRate(mPlaybackRate);
+            }
+            break;
+        }
+
         case kWhatScanSources:
         {
             int32_t generation;
@@ -1048,6 +1065,9 @@
     ++mRendererGeneration;
     notify->setInt32("generation", mRendererGeneration);
     mRenderer = new Renderer(mAudioSink, notify, flags);
+    if (mPlaybackRate != 1.0) {
+        mRenderer->setPlaybackRate(mPlaybackRate);
+    }
 
     mRendererLooper = new ALooper;
     mRendererLooper->setName("NuPlayerRenderer");
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 57eaf74..a2cb53e 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -51,6 +51,7 @@
             const sp<IGraphicBufferProducer> &bufferProducer);
 
     void setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink);
+    void setPlaybackRate(float rate);
     void start();
 
     void pause();
@@ -104,6 +105,7 @@
         kWhatSetVideoNativeWindow       = '=NaW',
         kWhatSetAudioSink               = '=AuS',
         kWhatMoreDataQueued             = 'more',
+        kWhatSetRate                    = 'setR',
         kWhatStart                      = 'strt',
         kWhatScanSources                = 'scan',
         kWhatVideoNotify                = 'vidN',
@@ -175,6 +177,7 @@
 
     int32_t mVideoScalingMode;
 
+    float mPlaybackRate;
     bool mStarted;
 
     // Actual pause state, either as requested by client or due to buffering.
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index abfa4d3..5887e50 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -341,6 +341,11 @@
     return mState == STATE_RUNNING && !mAtEOS;
 }
 
+status_t NuPlayerDriver::setPlaybackRate(float rate) {
+    mPlayer->setPlaybackRate(rate);
+    return OK;
+}
+
 status_t NuPlayerDriver::seekTo(int msec) {
     ALOGD("seekTo(%p) %d ms", this, msec);
     Mutex::Autolock autoLock(mLock);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 5cba7d9..e53abcd 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -47,6 +47,7 @@
     virtual status_t stop();
     virtual status_t pause();
     virtual bool isPlaying();
+    virtual status_t setPlaybackRate(float rate);
     virtual status_t seekTo(int msec);
     virtual status_t getCurrentPosition(int *msec);
     virtual status_t getDuration(int *msec);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 7f8680d..d21884b 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -67,6 +67,7 @@
       mVideoQueueGeneration(0),
       mAudioDrainGeneration(0),
       mVideoDrainGeneration(0),
+      mPlaybackRate(1.0),
       mAudioFirstAnchorTimeMediaUs(-1),
       mAnchorTimeMediaUs(-1),
       mAnchorNumFramesWritten(-1),
@@ -121,6 +122,12 @@
     msg->post();
 }
 
+void NuPlayer::Renderer::setPlaybackRate(float rate) {
+    sp<AMessage> msg = new AMessage(kWhatSetRate, id());
+    msg->setFloat("rate", rate);
+    msg->post();
+}
+
 void NuPlayer::Renderer::flush(bool audio, bool notifyComplete) {
     {
         Mutex::Autolock autoLock(mLock);
@@ -172,12 +179,7 @@
 
 // Called on any threads.
 status_t NuPlayer::Renderer::getCurrentPosition(int64_t *mediaUs) {
-    int64_t currentTimeUs = mMediaClock->getTimeMedia(ALooper::GetNowUs());
-    if (currentTimeUs == -1) {
-        return NO_INIT;
-    }
-    *mediaUs = currentTimeUs;
-    return OK;
+    return mMediaClock->getMediaTime(ALooper::GetNowUs(), mediaUs);
 }
 
 void NuPlayer::Renderer::clearAudioFirstAnchorTime_l() {
@@ -361,6 +363,16 @@
             break;
         }
 
+        case kWhatSetRate:
+        {
+            CHECK(msg->findFloat("rate", &mPlaybackRate));
+            int32_t ratePermille = (int32_t)(0.5f + 1000 * mPlaybackRate);
+            mPlaybackRate = ratePermille / 1000.0f;
+            mMediaClock->setPlaybackRate(mPlaybackRate);
+            mAudioSink->setPlaybackRatePermille(ratePermille);
+            break;
+        }
+
         case kWhatFlush:
         {
             onFlush(msg);
@@ -541,10 +553,10 @@
 
     if (mAudioFirstAnchorTimeMediaUs >= 0) {
         int64_t nowUs = ALooper::GetNowUs();
+        int64_t nowMediaUs =
+            mAudioFirstAnchorTimeMediaUs + getPlayedOutAudioDurationUs(nowUs);
         // we don't know how much data we are queueing for offloaded tracks.
-        mMediaClock->updateAnchor(mAudioFirstAnchorTimeMediaUs,
-                                  nowUs - getPlayedOutAudioDurationUs(nowUs),
-                                  INT64_MAX);
+        mMediaClock->updateAnchor(nowMediaUs, nowUs, INT64_MAX);
     }
 
     if (hasEOS) {
@@ -670,21 +682,27 @@
     return !mAudioQueue.empty();
 }
 
+int64_t NuPlayer::Renderer::getDurationUsIfPlayedAtSampleRate(uint32_t numFrames) {
+    int32_t sampleRate = offloadingAudio() ?
+            mCurrentOffloadInfo.sample_rate : mCurrentPcmInfo.mSampleRate;
+    // TODO: remove the (int32_t) casting below as it may overflow at 12.4 hours.
+    return (int64_t)((int32_t)numFrames * 1000000LL / sampleRate);
+}
+
+// Calculate duration of pending samples if played at normal rate (i.e., 1.0).
 int64_t NuPlayer::Renderer::getPendingAudioPlayoutDurationUs(int64_t nowUs) {
-    int64_t writtenAudioDurationUs =
-        mNumFramesWritten * 1000LL * mAudioSink->msecsPerFrame();
+    int64_t writtenAudioDurationUs = getDurationUsIfPlayedAtSampleRate(mNumFramesWritten);
     return writtenAudioDurationUs - getPlayedOutAudioDurationUs(nowUs);
 }
 
 int64_t NuPlayer::Renderer::getRealTimeUs(int64_t mediaTimeUs, int64_t nowUs) {
-    int64_t currentPositionUs =
-            mMediaClock->getTimeMedia(nowUs, true /* allowPastMaxTime */);
-    if (currentPositionUs == -1) {
+    int64_t realUs;
+    if (mMediaClock->getRealTimeFor(mediaTimeUs, &realUs) != OK) {
         // If failed to get current position, e.g. due to audio clock is
         // not ready, then just play out video immediately without delay.
         return nowUs;
     }
-    return (mediaTimeUs - currentPositionUs) + nowUs;
+    return realUs;
 }
 
 void NuPlayer::Renderer::onNewAudioMediaTime(int64_t mediaTimeUs) {
@@ -696,9 +714,8 @@
     }
     setAudioFirstAnchorTimeIfNeeded_l(mediaTimeUs);
     int64_t nowUs = ALooper::GetNowUs();
-    mMediaClock->updateAnchor(mediaTimeUs,
-                              nowUs + getPendingAudioPlayoutDurationUs(nowUs),
-                              mediaTimeUs);
+    int64_t nowMediaUs = mediaTimeUs - getPendingAudioPlayoutDurationUs(nowUs);
+    mMediaClock->updateAnchor(nowMediaUs, nowUs, mediaTimeUs);
     mAnchorTimeMediaUs = mediaTimeUs;
 }
 
@@ -828,9 +845,11 @@
             ALOGV("video late by %lld us (%.2f secs)",
                  mVideoLateByUs, mVideoLateByUs / 1E6);
         } else {
+            int64_t mediaUs = 0;
+            mMediaClock->getMediaTime(realTimeUs, &mediaUs);
             ALOGV("rendering video at media time %.2f secs",
                     (mFlags & FLAG_REAL_TIME ? realTimeUs :
-                    mMediaClock->getTimeMedia(realTimeUs)) / 1E6);
+                    mediaUs) / 1E6);
         }
     } else {
         setVideoLateByUs(0);
@@ -1153,7 +1172,7 @@
         ++mVideoDrainGeneration;
         prepareForMediaRenderingStart_l();
         mPaused = true;
-        mMediaClock->pause();
+        mMediaClock->setPlaybackRate(0.0);
     }
 
     mDrainAudioQueuePending = false;
@@ -1181,7 +1200,7 @@
     {
         Mutex::Autolock autoLock(mLock);
         mPaused = false;
-        mMediaClock->resume();
+        mMediaClock->setPlaybackRate(mPlaybackRate);
 
         if (!mAudioQueue.empty()) {
             postDrainAudioQueue_l();
@@ -1222,6 +1241,7 @@
 // accessing getTimestamp() or getPosition() every time a data buffer with
 // a media time is received.
 //
+// Calculate duration of played samples if played at normal rate (i.e., 1.0).
 int64_t NuPlayer::Renderer::getPlayedOutAudioDurationUs(int64_t nowUs) {
     uint32_t numFramesPlayed;
     int64_t numFramesPlayedAt;
@@ -1259,9 +1279,8 @@
         //ALOGD("getPosition: %d %lld", numFramesPlayed, numFramesPlayedAt);
     }
 
-    // TODO: remove the (int32_t) casting below as it may overflow at 12.4 hours.
     //CHECK_EQ(numFramesPlayed & (1 << 31), 0);  // can't be negative until 12.4 hrs, test
-    int64_t durationUs = (int64_t)((int32_t)numFramesPlayed * 1000LL * mAudioSink->msecsPerFrame())
+    int64_t durationUs = getDurationUsIfPlayedAtSampleRate(numFramesPlayed)
             + nowUs - numFramesPlayedAt;
     if (durationUs < 0) {
         // Occurs when numFramesPlayed position is very small and the following:
@@ -1400,6 +1419,10 @@
                     &offloadInfo);
 
             if (err == OK) {
+                if (mPlaybackRate != 1.0) {
+                    mAudioSink->setPlaybackRatePermille(
+                            (int32_t)(mPlaybackRate * 1000 + 0.5f));
+                }
                 // If the playback is offloaded to h/w, we pass
                 // the HAL some metadata information.
                 // We don't want to do this for PCM because it
@@ -1455,6 +1478,10 @@
             return err;
         }
         mCurrentPcmInfo = info;
+        if (mPlaybackRate != 1.0) {
+            mAudioSink->setPlaybackRatePermille(
+                    (int32_t)(mPlaybackRate * 1000 + 0.5f));
+        }
         mAudioSink->start();
     }
     if (audioSinkChanged) {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
index faf3b3f..38843d5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
@@ -48,6 +48,8 @@
 
     void queueEOS(bool audio, status_t finalResult);
 
+    void setPlaybackRate(float rate);
+
     void flush(bool audio, bool notifyComplete);
 
     void signalTimeDiscontinuity();
@@ -100,6 +102,7 @@
         kWhatPostDrainVideoQueue = 'pDVQ',
         kWhatQueueBuffer         = 'queB',
         kWhatQueueEOS            = 'qEOS',
+        kWhatSetRate             = 'setR',
         kWhatFlush               = 'flus',
         kWhatPause               = 'paus',
         kWhatResume              = 'resm',
@@ -138,6 +141,7 @@
     int32_t mVideoDrainGeneration;
 
     sp<MediaClock> mMediaClock;
+    float mPlaybackRate;
     int64_t mAudioFirstAnchorTimeMediaUs;
     int64_t mAnchorTimeMediaUs;
     int64_t mAnchorNumFramesWritten;
@@ -243,6 +247,8 @@
     void startAudioOffloadPauseTimeout();
     void cancelAudioOffloadPauseTimeout();
 
+    int64_t getDurationUsIfPlayedAtSampleRate(uint32_t numFrames);
+
     DISALLOW_EVIL_CONSTRUCTORS(Renderer);
 };