Work to support switching transport streams mid-stream and signalling discontinuities to the decoder.

Change-Id: I7150e5e7342e1117c524856b204aadcb763e06ed
related-to-bug: 2368598
diff --git a/include/media/stagefright/MediaErrors.h b/include/media/stagefright/MediaErrors.h
index 73d0f77..e44122d 100644
--- a/include/media/stagefright/MediaErrors.h
+++ b/include/media/stagefright/MediaErrors.h
@@ -39,6 +39,7 @@
 
     // Not technically an error.
     INFO_FORMAT_CHANGED    = MEDIA_ERROR_BASE - 12,
+    INFO_DISCONTINUITY     = MEDIA_ERROR_BASE - 13,
 };
 
 }  // namespace android
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 9badf92..2bb7783 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -32,7 +32,8 @@
 struct OMXCodec : public MediaSource,
                   public MediaBufferObserver {
     enum CreationFlags {
-        kPreferSoftwareCodecs = 1,
+        kPreferSoftwareCodecs    = 1,
+        kIgnoreCodecSpecificData = 2
     };
     static sp<MediaSource> Create(
             const sp<IOMX> &omx,
@@ -248,7 +249,7 @@
 
     void dumpPortStatus(OMX_U32 portIndex);
 
-    status_t configureCodec(const sp<MetaData> &meta);
+    status_t configureCodec(const sp<MetaData> &meta, uint32_t flags);
 
     static uint32_t getComponentQuirks(
             const char *componentName, bool isEncoder);
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 3d0d108..8c17aab 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -561,6 +561,39 @@
     postBufferingEvent_l();
 }
 
+void AwesomePlayer::partial_reset_l() {
+    // Only reset the video renderer and shut down the video decoder.
+    // Then instantiate a new video decoder and resume video playback.
+
+    mVideoRenderer.clear();
+
+    if (mLastVideoBuffer) {
+        mLastVideoBuffer->release();
+        mLastVideoBuffer = NULL;
+    }
+
+    if (mVideoBuffer) {
+        mVideoBuffer->release();
+        mVideoBuffer = NULL;
+    }
+
+    {
+        mVideoSource->stop();
+
+        // The following hack is necessary to ensure that the OMX
+        // component is completely released by the time we may try
+        // to instantiate it again.
+        wp<MediaSource> tmp = mVideoSource;
+        mVideoSource.clear();
+        while (tmp.promote() != NULL) {
+            usleep(1000);
+        }
+        IPCThreadState::self()->flushCommands();
+    }
+
+    CHECK_EQ(OK, initVideoDecoder(OMXCodec::kIgnoreCodecSpecificData));
+}
+
 void AwesomePlayer::onStreamDone() {
     // Posted whenever any stream finishes playing.
 
@@ -570,7 +603,21 @@
     }
     mStreamDoneEventPending = false;
 
-    if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
+    if (mStreamDoneStatus == INFO_DISCONTINUITY) {
+        // This special status is returned because an http live stream's
+        // video stream switched to a different bandwidth at this point
+        // and future data may have been encoded using different parameters.
+        // This requires us to shutdown the video decoder and reinstantiate
+        // a fresh one.
+
+        LOGV("INFO_DISCONTINUITY");
+
+        CHECK(mVideoSource != NULL);
+
+        partial_reset_l();
+        postVideoEvent_l();
+        return;
+    } else if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
         LOGV("MEDIA_ERROR %d", mStreamDoneStatus);
 
         notifyListener_l(
@@ -939,8 +986,7 @@
     mVideoTrack = source;
 }
 
-status_t AwesomePlayer::initVideoDecoder() {
-    uint32_t flags = 0;
+status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
     mVideoSource = OMXCodec::Create(
             mClient.interface(), mVideoTrack->getFormat(),
             false, // createEncoder
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index e6cdac1..4648ad3 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -504,7 +504,7 @@
 
             observer->setCodec(codec);
 
-            err = codec->configureCodec(meta);
+            err = codec->configureCodec(meta, flags);
 
             if (err == OK) {
                 return codec;
@@ -517,93 +517,95 @@
     return NULL;
 }
 
-status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
-    uint32_t type;
-    const void *data;
-    size_t size;
-    if (meta->findData(kKeyESDS, &type, &data, &size)) {
-        ESDS esds((const char *)data, size);
-        CHECK_EQ(esds.InitCheck(), OK);
+status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) {
+    if (!(flags & kIgnoreCodecSpecificData)) {
+        uint32_t type;
+        const void *data;
+        size_t size;
+        if (meta->findData(kKeyESDS, &type, &data, &size)) {
+            ESDS esds((const char *)data, size);
+            CHECK_EQ(esds.InitCheck(), OK);
 
-        const void *codec_specific_data;
-        size_t codec_specific_data_size;
-        esds.getCodecSpecificInfo(
-                &codec_specific_data, &codec_specific_data_size);
+            const void *codec_specific_data;
+            size_t codec_specific_data_size;
+            esds.getCodecSpecificInfo(
+                    &codec_specific_data, &codec_specific_data_size);
 
-        addCodecSpecificData(
-                codec_specific_data, codec_specific_data_size);
-    } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
-        // Parse the AVCDecoderConfigurationRecord
+            addCodecSpecificData(
+                    codec_specific_data, codec_specific_data_size);
+        } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
+            // Parse the AVCDecoderConfigurationRecord
 
-        const uint8_t *ptr = (const uint8_t *)data;
+            const uint8_t *ptr = (const uint8_t *)data;
 
-        CHECK(size >= 7);
-        CHECK_EQ(ptr[0], 1);  // configurationVersion == 1
-        uint8_t profile = ptr[1];
-        uint8_t level = ptr[3];
+            CHECK(size >= 7);
+            CHECK_EQ(ptr[0], 1);  // configurationVersion == 1
+            uint8_t profile = ptr[1];
+            uint8_t level = ptr[3];
 
-        // There is decodable content out there that fails the following
-        // assertion, let's be lenient for now...
-        // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
+            // There is decodable content out there that fails the following
+            // assertion, let's be lenient for now...
+            // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
 
-        size_t lengthSize = 1 + (ptr[4] & 3);
+            size_t lengthSize = 1 + (ptr[4] & 3);
 
-        // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
-        // violates it...
-        // CHECK((ptr[5] >> 5) == 7);  // reserved
+            // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
+            // violates it...
+            // CHECK((ptr[5] >> 5) == 7);  // reserved
 
-        size_t numSeqParameterSets = ptr[5] & 31;
+            size_t numSeqParameterSets = ptr[5] & 31;
 
-        ptr += 6;
-        size -= 6;
+            ptr += 6;
+            size -= 6;
 
-        for (size_t i = 0; i < numSeqParameterSets; ++i) {
-            CHECK(size >= 2);
-            size_t length = U16_AT(ptr);
+            for (size_t i = 0; i < numSeqParameterSets; ++i) {
+                CHECK(size >= 2);
+                size_t length = U16_AT(ptr);
 
-            ptr += 2;
-            size -= 2;
+                ptr += 2;
+                size -= 2;
 
-            CHECK(size >= length);
+                CHECK(size >= length);
 
-            addCodecSpecificData(ptr, length);
+                addCodecSpecificData(ptr, length);
 
-            ptr += length;
-            size -= length;
-        }
+                ptr += length;
+                size -= length;
+            }
 
-        CHECK(size >= 1);
-        size_t numPictureParameterSets = *ptr;
-        ++ptr;
-        --size;
+            CHECK(size >= 1);
+            size_t numPictureParameterSets = *ptr;
+            ++ptr;
+            --size;
 
-        for (size_t i = 0; i < numPictureParameterSets; ++i) {
-            CHECK(size >= 2);
-            size_t length = U16_AT(ptr);
+            for (size_t i = 0; i < numPictureParameterSets; ++i) {
+                CHECK(size >= 2);
+                size_t length = U16_AT(ptr);
 
-            ptr += 2;
-            size -= 2;
+                ptr += 2;
+                size -= 2;
 
-            CHECK(size >= length);
+                CHECK(size >= length);
 
-            addCodecSpecificData(ptr, length);
+                addCodecSpecificData(ptr, length);
 
-            ptr += length;
-            size -= length;
-        }
+                ptr += length;
+                size -= length;
+            }
 
-        CODEC_LOGV(
-                "AVC profile = %d (%s), level = %d",
-                (int)profile, AVCProfileToString(profile), level);
+            CODEC_LOGV(
+                    "AVC profile = %d (%s), level = %d",
+                    (int)profile, AVCProfileToString(profile), level);
 
-        if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
-            && (profile != kAVCProfileBaseline || level > 30)) {
-            // This stream exceeds the decoder's capabilities. The decoder
-            // does not handle this gracefully and would clobber the heap
-            // and wreak havoc instead...
+            if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
+                && (profile != kAVCProfileBaseline || level > 30)) {
+                // This stream exceeds the decoder's capabilities. The decoder
+                // does not handle this gracefully and would clobber the heap
+                // and wreak havoc instead...
 
-            LOGE("Profile and/or level exceed the decoder's capabilities.");
-            return ERROR_UNSUPPORTED;
+                LOGE("Profile and/or level exceed the decoder's capabilities.");
+                return ERROR_UNSUPPORTED;
+            }
         }
     }
 
diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
index 9103927..943a0fc 100644
--- a/media/libstagefright/httplive/LiveSource.cpp
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
 #define LOG_TAG "LiveSource"
 #include <utils/Log.h>
 
@@ -22,18 +23,21 @@
 #include "include/NuHTTPDataSource.h"
 
 #include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/FileSource.h>
 #include <media/stagefright/MediaDebug.h>
 
 namespace android {
 
 LiveSource::LiveSource(const char *url)
-    : mURL(url),
+    : mMasterURL(url),
       mInitCheck(NO_INIT),
       mPlaylistIndex(0),
       mLastFetchTimeUs(-1),
       mSource(new NuHTTPDataSource),
       mSourceSize(0),
-      mOffsetBias(0) {
+      mOffsetBias(0),
+      mSignalDiscontinuity(false),
+      mPrevBandwidthIndex(-1) {
     if (switchToNext()) {
         mInitCheck = OK;
     }
@@ -46,21 +50,129 @@
     return mInitCheck;
 }
 
-bool LiveSource::loadPlaylist() {
+// static
+int LiveSource::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b) {
+    if (a->mBandwidth < b->mBandwidth) {
+        return -1;
+    } else if (a->mBandwidth == b->mBandwidth) {
+        return 0;
+    }
+
+    return 1;
+}
+
+static double uniformRand() {
+    return (double)rand() / RAND_MAX;
+}
+
+bool LiveSource::loadPlaylist(bool fetchMaster) {
+    mSignalDiscontinuity = false;
+
     mPlaylist.clear();
     mPlaylistIndex = 0;
 
-    sp<ABuffer> buffer;
-    status_t err = fetchM3U(mURL.c_str(), &buffer);
+    if (fetchMaster) {
+        mPrevBandwidthIndex = -1;
 
-    if (err != OK) {
-        return false;
+        sp<ABuffer> buffer;
+        status_t err = fetchM3U(mMasterURL.c_str(), &buffer);
+
+        if (err != OK) {
+            return false;
+        }
+
+        mPlaylist = new M3UParser(
+                mMasterURL.c_str(), buffer->data(), buffer->size());
+
+        if (mPlaylist->initCheck() != OK) {
+            return false;
+        }
+
+        if (mPlaylist->isVariantPlaylist()) {
+            for (size_t i = 0; i < mPlaylist->size(); ++i) {
+                BandwidthItem item;
+
+                sp<AMessage> meta;
+                mPlaylist->itemAt(i, &item.mURI, &meta);
+
+                unsigned long bandwidth;
+                CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
+
+                mBandwidthItems.push(item);
+            }
+            mPlaylist.clear();
+
+            // fall through
+            if (mBandwidthItems.size() == 0) {
+                return false;
+            }
+
+            mBandwidthItems.sort(SortByBandwidth);
+
+            for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
+                const BandwidthItem &item = mBandwidthItems.itemAt(i);
+                LOGV("item #%d: %s", i, item.mURI.c_str());
+            }
+        }
     }
 
-    mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
+    if (mBandwidthItems.size() > 0) {
+#if 0
+        // Change bandwidth at random()
+        size_t index = uniformRand() * mBandwidthItems.size();
+#elif 0
+        // There's a 50% chance to stay on the current bandwidth and
+        // a 50% chance to switch to the next higher bandwidth (wrapping around
+        // to lowest)
+        size_t index;
+        if (uniformRand() < 0.5) {
+            index = mPrevBandwidthIndex < 0 ? 0 : (size_t)mPrevBandwidthIndex;
+        } else {
+            if (mPrevBandwidthIndex < 0) {
+                index = 0;
+            } else {
+                index = mPrevBandwidthIndex + 1;
+                if (index == mBandwidthItems.size()) {
+                    index = 0;
+                }
+            }
+        }
+#else
+        // Stay on the lowest bandwidth available.
+        size_t index = 0;  // Lowest bandwidth stream
+#endif
 
-    if (mPlaylist->initCheck() != OK) {
-        return false;
+        mURL = mBandwidthItems.editItemAt(index).mURI;
+
+        if (mPrevBandwidthIndex >= 0 && (size_t)mPrevBandwidthIndex != index) {
+            // If we switched streams because of bandwidth changes,
+            // we'll signal this discontinuity by inserting a
+            // special transport stream packet into the stream.
+            mSignalDiscontinuity = true;
+        }
+
+        mPrevBandwidthIndex = index;
+    } else {
+        mURL = mMasterURL;
+    }
+
+    if (mPlaylist == NULL) {
+        sp<ABuffer> buffer;
+        status_t err = fetchM3U(mURL.c_str(), &buffer);
+
+        if (err != OK) {
+            return false;
+        }
+
+        mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
+
+        if (mPlaylist->initCheck() != OK) {
+            return false;
+        }
+
+        if (mPlaylist->isVariantPlaylist()) {
+            return false;
+        }
     }
 
     if (!mPlaylist->meta()->findInt32(
@@ -79,6 +191,8 @@
 }
 
 bool LiveSource::switchToNext() {
+    mSignalDiscontinuity = false;
+
     mOffsetBias += mSourceSize;
     mSourceSize = 0;
 
@@ -87,7 +201,7 @@
         int32_t nextSequenceNumber =
             mPlaylistIndex + mFirstItemSequenceNumber;
 
-        if (!loadPlaylist()) {
+        if (!loadPlaylist(mLastFetchTimeUs < 0)) {
             LOGE("failed to reload playlist");
             return false;
         }
@@ -111,35 +225,62 @@
     }
 
     AString uri;
-    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri));
-    LOGI("switching to %s", uri.c_str());
+    sp<AMessage> itemMeta;
+    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
+    LOGV("switching to %s", uri.c_str());
 
     if (mSource->connect(uri.c_str()) != OK
             || mSource->getSize(&mSourceSize) != OK) {
         return false;
     }
 
+    int32_t val;
+    if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
+        mSignalDiscontinuity = true;
+    }
+
     mPlaylistIndex++;
     return true;
 }
 
+static const ssize_t kHeaderSize = 188;
+
 ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
     CHECK(offset >= mOffsetBias);
     offset -= mOffsetBias;
 
-    if (offset >= mSourceSize) {
-        CHECK_EQ(offset, mSourceSize);
+    off_t delta = mSignalDiscontinuity ? kHeaderSize : 0;
 
-        offset -= mSourceSize;
+    if (offset >= mSourceSize + delta) {
+        CHECK_EQ(offset, mSourceSize + delta);
+
+        offset -= mSourceSize + delta;
         if (!switchToNext()) {
             return ERROR_END_OF_STREAM;
         }
+
+        if (mSignalDiscontinuity) {
+            LOGV("switchToNext changed streams");
+        } else {
+            LOGV("switchToNext stayed within the same stream");
+        }
+
+        mOffsetBias += delta;
+
+        delta = mSignalDiscontinuity ? kHeaderSize : 0;
+    }
+
+    if (offset < delta) {
+        size_t avail = delta - offset;
+        memset(data, 0, avail);
+        return avail;
     }
 
     size_t numRead = 0;
     while (numRead < size) {
         ssize_t n = mSource->readAt(
-                offset + numRead, (uint8_t *)data + numRead, size - numRead);
+                offset + numRead - delta,
+                (uint8_t *)data + numRead, size - numRead);
 
         if (n <= 0) {
             break;
@@ -154,14 +295,24 @@
 status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
     *out = NULL;
 
-    status_t err = mSource->connect(url);
+    sp<DataSource> source;
 
-    if (err != OK) {
-        return err;
+    if (!strncasecmp(url, "file://", 7)) {
+        source = new FileSource(url + 7);
+    } else {
+        CHECK(!strncasecmp(url, "http://", 7));
+
+        status_t err = mSource->connect(url);
+
+        if (err != OK) {
+            return err;
+        }
+
+        source = mSource;
     }
 
     off_t size;
-    err = mSource->getSize(&size);
+    status_t err = source->getSize(&size);
 
     if (err != OK) {
         return err;
@@ -170,7 +321,7 @@
     sp<ABuffer> buffer = new ABuffer(size);
     size_t offset = 0;
     while (offset < (size_t)size) {
-        ssize_t n = mSource->readAt(
+        ssize_t n = source->readAt(
                 offset, buffer->data() + offset, size - offset);
 
         if (n <= 0) {
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index edd8648..0d7daa9 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -74,7 +74,8 @@
 static bool MakeURL(const char *baseURL, const char *url, AString *out) {
     out->clear();
 
-    if (strncasecmp("http://", baseURL, 7)) {
+    if (strncasecmp("http://", baseURL, 7)
+            && strncasecmp("file://", baseURL, 7)) {
         // Base URL must be absolute
         return false;
     }
@@ -128,7 +129,12 @@
             line.setTo(&data[offset], offsetLF - offset);
         }
 
-        LOGI("#%s#", line.c_str());
+        // LOGI("#%s#", line.c_str());
+
+        if (line.empty()) {
+            offset = offsetLF + 1;
+            continue;
+        }
 
         if (lineNo == 0 && line == "#EXTM3U") {
             mIsExtM3U = true;
@@ -152,11 +158,20 @@
                     return ERROR_MALFORMED;
                 }
                 err = parseMetaData(line, &itemMeta, "duration");
+            } else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
+                if (mIsVariantPlaylist) {
+                    return ERROR_MALFORMED;
+                }
+                if (itemMeta == NULL) {
+                    itemMeta = new AMessage;
+                }
+                itemMeta->setInt32("discontinuity", true);
             } else if (line.startsWith("#EXT-X-STREAM-INF")) {
                 if (mMeta != NULL) {
                     return ERROR_MALFORMED;
                 }
                 mIsVariantPlaylist = true;
+                err = parseStreamInf(line, &itemMeta);
             }
 
             if (err != OK) {
@@ -215,6 +230,61 @@
 }
 
 // static
+status_t M3UParser::parseStreamInf(
+        const AString &line, sp<AMessage> *meta) {
+    ssize_t colonPos = line.find(":");
+
+    if (colonPos < 0) {
+        return ERROR_MALFORMED;
+    }
+
+    size_t offset = colonPos + 1;
+
+    while (offset < line.size()) {
+        ssize_t end = line.find(",", offset);
+        if (end < 0) {
+            end = line.size();
+        }
+
+        AString attr(line, offset, end - offset);
+        attr.trim();
+
+        offset = end + 1;
+
+        ssize_t equalPos = attr.find("=");
+        if (equalPos < 0) {
+            continue;
+        }
+
+        AString key(attr, 0, equalPos);
+        key.trim();
+
+        AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
+        val.trim();
+
+        LOGV("key=%s value=%s", key.c_str(), val.c_str());
+
+        if (!strcasecmp("bandwidth", key.c_str())) {
+            const char *s = val.c_str();
+            char *end;
+            unsigned long x = strtoul(s, &end, 10);
+
+            if (end == s || *end != '\0') {
+                // malformed
+                continue;
+            }
+
+            if (meta->get() == NULL) {
+                *meta = new AMessage;
+            }
+            (*meta)->setInt32("bandwidth", x);
+        }
+    }
+
+    return OK;
+}
+
+// static
 status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
     char *end;
     long lval = strtol(s, &end, 10);
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 40ea37d..bbf482d 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -220,6 +220,7 @@
     status_t setDataSource_l(const sp<DataSource> &dataSource);
     status_t setDataSource_l(const sp<MediaExtractor> &extractor);
     void reset_l();
+    void partial_reset_l();
     status_t seekTo_l(int64_t timeUs);
     status_t pause_l(bool at_eos = false);
     void initRenderer_l();
@@ -231,7 +232,7 @@
     status_t initAudioDecoder();
 
     void setVideoSource(sp<MediaSource> source);
-    status_t initVideoDecoder();
+    status_t initVideoDecoder(uint32_t flags = 0);
 
     void onStreamDone();
 
diff --git a/media/libstagefright/include/LiveSource.h b/media/libstagefright/include/LiveSource.h
index c55508c..5e89581 100644
--- a/media/libstagefright/include/LiveSource.h
+++ b/media/libstagefright/include/LiveSource.h
@@ -44,6 +44,13 @@
     virtual ~LiveSource();
 
 private:
+    struct BandwidthItem {
+        AString mURI;
+        unsigned long mBandwidth;
+    };
+    Vector<BandwidthItem> mBandwidthItems;
+
+    AString mMasterURL;
     AString mURL;
     status_t mInitCheck;
 
@@ -56,10 +63,15 @@
     off_t mSourceSize;
     off_t mOffsetBias;
 
+    bool mSignalDiscontinuity;
+    ssize_t mPrevBandwidthIndex;
+
     status_t fetchM3U(const char *url, sp<ABuffer> *buffer);
 
+    static int SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b);
+
     bool switchToNext();
-    bool loadPlaylist();
+    bool loadPlaylist(bool fetchMaster);
 
     DISALLOW_EVIL_CONSTRUCTORS(LiveSource);
 };
diff --git a/media/libstagefright/include/M3UParser.h b/media/libstagefright/include/M3UParser.h
index 36553de..69199ab 100644
--- a/media/libstagefright/include/M3UParser.h
+++ b/media/libstagefright/include/M3UParser.h
@@ -61,6 +61,9 @@
     static status_t parseMetaData(
             const AString &line, sp<AMessage> *meta, const char *key);
 
+    static status_t parseStreamInf(
+            const AString &line, sp<AMessage> *meta);
+
     static status_t ParseInt32(const char *s, int32_t *x);
 
     DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index bcaab9f..7c9b83a 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -49,6 +49,8 @@
             unsigned pid, unsigned payload_unit_start_indicator,
             ABitReader *br);
 
+    void signalDiscontinuity();
+
     sp<MediaSource> getSource(SourceType type);
 
 private:
@@ -67,6 +69,8 @@
             unsigned payload_unit_start_indicator,
             ABitReader *br);
 
+    void signalDiscontinuity();
+
     sp<MediaSource> getSource(SourceType type);
 
 protected:
@@ -124,6 +128,12 @@
     return true;
 }
 
+void ATSParser::Program::signalDiscontinuity() {
+    for (size_t i = 0; i < mStreams.size(); ++i) {
+        mStreams.editValueAt(i)->signalDiscontinuity();
+    }
+}
+
 void ATSParser::Program::parseProgramMap(ABitReader *br) {
     unsigned table_id = br->getBits(8);
     LOGV("  table_id = %u", table_id);
@@ -271,6 +281,19 @@
     mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);
 }
 
+void ATSParser::Stream::signalDiscontinuity() {
+    LOGV("Stream discontinuity");
+    mPayloadStarted = false;
+    mBuffer->setRange(0, 0);
+
+    mQueue.clear();
+
+    if (mStreamType == 0x1b && mSource != NULL) {
+        // Don't signal discontinuities on audio streams.
+        mSource->queueDiscontinuity();
+    }
+}
+
 void ATSParser::Stream::parsePES(ABitReader *br) {
     unsigned packet_startcode_prefix = br->getBits(24);
 
@@ -459,7 +482,10 @@
                 mSource = new AnotherPacketSource(meta);
                 mSource->queueAccessUnit(accessUnit);
             }
-        } else {
+        } else if (mQueue.getFormat() != NULL) {
+            // After a discontinuity we invalidate the queue's format
+            // and won't enqueue any access units to the source until
+            // the queue has reestablished the new format.
             mSource->queueAccessUnit(accessUnit);
         }
     }
@@ -489,6 +515,12 @@
     parseTS(&br);
 }
 
+void ATSParser::signalDiscontinuity() {
+    for (size_t i = 0; i < mPrograms.size(); ++i) {
+        mPrograms.editItemAt(i)->signalDiscontinuity();
+    }
+}
+
 void ATSParser::parseProgramAssociationTable(ABitReader *br) {
     unsigned table_id = br->getBits(8);
     LOGV("  table_id = %u", table_id);
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 1e22e7b..9ec6d7b 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -33,6 +33,7 @@
     ATSParser();
 
     void feedTSPacket(const void *data, size_t size);
+    void signalDiscontinuity();
 
     enum SourceType {
         AVC_VIDEO,
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 3d51177..3f76820 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -59,21 +59,26 @@
 
     if (!mBuffers.empty()) {
         const sp<ABuffer> buffer = *mBuffers.begin();
-
-        uint64_t timeUs;
-        CHECK(buffer->meta()->findInt64(
-                    "time", (int64_t *)&timeUs));
-
-        MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
-        mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
-
-        // hexdump(buffer->data(), buffer->size());
-
-        memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
-        *out = mediaBuffer;
-
         mBuffers.erase(mBuffers.begin());
-        return OK;
+
+        int32_t discontinuity;
+        if (buffer->meta()->findInt32("discontinuity", &discontinuity)
+                && discontinuity) {
+            return INFO_DISCONTINUITY;
+        } else {
+            uint64_t timeUs;
+            CHECK(buffer->meta()->findInt64(
+                        "time", (int64_t *)&timeUs));
+
+            MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
+            mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
+
+            // hexdump(buffer->data(), buffer->size());
+
+            memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
+            *out = mediaBuffer;
+            return OK;
+        }
     }
 
     return mEOSResult;
@@ -91,6 +96,15 @@
     mCondition.signal();
 }
 
+void AnotherPacketSource::queueDiscontinuity() {
+    sp<ABuffer> buffer = new ABuffer(0);
+    buffer->meta()->setInt32("discontinuity", true);
+
+    Mutex::Autolock autoLock(mLock);
+    mBuffers.push_back(buffer);
+    mCondition.signal();
+}
+
 void AnotherPacketSource::signalEOS(status_t result) {
     CHECK(result != OK);
 
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index ce83d21..6b43c4e 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -40,6 +40,7 @@
     bool hasBufferAvailable(status_t *finalResult);
 
     void queueAccessUnit(const sp<ABuffer> &buffer);
+    void queueDiscontinuity();
     void signalEOS(status_t result);
 
 protected:
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index d87040b..4a75ee4 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -115,6 +115,11 @@
     return OK;
 }
 
+void ElementaryStreamQueue::clear() {
+    mBuffer->setRange(0, 0);
+    mFormat.clear();
+}
+
 status_t ElementaryStreamQueue::appendData(
         const void *data, size_t size, int64_t timeUs) {
     if (mBuffer == NULL || mBuffer->size() == 0) {
@@ -147,7 +152,7 @@
     if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
         neededSize = (neededSize + 65535) & ~65535;
 
-        LOGI("resizing buffer to size %d", neededSize);
+        LOGV("resizing buffer to size %d", neededSize);
 
         sp<ABuffer> buffer = new ABuffer(neededSize);
         if (mBuffer != NULL) {
@@ -498,6 +503,8 @@
     meta->setInt32(kKeyWidth, width);
     meta->setInt32(kKeyHeight, height);
 
+    LOGI("found AVC codec config (%d x %d)", width, height);
+
     return meta;
 }
 
diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h
index d2e87f2..246c390 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.h
+++ b/media/libstagefright/mpeg2ts/ESQueue.h
@@ -35,6 +35,7 @@
     ElementaryStreamQueue(Mode mode);
 
     status_t appendData(const void *data, size_t size, int64_t timeUs);
+    void clear();
 
     sp<ABuffer> dequeueAccessUnit();
 
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index c5257bb..0d96bd1 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -165,18 +165,26 @@
     LOGI("haveAudio=%d, haveVideo=%d", haveAudio, haveVideo);
 }
 
+static bool isDiscontinuity(const uint8_t *data, ssize_t size) {
+    return size == 188 && data[0] == 0x00;
+}
+
 status_t MPEG2TSExtractor::feedMore() {
     Mutex::Autolock autoLock(mLock);
 
     uint8_t packet[kTSPacketSize];
     ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);
 
-    if (n < (ssize_t)kTSPacketSize) {
+    if (isDiscontinuity(packet, n)) {
+        LOGI("XXX discontinuity detected");
+        mParser->signalDiscontinuity();
+    } else if (n < (ssize_t)kTSPacketSize) {
         return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;
+    } else {
+        mParser->feedTSPacket(packet, kTSPacketSize);
     }
 
-    mOffset += kTSPacketSize;
-    mParser->feedTSPacket(packet, kTSPacketSize);
+    mOffset += n;
 
     return OK;
 }