Merge "NuPlayer: save thread id in MediaPlayer::start" into lmp-dev
diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h
index 142cb90..b0a62a7 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -61,12 +61,18 @@
     OUTPUT_FORMAT_AAC_ADIF = 5,
     OUTPUT_FORMAT_AAC_ADTS = 6,
 
+    OUTPUT_FORMAT_AUDIO_ONLY_END = 7, // Used in validating the output format.  Should be the
+                                      //  at the end of the audio only output formats.
+
     /* Stream over a socket, limited to a single stream */
     OUTPUT_FORMAT_RTP_AVP = 7,
 
     /* H.264/AAC data encapsulated in MPEG2/TS */
     OUTPUT_FORMAT_MPEG2TS = 8,
 
+    /* VP8/VORBIS data in a WEBM container */
+    OUTPUT_FORMAT_WEBM = 9,
+
     OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
 };
 
@@ -77,6 +83,7 @@
     AUDIO_ENCODER_AAC = 3,
     AUDIO_ENCODER_HE_AAC = 4,
     AUDIO_ENCODER_AAC_ELD = 5,
+    AUDIO_ENCODER_VORBIS = 6,
 
     AUDIO_ENCODER_LIST_END // must be the last - used to validate the audio encoder type
 };
@@ -86,6 +93,7 @@
     VIDEO_ENCODER_H263 = 1,
     VIDEO_ENCODER_H264 = 2,
     VIDEO_ENCODER_MPEG_4_SP = 3,
+    VIDEO_ENCODER_VP8 = 4,
 
     VIDEO_ENCODER_LIST_END // must be the last - used to validate the video encoder type
 };
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 3ef6b9a..26ce5f9 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -63,8 +63,8 @@
     int32_t getTimeScale() const { return mTimeScale; }
 
     status_t setGeoData(int latitudex10000, int longitudex10000);
-    void setStartTimeOffsetMs(int ms) { mStartTimeOffsetMs = ms; }
-    int32_t getStartTimeOffsetMs() const { return mStartTimeOffsetMs; }
+    virtual void setStartTimeOffsetMs(int ms) { mStartTimeOffsetMs = ms; }
+    virtual int32_t getStartTimeOffsetMs() const { return mStartTimeOffsetMs; }
 
 protected:
     virtual ~MPEG4Writer();
diff --git a/include/media/stagefright/MediaCodec.h b/include/media/stagefright/MediaCodec.h
index 3f7508b..26a0963 100644
--- a/include/media/stagefright/MediaCodec.h
+++ b/include/media/stagefright/MediaCodec.h
@@ -30,6 +30,7 @@
 struct AString;
 struct CodecBase;
 struct ICrypto;
+struct IBatteryStats;
 struct SoftwareRenderer;
 struct Surface;
 
@@ -51,6 +52,8 @@
         CB_OUTPUT_FORMAT_CHANGED = 4,
     };
 
+    struct BatteryNotifier;
+
     static sp<MediaCodec> CreateByType(
             const sp<ALooper> &looper, const char *mime, bool encoder);
 
@@ -225,6 +228,9 @@
     sp<AMessage> mInputFormat;
     sp<AMessage> mCallback;
 
+    bool mBatteryStatNotified;
+    bool mIsVideo;
+
     // initial create parameters
     AString mInitName;
     bool mInitNameIsType;
@@ -294,6 +300,7 @@
     status_t onSetParameters(const sp<AMessage> &params);
 
     status_t amendOutputFormatWithCodecSpecificData(const sp<ABuffer> &buffer);
+    void updateBatteryStat();
 
     DISALLOW_EVIL_CONSTRUCTORS(MediaCodec);
 };
diff --git a/include/media/stagefright/MediaWriter.h b/include/media/stagefright/MediaWriter.h
index 5cc8dcf..e27ea1d 100644
--- a/include/media/stagefright/MediaWriter.h
+++ b/include/media/stagefright/MediaWriter.h
@@ -48,6 +48,9 @@
         return OK;
     }
 
+    virtual void setStartTimeOffsetMs(int ms) {}
+    virtual int32_t getStartTimeOffsetMs() const { return 0; }
+
 protected:
     virtual ~MediaWriter() {}
     int64_t mMaxFileSizeLimitBytes;
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index c8192e9..1952b86 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -186,8 +186,11 @@
         ALOGE("setOutputFormat called in an invalid state: %d", mCurrentState);
         return INVALID_OPERATION;
     }
-    if (mIsVideoSourceSet && of >= OUTPUT_FORMAT_AUDIO_ONLY_START && of != OUTPUT_FORMAT_RTP_AVP && of != OUTPUT_FORMAT_MPEG2TS) { //first non-video output format
-        ALOGE("output format (%d) is meant for audio recording only and incompatible with video recording", of);
+    if (mIsVideoSourceSet
+            && of >= OUTPUT_FORMAT_AUDIO_ONLY_START //first non-video output format
+            && of < OUTPUT_FORMAT_AUDIO_ONLY_END) {
+        ALOGE("output format (%d) is meant for audio recording only"
+              " and incompatible with video recording", of);
         return INVALID_OPERATION;
     }
 
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index 48d44c1..0c7e590c 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -49,6 +49,7 @@
     $(TOP)/frameworks/av/media/libstagefright/include               \
     $(TOP)/frameworks/av/media/libstagefright/rtsp                  \
     $(TOP)/frameworks/av/media/libstagefright/wifi-display          \
+    $(TOP)/frameworks/av/media/libstagefright/webm                  \
     $(TOP)/frameworks/native/include/media/openmax                  \
     $(TOP)/external/tremolo/Tremolo                                 \
 
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index bfc075c..217b248 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -19,6 +19,7 @@
 #include <inttypes.h>
 #include <utils/Log.h>
 
+#include "WebmWriter.h"
 #include "StagefrightRecorder.h"
 
 #include <binder/IPCThreadState.h>
@@ -764,7 +765,8 @@
         case OUTPUT_FORMAT_DEFAULT:
         case OUTPUT_FORMAT_THREE_GPP:
         case OUTPUT_FORMAT_MPEG_4:
-            status = setupMPEG4Recording();
+        case OUTPUT_FORMAT_WEBM:
+            status = setupMPEG4orWEBMRecording();
             break;
 
         case OUTPUT_FORMAT_AMR_NB:
@@ -826,9 +828,14 @@
         case OUTPUT_FORMAT_DEFAULT:
         case OUTPUT_FORMAT_THREE_GPP:
         case OUTPUT_FORMAT_MPEG_4:
+        case OUTPUT_FORMAT_WEBM:
         {
+            bool isMPEG4 = true;
+            if (mOutputFormat == OUTPUT_FORMAT_WEBM) {
+                isMPEG4 = false;
+            }
             sp<MetaData> meta = new MetaData;
-            setupMPEG4MetaData(&meta);
+            setupMPEG4orWEBMMetaData(&meta);
             status = mWriter->start(meta.get());
             break;
         }
@@ -1538,12 +1545,17 @@
     return OK;
 }
 
-status_t StagefrightRecorder::setupMPEG4Recording() {
+status_t StagefrightRecorder::setupMPEG4orWEBMRecording() {
     mWriter.clear();
     mTotalBitRate = 0;
 
     status_t err = OK;
-    sp<MediaWriter> writer = new MPEG4Writer(mOutputFd);
+    sp<MediaWriter> writer;
+    if (mOutputFormat == OUTPUT_FORMAT_MPEG_4) {
+        writer = new MPEG4Writer(mOutputFd);
+    } else {
+        writer = new WebmWriter(mOutputFd);
+    }
 
     if (mVideoSource < VIDEO_SOURCE_LIST_END) {
 
@@ -1563,22 +1575,25 @@
         mTotalBitRate += mVideoBitRate;
     }
 
-    // Audio source is added at the end if it exists.
-    // This help make sure that the "recoding" sound is suppressed for
-    // camcorder applications in the recorded files.
-    if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_CNT)) {
-        err = setupAudioEncoder(writer);
-        if (err != OK) return err;
-        mTotalBitRate += mAudioBitRate;
-    }
+    if (mOutputFormat == OUTPUT_FORMAT_MPEG_4) {
+        // Audio source is added at the end if it exists.
+        // This help make sure that the "recoding" sound is suppressed for
+        // camcorder applications in the recorded files.
+        // TODO Audio source is currently unsupported for webm output; vorbis encoder needed.
+        if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_CNT)) {
+            err = setupAudioEncoder(writer);
+            if (err != OK) return err;
+            mTotalBitRate += mAudioBitRate;
+        }
 
-    if (mInterleaveDurationUs > 0) {
-        reinterpret_cast<MPEG4Writer *>(writer.get())->
-            setInterleaveDuration(mInterleaveDurationUs);
-    }
-    if (mLongitudex10000 > -3600000 && mLatitudex10000 > -3600000) {
-        reinterpret_cast<MPEG4Writer *>(writer.get())->
-            setGeoData(mLatitudex10000, mLongitudex10000);
+        if (mInterleaveDurationUs > 0) {
+            reinterpret_cast<MPEG4Writer *>(writer.get())->
+                setInterleaveDuration(mInterleaveDurationUs);
+        }
+        if (mLongitudex10000 > -3600000 && mLatitudex10000 > -3600000) {
+            reinterpret_cast<MPEG4Writer *>(writer.get())->
+                setGeoData(mLatitudex10000, mLongitudex10000);
+        }
     }
     if (mMaxFileDurationUs != 0) {
         writer->setMaxFileDuration(mMaxFileDurationUs);
@@ -1586,7 +1601,6 @@
     if (mMaxFileSizeBytes != 0) {
         writer->setMaxFileSize(mMaxFileSizeBytes);
     }
-
     if (mVideoSource == VIDEO_SOURCE_DEFAULT
             || mVideoSource == VIDEO_SOURCE_CAMERA) {
         mStartTimeOffsetMs = mEncoderProfiles->getStartTimeOffsetMs(mCameraId);
@@ -1595,8 +1609,7 @@
         mStartTimeOffsetMs = 200;
     }
     if (mStartTimeOffsetMs > 0) {
-        reinterpret_cast<MPEG4Writer *>(writer.get())->
-            setStartTimeOffsetMs(mStartTimeOffsetMs);
+        writer->setStartTimeOffsetMs(mStartTimeOffsetMs);
     }
 
     writer->setListener(mListener);
@@ -1604,20 +1617,22 @@
     return OK;
 }
 
-void StagefrightRecorder::setupMPEG4MetaData(sp<MetaData> *meta) {
+void StagefrightRecorder::setupMPEG4orWEBMMetaData(sp<MetaData> *meta) {
     int64_t startTimeUs = systemTime() / 1000;
     (*meta)->setInt64(kKeyTime, startTimeUs);
     (*meta)->setInt32(kKeyFileType, mOutputFormat);
     (*meta)->setInt32(kKeyBitRate, mTotalBitRate);
-    (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset);
     if (mMovieTimeScale > 0) {
         (*meta)->setInt32(kKeyTimeScale, mMovieTimeScale);
     }
-    if (mTrackEveryTimeDurationUs > 0) {
-        (*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs);
-    }
-    if (mRotationDegrees != 0) {
-        (*meta)->setInt32(kKeyRotation, mRotationDegrees);
+    if (mOutputFormat == OUTPUT_FORMAT_MPEG_4) {
+        (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset);
+        if (mTrackEveryTimeDurationUs > 0) {
+            (*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs);
+        }
+        if (mRotationDegrees != 0) {
+            (*meta)->setInt32(kKeyRotation, mRotationDegrees);
+        }
     }
 }
 
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 377d168..9062f30 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -128,8 +128,8 @@
     sp<ALooper> mLooper;
 
     status_t prepareInternal();
-    status_t setupMPEG4Recording();
-    void setupMPEG4MetaData(sp<MetaData> *meta);
+    status_t setupMPEG4orWEBMRecording();
+    void setupMPEG4orWEBMMetaData(sp<MetaData> *meta);
     status_t setupAMRRecording();
     status_t setupAACRecording();
     status_t setupRawAudioRecording();
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index d75408d..63a907c 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -41,6 +41,7 @@
         bool uidValid,
         uid_t uid)
     : Source(notify),
+      mFetchSubtitleDataGeneration(0),
       mDurationUs(0ll),
       mAudioIsVorbis(false),
       mIsWidevine(isWidevine),
@@ -59,6 +60,7 @@
         const sp<AMessage> &notify,
         int fd, int64_t offset, int64_t length)
     : Source(notify),
+      mFetchSubtitleDataGeneration(0),
       mDurationUs(0ll),
       mAudioIsVorbis(false) {
     DataSource::RegisterDefaultSniffers();
@@ -133,6 +135,7 @@
         }
 
         if (track != NULL) {
+            CHECK_EQ(track->start(), (status_t)OK);
             mSources.push(track);
             int64_t durationUs;
             if (meta->findInt64(kKeyDuration, &durationUs)) {
@@ -179,21 +182,17 @@
     ALOGI("start");
 
     if (mAudioTrack.mSource != NULL) {
-        CHECK_EQ(mAudioTrack.mSource->start(), (status_t)OK);
-
         mAudioTrack.mPackets =
             new AnotherPacketSource(mAudioTrack.mSource->getFormat());
 
-        readBuffer(true /* audio */);
+        readBuffer(MEDIA_TRACK_TYPE_AUDIO);
     }
 
     if (mVideoTrack.mSource != NULL) {
-        CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK);
-
         mVideoTrack.mPackets =
             new AnotherPacketSource(mVideoTrack.mSource->getFormat());
 
-        readBuffer(false /* audio */);
+        readBuffer(MEDIA_TRACK_TYPE_VIDEO);
     }
 }
 
@@ -201,6 +200,123 @@
     return OK;
 }
 
+void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+      case kWhatFetchSubtitleData:
+      {
+          int32_t generation;
+          CHECK(msg->findInt32("generation", &generation));
+          if (generation != mFetchSubtitleDataGeneration) {
+              // stale
+              break;
+          }
+
+          int32_t avail;
+          if (mSubtitleTrack.mPackets->hasBufferAvailable(&avail)) {
+              break;
+          }
+
+          int64_t timeUs;
+          CHECK(msg->findInt64("timeUs", &timeUs));
+
+          int64_t subTimeUs;
+          readBuffer(MEDIA_TRACK_TYPE_SUBTITLE, timeUs, &subTimeUs);
+
+          const int64_t oneSecUs = 1000000ll;
+          const int64_t delayUs = subTimeUs - timeUs - oneSecUs;
+          sp<AMessage> msg2 = new AMessage(kWhatSendSubtitleData, id());
+          msg2->setInt32("generation", generation);
+          msg2->post(delayUs < 0 ? 0 : delayUs);
+          ALOGV("kWhatFetchSubtitleData generation %d, delayUs %lld",
+                  mFetchSubtitleDataGeneration, delayUs);
+
+          break;
+      }
+
+      case kWhatSendSubtitleData:
+      {
+          int32_t generation;
+          CHECK(msg->findInt32("generation", &generation));
+          if (generation != mFetchSubtitleDataGeneration) {
+              // stale
+              break;
+          }
+
+          int64_t subTimeUs;
+          if (mSubtitleTrack.mPackets->nextBufferTime(&subTimeUs) != OK) {
+              break;
+          }
+
+          int64_t nextSubTimeUs;
+          readBuffer(MEDIA_TRACK_TYPE_SUBTITLE, -1, &nextSubTimeUs);
+
+          sp<ABuffer> buffer;
+          status_t dequeueStatus = mSubtitleTrack.mPackets->dequeueAccessUnit(&buffer);
+          if (dequeueStatus != OK) {
+              ALOGE("kWhatSendSubtitleData dequeueAccessUnit: %d", dequeueStatus);
+          } else {
+              sp<AMessage> notify = dupNotify();
+              notify->setInt32("what", kWhatSubtitleData);
+              notify->setBuffer("buffer", buffer);
+              notify->post();
+
+              const int64_t delayUs = nextSubTimeUs - subTimeUs;
+              msg->post(delayUs < 0 ? 0 : delayUs);
+          }
+
+          break;
+      }
+
+      case kWhatChangeAVSource:
+      {
+          int32_t trackIndex;
+          CHECK(msg->findInt32("trackIndex", &trackIndex));
+          const sp<MediaSource> source = mSources.itemAt(trackIndex);
+
+          Track* track;
+          const char *mime;
+          media_track_type trackType, counterpartType;
+          sp<MetaData> meta = source->getFormat();
+          meta->findCString(kKeyMIMEType, &mime);
+          if (!strncasecmp(mime, "audio/", 6)) {
+              track = &mAudioTrack;
+              trackType = MEDIA_TRACK_TYPE_AUDIO;
+              counterpartType = MEDIA_TRACK_TYPE_VIDEO;;
+          } else {
+              CHECK(!strncasecmp(mime, "video/", 6));
+              track = &mVideoTrack;
+              trackType = MEDIA_TRACK_TYPE_VIDEO;
+              counterpartType = MEDIA_TRACK_TYPE_AUDIO;;
+          }
+
+
+          track->mSource = source;
+          track->mIndex = trackIndex;
+
+          status_t avail;
+          if (!track->mPackets->hasBufferAvailable(&avail)) {
+              // sync from other source
+              TRESPASS();
+              break;
+          }
+
+          int64_t timeUs, actualTimeUs;
+          const bool formatChange = true;
+          sp<AMessage> latestMeta = track->mPackets->getLatestMeta();
+          CHECK(latestMeta != NULL && latestMeta->findInt64("timeUs", &timeUs));
+          readBuffer(trackType, timeUs, &actualTimeUs, formatChange);
+          readBuffer(counterpartType, -1, NULL, formatChange);
+          ALOGV("timeUs %lld actualTimeUs %lld", timeUs, actualTimeUs);
+
+          break;
+      }
+
+      default:
+          Source::onMessageReceived(msg);
+          break;
+    }
+}
+
 sp<MetaData> NuPlayer::GenericSource::getFormatMeta(bool audio) {
     sp<MediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource;
 
@@ -221,7 +337,7 @@
 
     if (mIsWidevine && !audio) {
         // try to read a buffer as we may not have been able to the last time
-        readBuffer(audio, -1ll);
+        readBuffer(MEDIA_TRACK_TYPE_AUDIO, -1ll);
     }
 
     status_t finalResult;
@@ -231,7 +347,30 @@
 
     status_t result = track->mPackets->dequeueAccessUnit(accessUnit);
 
-    readBuffer(audio, -1ll);
+    if (!track->mPackets->hasBufferAvailable(&finalResult)) {
+        readBuffer(audio? MEDIA_TRACK_TYPE_AUDIO : MEDIA_TRACK_TYPE_VIDEO, -1ll);
+    }
+
+    if (mSubtitleTrack.mSource == NULL) {
+        return result;
+    }
+
+    CHECK(mSubtitleTrack.mPackets != NULL);
+    if (result != OK) {
+        mSubtitleTrack.mPackets->clear();
+        mFetchSubtitleDataGeneration++;
+        return result;
+    }
+
+    int64_t timeUs;
+    status_t eosResult; // ignored
+    CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs));
+    if (!mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) {
+        sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, id());
+        msg->setInt64("timeUs", timeUs);
+        msg->setInt32("generation", mFetchSubtitleDataGeneration);
+        msg->post();
+    }
 
     return result;
 }
@@ -291,25 +430,150 @@
     return format;
 }
 
+status_t NuPlayer::GenericSource::selectTrack(size_t trackIndex, bool select) {
+    ALOGV("selectTrack: %zu", trackIndex);
+    if (trackIndex >= mSources.size()) {
+        return BAD_INDEX;
+    }
+
+    if (!select) {
+        if (mSubtitleTrack.mSource == NULL || trackIndex != mSubtitleTrack.mIndex) {
+            return INVALID_OPERATION;
+        }
+        mSubtitleTrack.mSource = NULL;
+        mSubtitleTrack.mPackets->clear();
+        mFetchSubtitleDataGeneration++;
+        return OK;
+    }
+
+    const sp<MediaSource> source = mSources.itemAt(trackIndex);
+    sp<MetaData> meta = source->getFormat();
+    const char *mime;
+    CHECK(meta->findCString(kKeyMIMEType, &mime));
+    if (!strncasecmp(mime, "text/", 5)) {
+        if (mSubtitleTrack.mSource != NULL && mSubtitleTrack.mIndex == trackIndex) {
+            return OK;
+        }
+        mSubtitleTrack.mIndex = trackIndex;
+        mSubtitleTrack.mSource = mSources.itemAt(trackIndex);
+        if (mSubtitleTrack.mPackets == NULL) {
+            mSubtitleTrack.mPackets = new AnotherPacketSource(mSubtitleTrack.mSource->getFormat());
+        } else {
+            mSubtitleTrack.mPackets->clear();
+
+        }
+        mFetchSubtitleDataGeneration++;
+        return OK;
+    } else if (!strncasecmp(mime, "audio/", 6) || !strncasecmp(mime, "video/", 6)) {
+        bool audio = !strncasecmp(mime, "audio/", 6);
+        Track *track = audio ? &mAudioTrack : &mVideoTrack;
+        if (track->mSource != NULL && track->mIndex == trackIndex) {
+            return OK;
+        }
+
+        sp<AMessage> msg = new AMessage(kWhatChangeAVSource, id());
+        msg->setInt32("trackIndex", trackIndex);
+        msg->post();
+        return OK;
+    }
+
+    return INVALID_OPERATION;
+}
+
 status_t NuPlayer::GenericSource::seekTo(int64_t seekTimeUs) {
     if (mVideoTrack.mSource != NULL) {
         int64_t actualTimeUs;
-        readBuffer(false /* audio */, seekTimeUs, &actualTimeUs);
+        readBuffer(MEDIA_TRACK_TYPE_VIDEO, seekTimeUs, &actualTimeUs);
 
         seekTimeUs = actualTimeUs;
     }
 
     if (mAudioTrack.mSource != NULL) {
-        readBuffer(true /* audio */, seekTimeUs);
+        readBuffer(MEDIA_TRACK_TYPE_AUDIO, seekTimeUs);
     }
 
     return OK;
 }
 
+sp<ABuffer> NuPlayer::GenericSource::mediaBufferToABuffer(
+        MediaBuffer* mb,
+        media_track_type trackType,
+        int64_t *actualTimeUs) {
+    bool audio = trackType == MEDIA_TRACK_TYPE_AUDIO;
+    size_t outLength = mb->range_length();
+
+    if (audio && mAudioIsVorbis) {
+        outLength += sizeof(int32_t);
+    }
+
+    sp<ABuffer> ab;
+    if (mIsWidevine && !audio) {
+        // data is already provided in the buffer
+        ab = new ABuffer(NULL, mb->range_length());
+        ab->meta()->setPointer("mediaBuffer", mb);
+        mb->add_ref();
+    } else {
+        ab = new ABuffer(outLength);
+        memcpy(ab->data(),
+               (const uint8_t *)mb->data() + mb->range_offset(),
+               mb->range_length());
+    }
+
+    if (audio && mAudioIsVorbis) {
+        int32_t numPageSamples;
+        if (!mb->meta_data()->findInt32(kKeyValidSamples, &numPageSamples)) {
+            numPageSamples = -1;
+        }
+
+        uint8_t* abEnd = ab->data() + mb->range_length();
+        memcpy(abEnd, &numPageSamples, sizeof(numPageSamples));
+    }
+
+    int64_t timeUs;
+    CHECK(mb->meta_data()->findInt64(kKeyTime, &timeUs));
+
+    sp<AMessage> meta = ab->meta();
+    meta->setInt64("timeUs", timeUs);
+
+    int64_t durationUs;
+    if (mb->meta_data()->findInt64(kKeyDuration, &durationUs)) {
+        meta->setInt64("durationUs", durationUs);
+    }
+
+    if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+        meta->setInt32("trackIndex", mSubtitleTrack.mIndex);
+    }
+
+    if (actualTimeUs) {
+        *actualTimeUs = timeUs;
+    }
+
+    mb->release();
+    mb = NULL;
+
+    return ab;
+}
+
 void NuPlayer::GenericSource::readBuffer(
-        bool audio, int64_t seekTimeUs, int64_t *actualTimeUs) {
-    Track *track = audio ? &mAudioTrack : &mVideoTrack;
-    CHECK(track->mSource != NULL);
+        media_track_type trackType, int64_t seekTimeUs, int64_t *actualTimeUs, bool formatChange) {
+    Track *track;
+    switch (trackType) {
+        case MEDIA_TRACK_TYPE_VIDEO:
+            track = &mVideoTrack;
+            break;
+        case MEDIA_TRACK_TYPE_AUDIO:
+            track = &mAudioTrack;
+            break;
+        case MEDIA_TRACK_TYPE_SUBTITLE:
+            track = &mSubtitleTrack;
+            break;
+        default:
+            TRESPASS();
+    }
+
+    if (track->mSource == NULL) {
+        return;
+    }
 
     if (actualTimeUs) {
         *actualTimeUs = seekTimeUs;
@@ -320,11 +584,11 @@
     bool seeking = false;
 
     if (seekTimeUs >= 0) {
-        options.setSeekTo(seekTimeUs);
+        options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
         seeking = true;
     }
 
-    if (mIsWidevine && !audio) {
+    if (mIsWidevine && trackType != MEDIA_TRACK_TYPE_AUDIO) {
         options.setNonBlocking();
     }
 
@@ -335,56 +599,19 @@
         options.clearSeekTo();
 
         if (err == OK) {
-            size_t outLength = mbuf->range_length();
-
-            if (audio && mAudioIsVorbis) {
-                outLength += sizeof(int32_t);
+            // formatChange && seeking: track whose source is changed during selection
+            // formatChange && !seeking: track whose source is not changed during selection
+            // !formatChange: normal seek
+            if ((seeking || formatChange) && trackType != MEDIA_TRACK_TYPE_SUBTITLE) {
+                ATSParser::DiscontinuityType type = formatChange
+                        ? (seeking
+                                ? ATSParser::DISCONTINUITY_FORMATCHANGE
+                                : ATSParser::DISCONTINUITY_NONE)
+                        : ATSParser::DISCONTINUITY_SEEK;
+                track->mPackets->queueDiscontinuity( type, NULL, true /* discard */);
             }
 
-            sp<ABuffer> buffer;
-            if (mIsWidevine && !audio) {
-                // data is already provided in the buffer
-                buffer = new ABuffer(NULL, mbuf->range_length());
-                buffer->meta()->setPointer("mediaBuffer", mbuf);
-                mbuf->add_ref();
-            } else {
-                buffer = new ABuffer(outLength);
-                memcpy(buffer->data(),
-                       (const uint8_t *)mbuf->data() + mbuf->range_offset(),
-                       mbuf->range_length());
-            }
-
-            if (audio && mAudioIsVorbis) {
-                int32_t numPageSamples;
-                if (!mbuf->meta_data()->findInt32(
-                            kKeyValidSamples, &numPageSamples)) {
-                    numPageSamples = -1;
-                }
-
-                memcpy(buffer->data() + mbuf->range_length(),
-                       &numPageSamples,
-                       sizeof(numPageSamples));
-            }
-
-            int64_t timeUs;
-            CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs));
-
-            buffer->meta()->setInt64("timeUs", timeUs);
-
-            if (actualTimeUs) {
-                *actualTimeUs = timeUs;
-            }
-
-            mbuf->release();
-            mbuf = NULL;
-
-            if (seeking) {
-                track->mPackets->queueDiscontinuity(
-                        ATSParser::DISCONTINUITY_SEEK,
-                        NULL,
-                        true /* discard */);
-            }
-
+            sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType, actualTimeUs);
             track->mPackets->queueAccessUnit(buffer);
             break;
         } else if (err == WOULD_BLOCK) {
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h
index 8e0209d..4e25d55 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.h
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.h
@@ -23,12 +23,15 @@
 
 #include "ATSParser.h"
 
+#include <media/mediaplayer.h>
+
 namespace android {
 
 struct AnotherPacketSource;
 struct ARTSPController;
 struct DataSource;
 struct MediaSource;
+class MediaBuffer;
 
 struct NuPlayer::GenericSource : public NuPlayer::Source {
     GenericSource(
@@ -55,6 +58,7 @@
     virtual status_t getDuration(int64_t *durationUs);
     virtual size_t getTrackCount() const;
     virtual sp<AMessage> getTrackInfo(size_t trackIndex) const;
+    virtual status_t selectTrack(size_t trackIndex, bool select);
     virtual status_t seekTo(int64_t seekTimeUs);
 
     virtual status_t setBuffers(bool audio, Vector<MediaBuffer *> &buffers);
@@ -62,9 +66,17 @@
 protected:
     virtual ~GenericSource();
 
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
     virtual sp<MetaData> getFormatMeta(bool audio);
 
 private:
+    enum {
+        kWhatFetchSubtitleData,
+        kWhatSendSubtitleData,
+        kWhatChangeAVSource,
+    };
+
     Vector<sp<MediaSource> > mSources;
 
     struct Track {
@@ -75,7 +87,9 @@
 
     Track mAudioTrack;
     Track mVideoTrack;
+    Track mSubtitleTrack;
 
+    int32_t mFetchSubtitleDataGeneration;
     int64_t mDurationUs;
     bool mAudioIsVorbis;
     bool mIsWidevine;
@@ -84,9 +98,14 @@
 
     void initFromDataSource(const sp<DataSource> &dataSource);
 
+    sp<ABuffer> mediaBufferToABuffer(
+            MediaBuffer *mbuf,
+            media_track_type trackType,
+            int64_t *actualTimeUs = NULL);
+
     void readBuffer(
-            bool audio,
-            int64_t seekTimeUs = -1ll, int64_t *actualTimeUs = NULL);
+            media_track_type trackType,
+            int64_t seekTimeUs = -1ll, int64_t *actualTimeUs = NULL, bool formatChange = false);
 
     DISALLOW_EVIL_CONSTRUCTORS(GenericSource);
 };
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 6ccd27a..fa6b1e5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -754,6 +754,7 @@
                             offloadInfo.has_video = (mVideoDecoder != NULL);
                             offloadInfo.is_streaming = true;
 
+                            ALOGV("try to open AudioSink in offload mode");
                             err = mAudioSink->open(
                                     sampleRate,
                                     numChannels,
@@ -793,6 +794,7 @@
 
                     if (!mOffloadAudio) {
                         flags &= ~AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;
+                        ALOGV("open AudioSink in NON-offload mode");
                         CHECK_EQ(mAudioSink->open(
                                     sampleRate,
                                     numChannels,
@@ -940,6 +942,21 @@
             } else if (what == Renderer::kWhatMediaRenderingStart) {
                 ALOGV("media rendering started");
                 notifyListener(MEDIA_STARTED, 0, 0);
+            } else if (what == Renderer::kWhatAudioOffloadTearDown) {
+                ALOGV("Tear down audio offload, fall back to s/w path");
+                int64_t positionUs;
+                CHECK(msg->findInt64("positionUs", &positionUs));
+                mAudioSink->close();
+                mAudioDecoder.clear();
+                mRenderer->flush(true /* audio */);
+                if (mVideoDecoder != NULL) {
+                    mRenderer->flush(false /* audio */);
+                }
+                mRenderer->signalDisableOffloadAudio();
+                mOffloadAudio = false;
+
+                performSeek(positionUs);
+                instantiateDecoder(true /* audio */, &mAudioDecoder);
             }
             break;
         }
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 8592ec2..3640038 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -223,6 +223,12 @@
             break;
         }
 
+        case kWhatAudioOffloadTearDown:
+        {
+            onAudioOffloadTearDown();
+            break;
+        }
+
         default:
             TRESPASS();
             break;
@@ -294,7 +300,7 @@
 
         case MediaPlayerBase::AudioSink::CB_EVENT_TEAR_DOWN:
         {
-            // TODO: send this to player.
+            me->notifyAudioOffloadTearDown();
             break;
         }
     }
@@ -582,6 +588,10 @@
     notify->post();
 }
 
+void NuPlayer::Renderer::notifyAudioOffloadTearDown() {
+    (new AMessage(kWhatAudioOffloadTearDown, id()))->post();
+}
+
 void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) {
     int32_t audio;
     CHECK(msg->findInt32("audio", &audio));
@@ -814,6 +824,7 @@
 void NuPlayer::Renderer::onDisableOffloadAudio() {
     Mutex::Autolock autoLock(mLock);
     mFlags &= ~FLAG_OFFLOAD_AUDIO;
+    ++mAudioQueueGeneration;
 }
 
 void NuPlayer::Renderer::notifyPosition() {
@@ -880,5 +891,21 @@
     }
 }
 
+void NuPlayer::Renderer::onAudioOffloadTearDown() {
+    uint32_t numFramesPlayed;
+    CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed), (status_t)OK);
+
+    int64_t currentPositionUs = mFirstAudioTimeUs
+            + (numFramesPlayed * mAudioSink->msecsPerFrame()) * 1000ll;
+
+    mAudioSink->stop();
+    mAudioSink->flush();
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatAudioOffloadTearDown);
+    notify->setInt64("positionUs", currentPositionUs);
+    notify->post();
+}
+
 }  // namespace android
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
index 6e86a8f..1cba1a0 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
@@ -62,6 +62,7 @@
         kWhatPosition            = 'posi',
         kWhatVideoRenderingStart = 'vdrd',
         kWhatMediaRenderingStart = 'mdrd',
+        kWhatAudioOffloadTearDown = 'aOTD',
     };
 
 protected:
@@ -143,12 +144,14 @@
     void onDisableOffloadAudio();
     void onPause();
     void onResume();
+    void onAudioOffloadTearDown();
 
     void notifyEOS(bool audio, status_t finalResult);
     void notifyFlushComplete(bool audio);
     void notifyPosition();
     void notifyVideoLateBy(int64_t lateByUs);
     void notifyVideoRenderingStart();
+    void notifyAudioOffloadTearDown();
 
     void flushQueue(List<QueueEntry> *queue);
     bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &msg);
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 6cb1c64..b6cc742 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -2765,6 +2765,50 @@
 
                     break;
                 }
+
+                case OMX_VIDEO_CodingVP8:
+                case OMX_VIDEO_CodingVP9:
+                {
+                    OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE vp8type;
+                    InitOMXParams(&vp8type);
+                    vp8type.nPortIndex = kPortIndexOutput;
+                    status_t err = mOMX->getParameter(
+                            mNode,
+                            (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidVp8Encoder,
+                            &vp8type,
+                            sizeof(vp8type));
+
+                    if (err == OK) {
+                        AString tsSchema = "none";
+                        if (vp8type.eTemporalPattern
+                                == OMX_VIDEO_VPXTemporalLayerPatternWebRTC) {
+                            switch (vp8type.nTemporalLayerCount) {
+                                case 1:
+                                {
+                                    tsSchema = "webrtc.vp8.1-layer";
+                                    break;
+                                }
+                                case 2:
+                                {
+                                    tsSchema = "webrtc.vp8.2-layer";
+                                    break;
+                                }
+                                case 3:
+                                {
+                                    tsSchema = "webrtc.vp8.3-layer";
+                                    break;
+                                }
+                                default:
+                                {
+                                    break;
+                                }
+                            }
+                        }
+                        notify->setString("ts-schema", tsSchema);
+                    }
+                    // Fall through to set up mime.
+                }
+
                 default:
                 {
                     CHECK(mIsEncoder ^ (portIndex == kPortIndexInput));
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 7a9cb0b..15e062e 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -16,13 +16,13 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaCodec"
-#include <utils/Log.h>
 #include <inttypes.h>
 
-#include <media/stagefright/MediaCodec.h>
-
+#include "include/avc_utils.h"
 #include "include/SoftwareRenderer.h"
 
+#include <binder/IBatteryStats.h>
+#include <binder/IServiceManager.h>
 #include <gui/Surface.h>
 #include <media/ICrypto.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -32,16 +32,85 @@
 #include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/ACodec.h>
 #include <media/stagefright/BufferProducerWrapper.h>
+#include <media/stagefright/MediaCodec.h>
 #include <media/stagefright/MediaCodecList.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/NativeWindowWrapper.h>
-
-#include "include/avc_utils.h"
+#include <private/android_filesystem_config.h>
+#include <utils/Log.h>
+#include <utils/Singleton.h>
 
 namespace android {
 
+struct MediaCodec::BatteryNotifier : public Singleton<BatteryNotifier> {
+    BatteryNotifier();
+
+    void noteStartVideo();
+    void noteStopVideo();
+    void noteStartAudio();
+    void noteStopAudio();
+
+private:
+    int32_t mVideoRefCount;
+    int32_t mAudioRefCount;
+    sp<IBatteryStats> mBatteryStatService;
+};
+
+ANDROID_SINGLETON_STATIC_INSTANCE(MediaCodec::BatteryNotifier)
+
+MediaCodec::BatteryNotifier::BatteryNotifier() :
+    mVideoRefCount(0),
+    mAudioRefCount(0) {
+    // get battery service
+    const sp<IServiceManager> sm(defaultServiceManager());
+    if (sm != NULL) {
+        const String16 name("batterystats");
+        mBatteryStatService = interface_cast<IBatteryStats>(sm->getService(name));
+        if (mBatteryStatService == NULL) {
+            ALOGE("batterystats service unavailable!");
+        }
+    }
+}
+
+void MediaCodec::BatteryNotifier::noteStartVideo() {
+    if (mVideoRefCount == 0 && mBatteryStatService != NULL) {
+        mBatteryStatService->noteStartVideo(AID_MEDIA);
+    }
+    mVideoRefCount++;
+}
+
+void MediaCodec::BatteryNotifier::noteStopVideo() {
+    if (mVideoRefCount == 0) {
+        ALOGW("BatteryNotifier::noteStop(): video refcount is broken!");
+        return;
+    }
+
+    mVideoRefCount--;
+    if (mVideoRefCount == 0 && mBatteryStatService != NULL) {
+        mBatteryStatService->noteStopVideo(AID_MEDIA);
+    }
+}
+
+void MediaCodec::BatteryNotifier::noteStartAudio() {
+    if (mAudioRefCount == 0 && mBatteryStatService != NULL) {
+        mBatteryStatService->noteStartAudio(AID_MEDIA);
+    }
+    mAudioRefCount++;
+}
+
+void MediaCodec::BatteryNotifier::noteStopAudio() {
+    if (mAudioRefCount == 0) {
+        ALOGW("BatteryNotifier::noteStop(): audio refcount is broken!");
+        return;
+    }
+
+    mAudioRefCount--;
+    if (mAudioRefCount == 0 && mBatteryStatService != NULL) {
+        mBatteryStatService->noteStopAudio(AID_MEDIA);
+    }
+}
 // static
 sp<MediaCodec> MediaCodec::CreateByType(
         const sp<ALooper> &looper, const char *mime, bool encoder) {
@@ -71,6 +140,8 @@
       mReplyID(0),
       mFlags(0),
       mSoftRenderer(NULL),
+      mBatteryStatNotified(false),
+      mIsVideo(false),
       mDequeueInputTimeoutGeneration(0),
       mDequeueInputReplyID(0),
       mDequeueOutputTimeoutGeneration(0),
@@ -756,7 +827,6 @@
                 case CodecBase::kWhatComponentConfigured:
                 {
                     CHECK_EQ(mState, CONFIGURING);
-                    setState(CONFIGURED);
 
                     // reset input surface flag
                     mHaveInputSurface = false;
@@ -764,6 +834,7 @@
                     CHECK(msg->findMessage("input-format", &mInputFormat));
                     CHECK(msg->findMessage("output-format", &mOutputFormat));
 
+                    setState(CONFIGURED);
                     (new AMessage)->postReply(mReplyID);
                     break;
                 }
@@ -1620,6 +1691,8 @@
     mState = newState;
 
     cancelPendingDequeueOperations();
+
+    updateBatteryStat();
 }
 
 void MediaCodec::returnBuffersToCodec() {
@@ -2054,4 +2127,34 @@
     return OK;
 }
 
+void MediaCodec::updateBatteryStat() {
+    if (mState == CONFIGURED && !mBatteryStatNotified) {
+        AString mime;
+        CHECK(mOutputFormat != NULL &&
+                mOutputFormat->findString("mime", &mime));
+
+        mIsVideo = mime.startsWithIgnoreCase("video/");
+
+        BatteryNotifier& notifier(BatteryNotifier::getInstance());
+
+        if (mIsVideo) {
+            notifier.noteStartVideo();
+        } else {
+            notifier.noteStartAudio();
+        }
+
+        mBatteryStatNotified = true;
+    } else if (mState == UNINITIALIZED && mBatteryStatNotified) {
+        BatteryNotifier& notifier(BatteryNotifier::getInstance());
+
+        if (mIsVideo) {
+            notifier.noteStopVideo();
+        } else {
+            notifier.noteStopAudio();
+        }
+
+        mBatteryStatNotified = false;
+    }
+}
+
 }  // namespace android
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index a0319ab..72c9dae 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -34,6 +34,7 @@
 
 AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
     : mIsAudio(false),
+      mIsVideo(false),
       mFormat(NULL),
       mLastQueuedTimeUs(0),
       mEOSResult(OK),
@@ -45,6 +46,7 @@
     CHECK(mFormat == NULL);
 
     mIsAudio = false;
+    mIsVideo = false;
 
     if (meta == NULL) {
         return;
@@ -56,8 +58,10 @@
 
     if (!strncasecmp("audio/", mime, 6)) {
         mIsAudio = true;
+    } else  if (!strncasecmp("video/", mime, 6)) {
+        mIsVideo = true;
     } else {
-        CHECK(!strncasecmp("video/", mime, 6));
+        CHECK(!strncasecmp("text/", mime, 5));
     }
 }
 
@@ -175,7 +179,11 @@
         return (discontinuityType & ATSParser::DISCONTINUITY_AUDIO_FORMAT) != 0;
     }
 
-    return (discontinuityType & ATSParser::DISCONTINUITY_VIDEO_FORMAT) != 0;
+    if (mIsVideo) {
+        return (discontinuityType & ATSParser::DISCONTINUITY_VIDEO_FORMAT) != 0;
+    }
+
+    return false;
 }
 
 void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index 06c49bd..f38f9dc 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -74,6 +74,7 @@
     Condition mCondition;
 
     bool mIsAudio;
+    bool mIsVideo;
     sp<MetaData> mFormat;
     int64_t mLastQueuedTimeUs;
     List<sp<ABuffer> > mBuffers;
diff --git a/media/libstagefright/webm/WebmWriter.h b/media/libstagefright/webm/WebmWriter.h
index 529dec8..36b6965 100644
--- a/media/libstagefright/webm/WebmWriter.h
+++ b/media/libstagefright/webm/WebmWriter.h
@@ -41,14 +41,14 @@
     ~WebmWriter() { reset(); }
 
 
-    status_t addSource(const sp<MediaSource> &source);
-    status_t start(MetaData *param = NULL);
-    status_t stop();
-    status_t pause();
-    bool reachedEOS();
+    virtual status_t addSource(const sp<MediaSource> &source);
+    virtual status_t start(MetaData *param = NULL);
+    virtual status_t stop();
+    virtual status_t pause();
+    virtual bool reachedEOS();
 
-    void setStartTimeOffsetMs(int ms) { mStartTimeOffsetMs = ms; }
-    int32_t getStartTimeOffsetMs() const { return mStartTimeOffsetMs; }
+    virtual void setStartTimeOffsetMs(int ms) { mStartTimeOffsetMs = ms; }
+    virtual int32_t getStartTimeOffsetMs() const { return mStartTimeOffsetMs; }
 
 private:
     int mFd;
diff --git a/services/audioflinger/AudioResamplerFirProcess.h b/services/audioflinger/AudioResamplerFirProcess.h
index bb0f1c9..d130013 100644
--- a/services/audioflinger/AudioResamplerFirProcess.h
+++ b/services/audioflinger/AudioResamplerFirProcess.h
@@ -109,40 +109,25 @@
     }
 };
 
-/*
- * Helper template functions for interpolating filter coefficients.
- */
-
-template<typename TC, typename T>
-void adjustLerp(T& lerpP __unused)
-{
-}
-
-template<int32_t, typename T>
-void adjustLerp(T& lerpP)
-{
-    lerpP >>= 16;   // lerpP is 32bit for NEON int32_t, but always 16 bit for non-NEON path
-}
-
 template<typename TC, typename TINTERP>
-static inline
+inline
 TC interpolate(TC coef_0, TC coef_1, TINTERP lerp)
 {
     return lerp * (coef_1 - coef_0) + coef_0;
 }
 
-template<int16_t, uint32_t>
-static inline
-int16_t interpolate(int16_t coef_0, int16_t coef_1, uint32_t lerp)
-{
-    return (static_cast<int16_t>(lerp) * ((coef_1-coef_0)<<1)>>16) + coef_0;
+template<>
+inline
+int16_t interpolate<int16_t, uint32_t>(int16_t coef_0, int16_t coef_1, uint32_t lerp)
+{   // in some CPU architectures 16b x 16b multiplies are faster.
+    return (static_cast<int16_t>(lerp) * static_cast<int16_t>(coef_1 - coef_0) >> 15) + coef_0;
 }
 
-template<int32_t, uint32_t>
-static inline
-int32_t interpolate(int32_t coef_0, int32_t coef_1, uint32_t lerp)
+template<>
+inline
+int32_t interpolate<int32_t, uint32_t>(int32_t coef_0, int32_t coef_1, uint32_t lerp)
 {
-    return mulAdd(static_cast<int16_t>(lerp), (coef_1-coef_0)<<1, coef_0);
+    return (lerp * static_cast<int64_t>(coef_1 - coef_0) >> 31) + coef_0;
 }
 
 /* class scope for passing in functions into templates */
@@ -283,7 +268,6 @@
         TINTERP lerpP,
         const TO* const volumeLR)
 {
-    adjustLerp<TC, TINTERP>(lerpP); // coefficient type adjustment for interpolations
     ProcessBase<CHANNELS, STRIDE, InterpCompute>(out, count, coefsP, coefsN, sP, sN, lerpP, volumeLR);
 }
 
diff --git a/services/audioflinger/tests/resampler_tests.cpp b/services/audioflinger/tests/resampler_tests.cpp
index 8624b62..169ce02 100644
--- a/services/audioflinger/tests/resampler_tests.cpp
+++ b/services/audioflinger/tests/resampler_tests.cpp
@@ -29,6 +29,7 @@
 #include <math.h>
 #include <vector>
 #include <utility>
+#include <iostream>
 #include <cutils/log.h>
 #include <gtest/gtest.h>
 #include <media/AudioBufferProvider.h>
@@ -153,6 +154,9 @@
     return accum / count;
 }
 
+// TI = resampler input type, int16_t or float
+// TO = resampler output type, int32_t or float
+template <typename TI, typename TO>
 void testStopbandDownconversion(size_t channels,
         unsigned inputFreq, unsigned outputFreq,
         unsigned passband, unsigned stopband,
@@ -161,20 +165,21 @@
     // create the provider
     std::vector<int> inputIncr;
     SignalProvider provider;
-    provider.setChirp<int16_t>(channels,
+    provider.setChirp<TI>(channels,
             0., inputFreq/2., inputFreq, inputFreq/2000.);
     provider.setIncr(inputIncr);
 
     // calculate the output size
     size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq;
-    size_t outputFrameSize = channels * sizeof(int32_t);
+    size_t outputFrameSize = channels * sizeof(TO);
     size_t outputSize = outputFrameSize * outputFrames;
     outputSize &= ~7;
 
     // create the resampler
     android::AudioResampler* resampler;
 
-    resampler = android::AudioResampler::create(AUDIO_FORMAT_PCM_16_BIT,
+    resampler = android::AudioResampler::create(
+            is_same<TI, int16_t>::value ? AUDIO_FORMAT_PCM_16_BIT : AUDIO_FORMAT_PCM_FLOAT,
             channels, outputFreq, quality);
     resampler->setSampleRate(inputFreq);
     resampler->setVolume(android::AudioResampler::UNITY_GAIN_FLOAT,
@@ -186,7 +191,7 @@
     void* reference = malloc(outputSize);
     resample(channels, reference, outputFrames, refIncr, &provider, resampler);
 
-    int32_t *out = reinterpret_cast<int32_t *>(reference);
+    TO *out = reinterpret_cast<TO *>(reference);
 
     // check signal energy in passband
     const unsigned passbandFrame = passband * outputFreq / 1000.;
@@ -206,10 +211,10 @@
                 provider.getNumFrames(), outputFrames,
                 passbandFrame, stopbandFrame, stopbandEnergy, passbandEnergy, dbAtten);
         for (size_t i = 0; i < 10; ++i) {
-            printf("%d\n", out[i+passbandFrame*channels]);
+            std::cout << out[i+passbandFrame*channels] << std::endl;
         }
         for (size_t i = 0; i < 10; ++i) {
-            printf("%d\n", out[i+stopbandFrame*channels]);
+            std::cout << out[i+stopbandFrame*channels] << std::endl;
         }
 #endif
     }
@@ -292,7 +297,7 @@
  * are properly suppressed.  It uses downsampling because the stopband can be
  * clearly isolated by input frequencies exceeding the output sample rate (nyquist).
  */
-TEST(audioflinger_resampler, stopbandresponse) {
+TEST(audioflinger_resampler, stopbandresponse_integer) {
     // not all of these may work (old resamplers fail on downsampling)
     static const enum android::AudioResampler::src_quality kQualityArray[] = {
             //android::AudioResampler::LOW_QUALITY,
@@ -307,13 +312,100 @@
     // in this test we assume a maximum transition band between 12kHz and 20kHz.
     // there must be at least 60dB relative attenuation between stopband and passband.
     for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
-        testStopbandDownconversion(2, 48000, 32000, 12000, 20000, kQualityArray[i]);
+        testStopbandDownconversion<int16_t, int32_t>(
+                2, 48000, 32000, 12000, 20000, kQualityArray[i]);
     }
 
     // in this test we assume a maximum transition band between 7kHz and 15kHz.
     // there must be at least 60dB relative attenuation between stopband and passband.
     // (the weird ratio triggers interpolative resampling)
     for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
-        testStopbandDownconversion(2, 48000, 22101, 7000, 15000, kQualityArray[i]);
+        testStopbandDownconversion<int16_t, int32_t>(
+                2, 48000, 22101, 7000, 15000, kQualityArray[i]);
     }
 }
+
+TEST(audioflinger_resampler, stopbandresponse_integer_multichannel) {
+    // not all of these may work (old resamplers fail on downsampling)
+    static const enum android::AudioResampler::src_quality kQualityArray[] = {
+            //android::AudioResampler::LOW_QUALITY,
+            //android::AudioResampler::MED_QUALITY,
+            //android::AudioResampler::HIGH_QUALITY,
+            //android::AudioResampler::VERY_HIGH_QUALITY,
+            android::AudioResampler::DYN_LOW_QUALITY,
+            android::AudioResampler::DYN_MED_QUALITY,
+            android::AudioResampler::DYN_HIGH_QUALITY,
+    };
+
+    // in this test we assume a maximum transition band between 12kHz and 20kHz.
+    // there must be at least 60dB relative attenuation between stopband and passband.
+    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+        testStopbandDownconversion<int16_t, int32_t>(
+                8, 48000, 32000, 12000, 20000, kQualityArray[i]);
+    }
+
+    // in this test we assume a maximum transition band between 7kHz and 15kHz.
+    // there must be at least 60dB relative attenuation between stopband and passband.
+    // (the weird ratio triggers interpolative resampling)
+    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+        testStopbandDownconversion<int16_t, int32_t>(
+                8, 48000, 22101, 7000, 15000, kQualityArray[i]);
+    }
+}
+
+TEST(audioflinger_resampler, stopbandresponse_float) {
+    // not all of these may work (old resamplers fail on downsampling)
+    static const enum android::AudioResampler::src_quality kQualityArray[] = {
+            //android::AudioResampler::LOW_QUALITY,
+            //android::AudioResampler::MED_QUALITY,
+            //android::AudioResampler::HIGH_QUALITY,
+            //android::AudioResampler::VERY_HIGH_QUALITY,
+            android::AudioResampler::DYN_LOW_QUALITY,
+            android::AudioResampler::DYN_MED_QUALITY,
+            android::AudioResampler::DYN_HIGH_QUALITY,
+    };
+
+    // in this test we assume a maximum transition band between 12kHz and 20kHz.
+    // there must be at least 60dB relative attenuation between stopband and passband.
+    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+        testStopbandDownconversion<float, float>(
+                2, 48000, 32000, 12000, 20000, kQualityArray[i]);
+    }
+
+    // in this test we assume a maximum transition band between 7kHz and 15kHz.
+    // there must be at least 60dB relative attenuation between stopband and passband.
+    // (the weird ratio triggers interpolative resampling)
+    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+        testStopbandDownconversion<float, float>(
+                2, 48000, 22101, 7000, 15000, kQualityArray[i]);
+    }
+}
+
+TEST(audioflinger_resampler, stopbandresponse_float_multichannel) {
+    // not all of these may work (old resamplers fail on downsampling)
+    static const enum android::AudioResampler::src_quality kQualityArray[] = {
+            //android::AudioResampler::LOW_QUALITY,
+            //android::AudioResampler::MED_QUALITY,
+            //android::AudioResampler::HIGH_QUALITY,
+            //android::AudioResampler::VERY_HIGH_QUALITY,
+            android::AudioResampler::DYN_LOW_QUALITY,
+            android::AudioResampler::DYN_MED_QUALITY,
+            android::AudioResampler::DYN_HIGH_QUALITY,
+    };
+
+    // in this test we assume a maximum transition band between 12kHz and 20kHz.
+    // there must be at least 60dB relative attenuation between stopband and passband.
+    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+        testStopbandDownconversion<float, float>(
+                8, 48000, 32000, 12000, 20000, kQualityArray[i]);
+    }
+
+    // in this test we assume a maximum transition band between 7kHz and 15kHz.
+    // there must be at least 60dB relative attenuation between stopband and passband.
+    // (the weird ratio triggers interpolative resampling)
+    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+        testStopbandDownconversion<float, float>(
+                8, 48000, 22101, 7000, 15000, kQualityArray[i]);
+    }
+}
+
diff --git a/services/audiopolicy/AudioPolicyManager.cpp b/services/audiopolicy/AudioPolicyManager.cpp
index cca1b34..8783ec9 100644
--- a/services/audiopolicy/AudioPolicyManager.cpp
+++ b/services/audiopolicy/AudioPolicyManager.cpp
@@ -3833,6 +3833,11 @@
         if (!deviceList.isEmpty()) {
             struct audio_patch patch;
             inputDesc->toAudioPortConfig(&patch.sinks[0]);
+            // AUDIO_SOURCE_HOTWORD is for internal use only:
+            // handled as AUDIO_SOURCE_VOICE_RECOGNITION by the audio HAL
+            if (patch.sinks[0].ext.mix.usecase.source == AUDIO_SOURCE_HOTWORD) {
+                patch.sinks[0].ext.mix.usecase.source = AUDIO_SOURCE_VOICE_RECOGNITION;
+            }
             patch.num_sinks = 1;
             //only one input device for now
             deviceList.itemAt(0)->toAudioPortConfig(&patch.sources[0]);
@@ -5316,7 +5321,9 @@
 const audio_format_t AudioPolicyManager::AudioPort::sPcmFormatCompareTable[] = {
         AUDIO_FORMAT_DEFAULT,
         AUDIO_FORMAT_PCM_16_BIT,
+        AUDIO_FORMAT_PCM_8_24_BIT,
         AUDIO_FORMAT_PCM_24_BIT_PACKED,
+        AUDIO_FORMAT_PCM_32_BIT,
 };
 
 int AudioPolicyManager::AudioPort::compareFormats(audio_format_t format1,
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 1642896..8075515 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -825,6 +825,7 @@
         }
         outputStreams.push(getZslStreamId());
     } else {
+        mZslProcessor->clearZslQueue();
         mZslProcessor->deleteStream();
     }
 
@@ -906,6 +907,13 @@
                 ALOGE("%s: Camera %d: Waiting to stop streaming failed: %s (%d)",
                         __FUNCTION__, mCameraId, strerror(-res), res);
             }
+            // Clean up recording stream
+            res = mStreamingProcessor->deleteRecordingStream();
+            if (res != OK) {
+                ALOGE("%s: Camera %d: Unable to delete recording stream before "
+                        "stop preview: %s (%d)",
+                        __FUNCTION__, mCameraId, strerror(-res), res);
+            }
             // no break
         case Parameters::WAITING_FOR_PREVIEW_WINDOW: {
             SharedParameters::Lock l(mParameters);
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 3004d3e..9d36bfa 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -1794,8 +1794,9 @@
                     return;
                 }
                 isPartialResult = (result->partial_result < mNumPartialResults);
-                request.partialResult.collectedResult.append(
-                    result->result);
+                if (isPartialResult) {
+                    request.partialResult.collectedResult.append(result->result);
+                }
             } else {
                 camera_metadata_ro_entry_t partialResultEntry;
                 res = find_camera_metadata_ro_entry(result->result,