Merge "MediaCodec: ignore error when linkToDeath to batterystats"
diff --git a/include/media/stagefright/MediaSync.h b/include/media/stagefright/MediaSync.h
index 8bb8c7f..8ad74a4 100644
--- a/include/media/stagefright/MediaSync.h
+++ b/include/media/stagefright/MediaSync.h
@@ -113,6 +113,9 @@
     // MediaClock::getMediaTime() and MediaClock::getRealTimeFor().
     sp<const MediaClock> getMediaClock();
 
+    // Get the play time for pending audio frames in audio sink.
+    status_t getPlayTimeForPendingAudioFrames(int64_t *outTimeUs);
+
 protected:
     virtual void onMessageReceived(const sp<AMessage> &msg);
 
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 8bdebf6..ca80123 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -176,6 +176,9 @@
     kKeyTrackIsDefault    = 'dflt', // bool (int32_t)
     // Similar to MediaFormat.KEY_IS_FORCED_SUBTITLE but pertains to av tracks as well.
     kKeyTrackIsForced     = 'frcd', // bool (int32_t)
+
+    // H264 supplemental enhancement information offsets/sizes
+    kKeySEI               = 'sei ', // raw data
 };
 
 enum {
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index fb21c73..8a0b060d 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -953,6 +953,7 @@
     if (mAudioTimeScale > 0) {
         format->setInt32("time-scale", mAudioTimeScale);
     }
+    format->setInt32("priority", 0 /* realtime */);
 
     sp<MediaSource> audioEncoder =
             MediaCodecSource::Create(mLooper, format, audioSource);
@@ -1543,6 +1544,11 @@
         format->setInt32("level", mVideoEncoderLevel);
     }
 
+    format->setInt32("priority", 0 /* realtime */);
+    if (mCaptureTimeLapse) {
+        format->setFloat("operating-rate", mCaptureFps);
+    }
+
     uint32_t flags = 0;
     if (mIsMetaDataStoredInVideoBuffers) {
         flags |= MediaCodecSource::FLAG_USE_METADATA_INPUT;
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index b7a88e7..7eaa0e0 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -1400,6 +1400,14 @@
         meta->setInt32("trackIndex", mSubtitleTrack.mIndex);
     }
 
+    uint32_t dataType; // unused
+    const void *seiData;
+    size_t seiLength;
+    if (mb->meta_data()->findData(kKeySEI, &dataType, &seiData, &seiLength)) {
+        sp<ABuffer> sei = ABuffer::CreateAsCopy(seiData, seiLength);;
+        meta->setBuffer("sei", sei);
+    }
+
     if (actualTimeUs) {
         *actualTimeUs = timeUs;
     }
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 65e80c3..acc9ef5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -658,7 +658,7 @@
 #if 0
     int64_t mediaTimeUs;
     CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs));
-    ALOGV("[%s] feeding input buffer at media time %" PRId64,
+    ALOGV("[%s] feeding input buffer at media time %.3f",
          mIsAudio ? "audio" : "video",
          mediaTimeUs / 1E6);
 #endif
diff --git a/media/libstagefright/MediaSync.cpp b/media/libstagefright/MediaSync.cpp
index ec956c4..8030a36 100644
--- a/media/libstagefright/MediaSync.cpp
+++ b/media/libstagefright/MediaSync.cpp
@@ -179,6 +179,42 @@
     return mMediaClock;
 }
 
+status_t MediaSync::getPlayTimeForPendingAudioFrames(int64_t *outTimeUs) {
+    Mutex::Autolock lock(mMutex);
+    // User should check the playback rate if it doesn't want to receive a
+    // huge number for play time.
+    if (mPlaybackRate == 0.0f) {
+        *outTimeUs = INT64_MAX;
+        return OK;
+    }
+
+    uint32_t numFramesPlayed = 0;
+    if (mAudioTrack != NULL) {
+        status_t res = mAudioTrack->getPosition(&numFramesPlayed);
+        if (res != OK) {
+            return res;
+        }
+    }
+
+    int64_t numPendingFrames = mNumFramesWritten - numFramesPlayed;
+    if (numPendingFrames < 0) {
+        numPendingFrames = 0;
+        ALOGW("getPlayTimeForPendingAudioFrames: pending frame count is negative.");
+    }
+    double timeUs = numPendingFrames * 1000000.0
+            / (mNativeSampleRateInHz * (double)mPlaybackRate);
+    if (timeUs > (double)INT64_MAX) {
+        // Overflow.
+        *outTimeUs = INT64_MAX;
+        ALOGW("getPlayTimeForPendingAudioFrames: play time for pending audio frames "
+              "is too high, possibly due to super low playback rate(%f)", mPlaybackRate);
+    } else {
+        *outTimeUs = (int64_t)timeUs;
+    }
+
+    return OK;
+}
+
 status_t MediaSync::updateQueuedAudioData(
         size_t sizeInBytes, int64_t presentationTimeUs) {
     if (sizeInBytes == 0) {
@@ -195,13 +231,14 @@
     int64_t numFrames = sizeInBytes / mAudioTrack->frameSize();
     int64_t maxMediaTimeUs = presentationTimeUs
             + getDurationIfPlayedAtNativeSampleRate_l(numFrames);
-    mNumFramesWritten += numFrames;
 
     int64_t nowUs = ALooper::GetNowUs();
-    int64_t nowMediaUs = maxMediaTimeUs
+    int64_t nowMediaUs = presentationTimeUs
             - getDurationIfPlayedAtNativeSampleRate_l(mNumFramesWritten)
             + getPlayedOutAudioDurationMedia_l(nowUs);
 
+    mNumFramesWritten += numFrames;
+
     int64_t oldRealTime = -1;
     if (mNextBufferItemMediaUs != -1) {
         oldRealTime = getRealTime(mNextBufferItemMediaUs, nowUs);
@@ -212,12 +249,13 @@
 
     if (oldRealTime != -1) {
         int64_t newRealTime = getRealTime(mNextBufferItemMediaUs, nowUs);
-        if (newRealTime < oldRealTime) {
-            mNextBufferItemMediaUs = -1;
-            onDrainVideo_l();
+        if (newRealTime >= oldRealTime) {
+            return OK;
         }
     }
 
+    mNextBufferItemMediaUs = -1;
+    onDrainVideo_l();
     return OK;
 }
 
@@ -316,12 +354,12 @@
         return;
     }
 
-    int64_t nowUs = ALooper::GetNowUs();
-
     while (!mBufferItems.empty()) {
+        int64_t nowUs = ALooper::GetNowUs();
         BufferItem *bufferItem = &*mBufferItems.begin();
         int64_t itemMediaUs = bufferItem->mTimestamp / 1000;
         int64_t itemRealUs = getRealTime(itemMediaUs, nowUs);
+
         if (itemRealUs <= nowUs) {
             if (mHasAudio) {
                 if (nowUs - itemRealUs <= kMaxAllowedVideoLateTimeUs) {
@@ -341,15 +379,13 @@
             }
 
             mBufferItems.erase(mBufferItems.begin());
-
-            if (mBufferItems.empty()) {
-                mNextBufferItemMediaUs = -1;
-            }
+            mNextBufferItemMediaUs = -1;
         } else {
             if (mNextBufferItemMediaUs == -1
-                    || mNextBufferItemMediaUs != itemMediaUs) {
+                    || mNextBufferItemMediaUs > itemMediaUs) {
                 sp<AMessage> msg = new AMessage(kWhatDrainVideo, this);
                 msg->post(itemRealUs - nowUs);
+                mNextBufferItemMediaUs = itemMediaUs;
             }
             break;
         }
@@ -395,7 +431,9 @@
     }
 
     mBufferItems.push_back(bufferItem);
-    onDrainVideo_l();
+    if (mBufferItems.size() == 1) {
+        onDrainVideo_l();
+    }
 }
 
 void MediaSync::renderOneBufferItem_l( const BufferItem &bufferItem) {
@@ -499,6 +537,20 @@
         case kWhatDrainVideo:
         {
             Mutex::Autolock lock(mMutex);
+            if (mNextBufferItemMediaUs != -1) {
+                int64_t nowUs = ALooper::GetNowUs();
+                int64_t itemRealUs = getRealTime(mNextBufferItemMediaUs, nowUs);
+
+                // The message could arrive earlier than expected due to
+                // various reasons, e.g., media clock has been changed because
+                // of new anchor time or playback rate. In such cases, the
+                // message needs to be re-posted.
+                if (itemRealUs > nowUs) {
+                    msg->post(itemRealUs - nowUs);
+                    break;
+                }
+            }
+
             onDrainVideo_l();
             break;
         }
diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk
index 2639deb..fc85835 100644
--- a/media/libstagefright/httplive/Android.mk
+++ b/media/libstagefright/httplive/Android.mk
@@ -3,6 +3,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=               \
+        HTTPDownloader.cpp      \
         LiveDataSource.cpp      \
         LiveSession.cpp         \
         M3UParser.cpp           \
diff --git a/media/libstagefright/httplive/HTTPDownloader.cpp b/media/libstagefright/httplive/HTTPDownloader.cpp
new file mode 100644
index 0000000..3b44bae
--- /dev/null
+++ b/media/libstagefright/httplive/HTTPDownloader.cpp
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "HTTPDownloader"
+#include <utils/Log.h>
+
+#include "HTTPDownloader.h"
+#include "M3UParser.h"
+
+#include <media/IMediaHTTPConnection.h>
+#include <media/IMediaHTTPService.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaHTTP.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/FileSource.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+HTTPDownloader::HTTPDownloader(
+        const sp<IMediaHTTPService> &httpService,
+        const KeyedVector<String8, String8> &headers) :
+    mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())),
+    mExtraHeaders(headers),
+    mDisconnecting(false) {
+}
+
+void HTTPDownloader::reconnect() {
+    AutoMutex _l(mLock);
+    mDisconnecting = false;
+}
+
+void HTTPDownloader::disconnect() {
+    {
+        AutoMutex _l(mLock);
+        mDisconnecting = true;
+    }
+    mHTTPDataSource->disconnect();
+}
+
+bool HTTPDownloader::isDisconnecting() {
+    AutoMutex _l(mLock);
+    return mDisconnecting;
+}
+
+/*
+ * Illustration of parameters:
+ *
+ * 0      `range_offset`
+ * +------------+-------------------------------------------------------+--+--+
+ * |            |                                 | next block to fetch |  |  |
+ * |            | `source` handle => `out` buffer |                     |  |  |
+ * | `url` file |<--------- buffer size --------->|<--- `block_size` -->|  |  |
+ * |            |<----------- `range_length` / buffer capacity ----------->|  |
+ * |<------------------------------ file_size ------------------------------->|
+ *
+ * Special parameter values:
+ * - range_length == -1 means entire file
+ * - block_size == 0 means entire range
+ *
+ */
+ssize_t HTTPDownloader::fetchBlock(
+        const char *url, sp<ABuffer> *out,
+        int64_t range_offset, int64_t range_length,
+        uint32_t block_size, /* download block size */
+        String8 *actualUrl,
+        bool reconnect /* force connect HTTP when resuing source */) {
+    if (isDisconnecting()) {
+        return ERROR_NOT_CONNECTED;
+    }
+
+    off64_t size;
+
+    if (reconnect) {
+        if (!strncasecmp(url, "file://", 7)) {
+            mDataSource = new FileSource(url + 7);
+        } else if (strncasecmp(url, "http://", 7)
+                && strncasecmp(url, "https://", 8)) {
+            return ERROR_UNSUPPORTED;
+        } else {
+            KeyedVector<String8, String8> headers = mExtraHeaders;
+            if (range_offset > 0 || range_length >= 0) {
+                headers.add(
+                        String8("Range"),
+                        String8(
+                            AStringPrintf(
+                                "bytes=%lld-%s",
+                                range_offset,
+                                range_length < 0
+                                    ? "" : AStringPrintf("%lld",
+                                            range_offset + range_length - 1).c_str()).c_str()));
+            }
+
+            status_t err = mHTTPDataSource->connect(url, &headers);
+
+            if (isDisconnecting()) {
+                return ERROR_NOT_CONNECTED;
+            }
+
+            if (err != OK) {
+                return err;
+            }
+
+            mDataSource = mHTTPDataSource;
+        }
+    }
+
+    status_t getSizeErr = mDataSource->getSize(&size);
+
+    if (isDisconnecting()) {
+        return ERROR_NOT_CONNECTED;
+    }
+
+    if (getSizeErr != OK) {
+        size = 65536;
+    }
+
+    sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
+    if (*out == NULL) {
+        buffer->setRange(0, 0);
+    }
+
+    ssize_t bytesRead = 0;
+    // adjust range_length if only reading partial block
+    if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
+        range_length = buffer->size() + block_size;
+    }
+    for (;;) {
+        // Only resize when we don't know the size.
+        size_t bufferRemaining = buffer->capacity() - buffer->size();
+        if (bufferRemaining == 0 && getSizeErr != OK) {
+            size_t bufferIncrement = buffer->size() / 2;
+            if (bufferIncrement < 32768) {
+                bufferIncrement = 32768;
+            }
+            bufferRemaining = bufferIncrement;
+
+            ALOGV("increasing download buffer to %zu bytes",
+                 buffer->size() + bufferRemaining);
+
+            sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
+            memcpy(copy->data(), buffer->data(), buffer->size());
+            copy->setRange(0, buffer->size());
+
+            buffer = copy;
+        }
+
+        size_t maxBytesToRead = bufferRemaining;
+        if (range_length >= 0) {
+            int64_t bytesLeftInRange = range_length - buffer->size();
+            if (bytesLeftInRange < (int64_t)maxBytesToRead) {
+                maxBytesToRead = bytesLeftInRange;
+
+                if (bytesLeftInRange == 0) {
+                    break;
+                }
+            }
+        }
+
+        // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
+        // to help us break out of the loop.
+        ssize_t n = mDataSource->readAt(
+                buffer->size(), buffer->data() + buffer->size(),
+                maxBytesToRead);
+
+        if (isDisconnecting()) {
+            return ERROR_NOT_CONNECTED;
+        }
+
+        if (n < 0) {
+            return n;
+        }
+
+        if (n == 0) {
+            break;
+        }
+
+        buffer->setRange(0, buffer->size() + (size_t)n);
+        bytesRead += n;
+    }
+
+    *out = buffer;
+    if (actualUrl != NULL) {
+        *actualUrl = mDataSource->getUri();
+        if (actualUrl->isEmpty()) {
+            *actualUrl = url;
+        }
+    }
+
+    return bytesRead;
+}
+
+ssize_t HTTPDownloader::fetchFile(
+        const char *url, sp<ABuffer> *out, String8 *actualUrl) {
+    ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
+
+    // close off the connection after use
+    mHTTPDataSource->disconnect();
+
+    return err;
+}
+
+sp<M3UParser> HTTPDownloader::fetchPlaylist(
+        const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
+    ALOGV("fetchPlaylist '%s'", url);
+
+    *unchanged = false;
+
+    sp<ABuffer> buffer;
+    String8 actualUrl;
+    ssize_t err = fetchFile(url, &buffer, &actualUrl);
+
+    // close off the connection after use
+    mHTTPDataSource->disconnect();
+
+    if (err <= 0) {
+        return NULL;
+    }
+
+    // MD5 functionality is not available on the simulator, treat all
+    // playlists as changed.
+
+#if defined(HAVE_ANDROID_OS)
+    uint8_t hash[16];
+
+    MD5_CTX m;
+    MD5_Init(&m);
+    MD5_Update(&m, buffer->data(), buffer->size());
+
+    MD5_Final(hash, &m);
+
+    if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
+        // playlist unchanged
+        *unchanged = true;
+
+        return NULL;
+    }
+
+    if (curPlaylistHash != NULL) {
+        memcpy(curPlaylistHash, hash, sizeof(hash));
+    }
+#endif
+
+    sp<M3UParser> playlist =
+        new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
+
+    if (playlist->initCheck() != OK) {
+        ALOGE("failed to parse .m3u8 playlist");
+
+        return NULL;
+    }
+
+    return playlist;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/httplive/HTTPDownloader.h b/media/libstagefright/httplive/HTTPDownloader.h
new file mode 100644
index 0000000..1db4a48
--- /dev/null
+++ b/media/libstagefright/httplive/HTTPDownloader.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HTTP_DOWNLOADER_H_
+
+#define HTTP_DOWNLOADER_H_
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <utils/KeyedVector.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+class DataSource;
+struct HTTPBase;
+struct IMediaHTTPService;
+struct M3UParser;
+
+struct HTTPDownloader : public RefBase {
+    HTTPDownloader(
+            const sp<IMediaHTTPService> &httpService,
+            const KeyedVector<String8, String8> &headers);
+
+    void reconnect();
+    void disconnect();
+    bool isDisconnecting();
+    // If given a non-zero block_size (default 0), it is used to cap the number of
+    // bytes read in from the DataSource. If given a non-NULL buffer, new content
+    // is read into the end.
+    //
+    // The DataSource we read from is responsible for signaling error or EOF to help us
+    // break out of the read loop. The DataSource can be returned to the caller, so
+    // that the caller can reuse it for subsequent fetches (within the initially
+    // requested range).
+    //
+    // For reused HTTP sources, the caller must download a file sequentially without
+    // any overlaps or gaps to prevent reconnection.
+    ssize_t fetchBlock(
+            const char *url,
+            sp<ABuffer> *out,
+            int64_t range_offset, /* open file at range_offset */
+            int64_t range_length, /* open file for range_length (-1: entire file) */
+            uint32_t block_size,  /* download block size (0: entire range) */
+            String8 *actualUrl,   /* returns actual URL */
+            bool reconnect        /* force connect http */
+            );
+
+    // simplified version to fetch a single file
+    ssize_t fetchFile(
+            const char *url,
+            sp<ABuffer> *out,
+            String8 *actualUrl = NULL);
+
+    // fetch a playlist file
+    sp<M3UParser> fetchPlaylist(
+            const char *url, uint8_t *curPlaylistHash, bool *unchanged);
+
+private:
+    sp<HTTPBase> mHTTPDataSource;
+    sp<DataSource> mDataSource;
+    KeyedVector<String8, String8> mExtraHeaders;
+
+    Mutex mLock;
+    bool mDisconnecting;
+
+    DISALLOW_EVIL_CONSTRUCTORS(HTTPDownloader);
+};
+
+}  // namespace android
+
+#endif  // HTTP_DOWNLOADER_H_
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 9ac764c..d8c38e7 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -19,25 +19,18 @@
 #include <utils/Log.h>
 
 #include "LiveSession.h"
-
+#include "HTTPDownloader.h"
 #include "M3UParser.h"
 #include "PlaylistFetcher.h"
 
-#include "include/HTTPBase.h"
 #include "mpeg2ts/AnotherPacketSource.h"
 
 #include <cutils/properties.h>
-#include <media/IMediaHTTPConnection.h>
 #include <media/IMediaHTTPService.h>
-#include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/AUtils.h>
-#include <media/stagefright/DataSource.h>
-#include <media/stagefright/FileSource.h>
-#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MediaHTTP.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/Utils.h>
@@ -46,8 +39,6 @@
 
 #include <ctype.h>
 #include <inttypes.h>
-#include <openssl/aes.h>
-#include <openssl/md5.h>
 
 namespace android {
 
@@ -257,7 +248,6 @@
       mInPreparationPhase(true),
       mPollBufferingGeneration(0),
       mPrevBufferPercentage(-1),
-      mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())),
       mCurBandwidthIndex(-1),
       mOrigBandwidthIndex(-1),
       mLastBandwidthBps(-1ll),
@@ -317,7 +307,7 @@
 
     ssize_t streamIdx = typeToIndex(stream);
     if (streamIdx < 0) {
-        return INVALID_VALUE;
+        return BAD_VALUE;
     }
     const char *streamStr = getNameForStream(stream);
     // Do not let client pull data if we don't have data packets yet.
@@ -465,8 +455,8 @@
     return convertMetaDataToMessage(meta, format);
 }
 
-sp<HTTPBase> LiveSession::getHTTPDataSource() {
-    return new MediaHTTP(mHTTPService->makeHTTPConnection());
+sp<HTTPDownloader> LiveSession::getHTTPDownloader() {
+    return new HTTPDownloader(mHTTPService, mExtraHeaders);
 }
 
 void LiveSession::connectAsync(
@@ -838,6 +828,12 @@
                     break;
                 }
 
+                case PlaylistFetcher::kWhatPlaylistFetched:
+                {
+                    onMasterPlaylistFetched(msg);
+                    break;
+                }
+
                 case PlaylistFetcher::kWhatMetadataDetected:
                 {
                     if (!mHasMetadata) {
@@ -874,12 +870,6 @@
             break;
         }
 
-        case kWhatFinishDisconnect2:
-        {
-            onFinishDisconnect2();
-            break;
-        }
-
         case kWhatPollBuffering:
         {
             int32_t generation;
@@ -931,8 +921,10 @@
 }
 
 void LiveSession::onConnect(const sp<AMessage> &msg) {
-    AString url;
-    CHECK(msg->findString("url", &url));
+    CHECK(msg->findString("url", &mMasterURL));
+
+    // TODO currently we don't know if we are coming here from incognito mode
+    ALOGI("onConnect %s", uriDebugString(mMasterURL).c_str());
 
     KeyedVector<String8, String8> *headers = NULL;
     if (!msg->findPointer("headers", (void **)&headers)) {
@@ -944,21 +936,6 @@
         headers = NULL;
     }
 
-    // TODO currently we don't know if we are coming here from incognito mode
-    ALOGI("onConnect %s", uriDebugString(url).c_str());
-
-    mMasterURL = url;
-
-    bool dummy;
-    mPlaylist = fetchPlaylist(url.c_str(), NULL /* curPlaylistHash */, &dummy);
-
-    if (mPlaylist == NULL) {
-        ALOGE("unable to fetch master playlist %s.", uriDebugString(url).c_str());
-
-        postPrepared(ERROR_IO);
-        return;
-    }
-
     // create looper for fetchers
     if (mFetcherLooper == NULL) {
         mFetcherLooper = new ALooper();
@@ -967,6 +944,31 @@
         mFetcherLooper->start(false, false);
     }
 
+    // create fetcher to fetch the master playlist
+    addFetcher(mMasterURL.c_str())->fetchPlaylistAsync();
+}
+
+void LiveSession::onMasterPlaylistFetched(const sp<AMessage> &msg) {
+    AString uri;
+    CHECK(msg->findString("uri", &uri));
+    ssize_t index = mFetcherInfos.indexOfKey(uri);
+    if (index < 0) {
+        ALOGW("fetcher for master playlist is gone.");
+        return;
+    }
+
+    // no longer useful, remove
+    mFetcherLooper->unregisterHandler(mFetcherInfos[index].mFetcher->id());
+    mFetcherInfos.removeItemsAt(index);
+
+    CHECK(msg->findObject("playlist", (sp<RefBase> *)&mPlaylist));
+    if (mPlaylist == NULL) {
+        ALOGE("unable to fetch master playlist %s.",
+                uriDebugString(mMasterURL).c_str());
+
+        postPrepared(ERROR_IO);
+        return;
+    }
     // We trust the content provider to make a reasonable choice of preferred
     // initial bandwidth by listing it first in the variant playlist.
     // At startup we really don't have a good estimate on the available
@@ -1050,22 +1052,26 @@
     // cancel buffer polling
     cancelPollBuffering();
 
+    // TRICKY: don't wait for all fetcher to be stopped when disconnecting
+    //
+    // Some fetchers might be stuck in connect/getSize at this point. These
+    // operations will eventually timeout (as we have a timeout set in
+    // MediaHTTPConnection), but we don't want to block the main UI thread
+    // until then. Here we just need to make sure we clear all references
+    // to the fetchers, so that when they finally exit from the blocking
+    // operation, they can be destructed.
+    //
+    // There is one very tricky point though. For this scheme to work, the
+    // fecther must hold a reference to LiveSession, so that LiveSession is
+    // destroyed after fetcher. Otherwise LiveSession would get stuck in its
+    // own destructor when it waits for mFetcherLooper to stop, which still
+    // blocks main UI thread.
     for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
         mFetcherInfos.valueAt(i).mFetcher->stopAsync();
+        mFetcherLooper->unregisterHandler(
+                mFetcherInfos.valueAt(i).mFetcher->id());
     }
-
-    sp<AMessage> msg = new AMessage(kWhatFinishDisconnect2, this);
-
-    mContinuationCounter = mFetcherInfos.size();
-    mContinuation = msg;
-
-    if (mContinuationCounter == 0) {
-        msg->post();
-    }
-}
-
-void LiveSession::onFinishDisconnect2() {
-    mContinuation.clear();
+    mFetcherInfos.clear();
 
     mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(ERROR_END_OF_STREAM);
     mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(ERROR_END_OF_STREAM);
@@ -1104,198 +1110,6 @@
     return info.mFetcher;
 }
 
-/*
- * Illustration of parameters:
- *
- * 0      `range_offset`
- * +------------+-------------------------------------------------------+--+--+
- * |            |                                 | next block to fetch |  |  |
- * |            | `source` handle => `out` buffer |                     |  |  |
- * | `url` file |<--------- buffer size --------->|<--- `block_size` -->|  |  |
- * |            |<----------- `range_length` / buffer capacity ----------->|  |
- * |<------------------------------ file_size ------------------------------->|
- *
- * Special parameter values:
- * - range_length == -1 means entire file
- * - block_size == 0 means entire range
- *
- */
-ssize_t LiveSession::fetchFile(
-        const char *url, sp<ABuffer> *out,
-        int64_t range_offset, int64_t range_length,
-        uint32_t block_size, /* download block size */
-        sp<DataSource> *source, /* to return and reuse source */
-        String8 *actualUrl,
-        bool forceConnectHTTP /* force connect HTTP when resuing source */) {
-    off64_t size;
-    sp<DataSource> temp_source;
-    if (source == NULL) {
-        source = &temp_source;
-    }
-
-    if (*source == NULL || forceConnectHTTP) {
-        if (!strncasecmp(url, "file://", 7)) {
-            *source = new FileSource(url + 7);
-        } else if (strncasecmp(url, "http://", 7)
-                && strncasecmp(url, "https://", 8)) {
-            return ERROR_UNSUPPORTED;
-        } else {
-            KeyedVector<String8, String8> headers = mExtraHeaders;
-            if (range_offset > 0 || range_length >= 0) {
-                headers.add(
-                        String8("Range"),
-                        String8(
-                            AStringPrintf(
-                                "bytes=%lld-%s",
-                                range_offset,
-                                range_length < 0
-                                    ? "" : AStringPrintf("%lld",
-                                            range_offset + range_length - 1).c_str()).c_str()));
-            }
-
-            HTTPBase* httpDataSource =
-                    (*source == NULL) ? mHTTPDataSource.get() : (HTTPBase*)source->get();
-            status_t err = httpDataSource->connect(url, &headers);
-
-            if (err != OK) {
-                return err;
-            }
-
-            if (*source == NULL) {
-                *source = mHTTPDataSource;
-            }
-        }
-    }
-
-    status_t getSizeErr = (*source)->getSize(&size);
-    if (getSizeErr != OK) {
-        size = 65536;
-    }
-
-    sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
-    if (*out == NULL) {
-        buffer->setRange(0, 0);
-    }
-
-    ssize_t bytesRead = 0;
-    // adjust range_length if only reading partial block
-    if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
-        range_length = buffer->size() + block_size;
-    }
-    for (;;) {
-        // Only resize when we don't know the size.
-        size_t bufferRemaining = buffer->capacity() - buffer->size();
-        if (bufferRemaining == 0 && getSizeErr != OK) {
-            size_t bufferIncrement = buffer->size() / 2;
-            if (bufferIncrement < 32768) {
-                bufferIncrement = 32768;
-            }
-            bufferRemaining = bufferIncrement;
-
-            ALOGV("increasing download buffer to %zu bytes",
-                 buffer->size() + bufferRemaining);
-
-            sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
-            memcpy(copy->data(), buffer->data(), buffer->size());
-            copy->setRange(0, buffer->size());
-
-            buffer = copy;
-        }
-
-        size_t maxBytesToRead = bufferRemaining;
-        if (range_length >= 0) {
-            int64_t bytesLeftInRange = range_length - buffer->size();
-            if (bytesLeftInRange < (int64_t)maxBytesToRead) {
-                maxBytesToRead = bytesLeftInRange;
-
-                if (bytesLeftInRange == 0) {
-                    break;
-                }
-            }
-        }
-
-        // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
-        // to help us break out of the loop.
-        ssize_t n = (*source)->readAt(
-                buffer->size(), buffer->data() + buffer->size(),
-                maxBytesToRead);
-
-        if (n < 0) {
-            return n;
-        }
-
-        if (n == 0) {
-            break;
-        }
-
-        buffer->setRange(0, buffer->size() + (size_t)n);
-        bytesRead += n;
-    }
-
-    *out = buffer;
-    if (actualUrl != NULL) {
-        *actualUrl = (*source)->getUri();
-        if (actualUrl->isEmpty()) {
-            *actualUrl = url;
-        }
-    }
-
-    return bytesRead;
-}
-
-sp<M3UParser> LiveSession::fetchPlaylist(
-        const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
-    ALOGV("fetchPlaylist '%s'", url);
-
-    *unchanged = false;
-
-    sp<ABuffer> buffer;
-    String8 actualUrl;
-    ssize_t  err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl);
-
-    // close off the connection after use
-    mHTTPDataSource->disconnect();
-
-    if (err <= 0) {
-        return NULL;
-    }
-
-    // MD5 functionality is not available on the simulator, treat all
-    // playlists as changed.
-
-#if defined(HAVE_ANDROID_OS)
-    uint8_t hash[16];
-
-    MD5_CTX m;
-    MD5_Init(&m);
-    MD5_Update(&m, buffer->data(), buffer->size());
-
-    MD5_Final(hash, &m);
-
-    if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
-        // playlist unchanged
-        *unchanged = true;
-
-        return NULL;
-    }
-
-    if (curPlaylistHash != NULL) {
-        memcpy(curPlaylistHash, hash, sizeof(hash));
-    }
-#endif
-
-    sp<M3UParser> playlist =
-        new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
-
-    if (playlist->initCheck() != OK) {
-        ALOGE("failed to parse .m3u8 playlist");
-
-        return NULL;
-    }
-
-    return playlist;
-}
-
 #if 0
 static double uniformRand() {
     return (double)rand() / RAND_MAX;
@@ -1690,9 +1504,11 @@
             fetcher->stopAsync();
         } else {
             float threshold = -1.0f; // always finish fetching by default
+            bool disconnect = false;
             if (timeUs >= 0ll) {
                 // seeking, no need to finish fetching
                 threshold = 0.0f;
+                disconnect = true;
             } else if (delayRemoval) {
                 // adapting, abort if remaining of current segment is over threshold
                 threshold = getAbortThreshold(
@@ -1701,7 +1517,7 @@
 
             ALOGV("pausing fetcher-%d, threshold=%.2f",
                     fetcher->getFetcherID(), threshold);
-            fetcher->pauseAsync(threshold);
+            fetcher->pauseAsync(threshold, disconnect);
         }
     }
 
diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index 4e7ccac..21be413 100644
--- a/media/libstagefright/httplive/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -37,6 +37,7 @@
 struct M3UParser;
 struct PlaylistFetcher;
 struct HLSTime;
+struct HTTPDownloader;
 
 struct LiveSession : public AHandler {
     enum Flags {
@@ -76,7 +77,7 @@
 
     status_t getStreamFormat(StreamType stream, sp<AMessage> *format);
 
-    sp<HTTPBase> getHTTPDataSource();
+    sp<HTTPDownloader> getHTTPDownloader();
 
     void connectAsync(
             const char *url,
@@ -127,7 +128,6 @@
         kWhatChangeConfiguration        = 'chC0',
         kWhatChangeConfiguration2       = 'chC2',
         kWhatChangeConfiguration3       = 'chC3',
-        kWhatFinishDisconnect2          = 'fin2',
         kWhatPollBuffering              = 'poll',
     };
 
@@ -191,7 +191,6 @@
     int32_t mPollBufferingGeneration;
     int32_t mPrevBufferPercentage;
 
-    sp<HTTPBase> mHTTPDataSource;
     KeyedVector<String8, String8> mExtraHeaders;
 
     AString mMasterURL;
@@ -253,34 +252,8 @@
     sp<PlaylistFetcher> addFetcher(const char *uri);
 
     void onConnect(const sp<AMessage> &msg);
+    void onMasterPlaylistFetched(const sp<AMessage> &msg);
     void onSeek(const sp<AMessage> &msg);
-    void onFinishDisconnect2();
-
-    // If given a non-zero block_size (default 0), it is used to cap the number of
-    // bytes read in from the DataSource. If given a non-NULL buffer, new content
-    // is read into the end.
-    //
-    // The DataSource we read from is responsible for signaling error or EOF to help us
-    // break out of the read loop. The DataSource can be returned to the caller, so
-    // that the caller can reuse it for subsequent fetches (within the initially
-    // requested range).
-    //
-    // For reused HTTP sources, the caller must download a file sequentially without
-    // any overlaps or gaps to prevent reconnection.
-    ssize_t fetchFile(
-            const char *url, sp<ABuffer> *out,
-            /* request/open a file starting at range_offset for range_length bytes */
-            int64_t range_offset = 0, int64_t range_length = -1,
-            /* download block size */
-            uint32_t block_size = 0,
-            /* reuse DataSource if doing partial fetch */
-            sp<DataSource> *source = NULL,
-            String8 *actualUrl = NULL,
-            /* force connect http even when resuing DataSource */
-            bool forceConnectHTTP = false);
-
-    sp<M3UParser> fetchPlaylist(
-            const char *url, uint8_t *curPlaylistHash, bool *unchanged);
 
     bool UriIsSameAsIndex( const AString &uri, int32_t index, bool newUri);
     sp<AnotherPacketSource> getPacketSourceForStreamIndex(size_t trackIndex, bool newUri);
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
index 8350c1b..53087b6 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.cpp
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -20,23 +20,16 @@
 #include <utils/misc.h>
 
 #include "PlaylistFetcher.h"
-
-#include "LiveDataSource.h"
+#include "HTTPDownloader.h"
 #include "LiveSession.h"
 #include "M3UParser.h"
-
 #include "include/avc_utils.h"
-#include "include/HTTPBase.h"
 #include "include/ID3.h"
 #include "mpeg2ts/AnotherPacketSource.h"
 
-#include <media/IStreamSource.h>
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/foundation/AUtils.h>
-#include <media/stagefright/foundation/hexdump.h>
-#include <media/stagefright/FileSource.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/Utils.h>
@@ -44,7 +37,6 @@
 #include <ctype.h>
 #include <inttypes.h>
 #include <openssl/aes.h>
-#include <openssl/md5.h>
 
 #define FLOGV(fmt, ...) ALOGV("[fetcher-%d] " fmt, mFetcherID, ##__VA_ARGS__)
 #define FSLOGV(stream, fmt, ...) ALOGV("[fetcher-%d] [%s] " fmt, mFetcherID, \
@@ -179,7 +171,7 @@
       mDownloadState(new DownloadState()),
       mHasMetadata(false) {
     memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
-    mHTTPDataSource = mSession->getHTTPDataSource();
+    mHTTPDownloader = mSession->getHTTPDownloader();
 }
 
 PlaylistFetcher::~PlaylistFetcher() {
@@ -338,9 +330,11 @@
     if (index >= 0) {
         key = mAESKeyForURI.valueAt(index);
     } else {
-        ssize_t err = mSession->fetchFile(keyURI.c_str(), &key);
+        ssize_t err = mHTTPDownloader->fetchFile(keyURI.c_str(), &key);
 
-        if (err < 0) {
+        if (err == ERROR_NOT_CONNECTED) {
+            return ERROR_NOT_CONNECTED;
+        } else if (err < 0) {
             ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str());
             return ERROR_IO;
         } else if (key->size() != 16) {
@@ -448,12 +442,32 @@
     ++mMonitorQueueGeneration;
 }
 
-void PlaylistFetcher::setStoppingThreshold(float thresholdRatio) {
-    AutoMutex _l(mThresholdLock);
-    if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
-        return;
+void PlaylistFetcher::setStoppingThreshold(float thresholdRatio, bool disconnect) {
+    {
+        AutoMutex _l(mThresholdLock);
+        mThresholdRatio = thresholdRatio;
     }
-    mThresholdRatio = thresholdRatio;
+    if (disconnect) {
+        mHTTPDownloader->disconnect();
+    }
+}
+
+void PlaylistFetcher::resetStoppingThreshold(bool disconnect) {
+    {
+        AutoMutex _l(mThresholdLock);
+        mThresholdRatio = -1.0f;
+    }
+    if (disconnect) {
+        mHTTPDownloader->disconnect();
+    } else {
+        // allow reconnect
+        mHTTPDownloader->reconnect();
+    }
+}
+
+float PlaylistFetcher::getStoppingThreshold() {
+    AutoMutex _l(mThresholdLock);
+    return mThresholdRatio;
 }
 
 void PlaylistFetcher::startAsync(
@@ -497,15 +511,15 @@
     msg->post();
 }
 
-void PlaylistFetcher::pauseAsync(float thresholdRatio) {
-    if (thresholdRatio >= 0.0f) {
-        setStoppingThreshold(thresholdRatio);
-    }
+void PlaylistFetcher::pauseAsync(
+        float thresholdRatio, bool disconnect) {
+    setStoppingThreshold(thresholdRatio, disconnect);
+
     (new AMessage(kWhatPause, this))->post();
 }
 
 void PlaylistFetcher::stopAsync(bool clear) {
-    setStoppingThreshold(0.0f);
+    setStoppingThreshold(0.0f, true /* disconncect */);
 
     sp<AMessage> msg = new AMessage(kWhatStop, this);
     msg->setInt32("clear", clear);
@@ -520,6 +534,10 @@
     msg->post();
 }
 
+void PlaylistFetcher::fetchPlaylistAsync() {
+    (new AMessage(kWhatFetchPlaylist, this))->post();
+}
+
 void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatStart:
@@ -557,6 +575,19 @@
             break;
         }
 
+        case kWhatFetchPlaylist:
+        {
+            bool unchanged;
+            sp<M3UParser> playlist = mHTTPDownloader->fetchPlaylist(
+                    mURI.c_str(), NULL /* curPlaylistHash */, &unchanged);
+
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatPlaylistFetched);
+            notify->setObject("playlist", playlist);
+            notify->post();
+            break;
+        }
+
         case kWhatMonitorQueue:
         case kWhatDownloadNext:
         {
@@ -676,7 +707,7 @@
     cancelMonitorQueue();
     mLastDiscontinuitySeq = mDiscontinuitySeq;
 
-    setStoppingThreshold(-1.0f);
+    resetStoppingThreshold(false /* disconnect */);
 }
 
 void PlaylistFetcher::onStop(const sp<AMessage> &msg) {
@@ -691,14 +722,11 @@
         }
     }
 
-    // close off the connection after use
-    mHTTPDataSource->disconnect();
-
     mDownloadState->resetState();
     mPacketSources.clear();
     mStreamTypeMask = 0;
 
-    setStoppingThreshold(-1.0f);
+    resetStoppingThreshold(true /* disconnect */);
 }
 
 // Resume until we have reached the boundary timestamps listed in `msg`; when
@@ -815,7 +843,7 @@
 status_t PlaylistFetcher::refreshPlaylist() {
     if (delayUsToRefreshPlaylist() <= 0) {
         bool unchanged;
-        sp<M3UParser> playlist = mSession->fetchPlaylist(
+        sp<M3UParser> playlist = mHTTPDownloader->fetchPlaylist(
                 mURI.c_str(), mPlaylistHash, &unchanged);
 
         if (playlist == NULL) {
@@ -864,18 +892,12 @@
     }
 
     // Calculate threshold to abort current download
-    int64_t targetDurationUs = mPlaylist->getTargetDuration();
-    int64_t thresholdUs = -1;
-    {
-        AutoMutex _l(mThresholdLock);
-        thresholdUs = (mThresholdRatio < 0.0f) ?
-                -1ll : mThresholdRatio * targetDurationUs;
-    }
+    float thresholdRatio = getStoppingThreshold();
 
-    if (thresholdUs < 0) {
+    if (thresholdRatio < 0.0f) {
         // never abort
         return false;
-    } else if (thresholdUs == 0) {
+    } else if (thresholdRatio == 0.0f) {
         // immediately abort
         return true;
     }
@@ -905,6 +927,9 @@
     }
     lastEnqueueUs -= mSegmentFirstPTS;
 
+    int64_t targetDurationUs = mPlaylist->getTargetDuration();
+    int64_t thresholdUs = thresholdRatio * targetDurationUs;
+
     FLOGV("%spausing now, thresholdUs %lld, remaining %lld",
             targetDurationUs - lastEnqueueUs > thresholdUs ? "" : "not ",
             (long long)thresholdUs,
@@ -1101,7 +1126,9 @@
         junk->setRange(0, 16);
         status_t err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, junk,
                 true /* first */);
-        if (err != OK) {
+        if (err == ERROR_NOT_CONNECTED) {
+            return false;
+        } else if (err != OK) {
             notifyError(err);
             return false;
         }
@@ -1202,12 +1229,21 @@
     bool shouldPause = false;
     ssize_t bytesRead;
     do {
-        sp<DataSource> source = mHTTPDataSource;
-
         int64_t startUs = ALooper::GetNowUs();
-        bytesRead = mSession->fetchFile(
+        bytesRead = mHTTPDownloader->fetchBlock(
                 uri.c_str(), &buffer, range_offset, range_length, kDownloadBlockSize,
-                &source, NULL, connectHTTP);
+                NULL /* actualURL */, connectHTTP);
+        int64_t delayUs = ALooper::GetNowUs() - startUs;
+
+        if (bytesRead == ERROR_NOT_CONNECTED) {
+            return;
+        }
+        if (bytesRead < 0) {
+            status_t err = bytesRead;
+            ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str());
+            notifyError(err);
+            return;
+        }
 
         // add sample for bandwidth estimation, excluding samples from subtitles (as
         // its too small), or during startup/resumeUntil (when we could have more than
@@ -1216,9 +1252,7 @@
                 && (mStreamTypeMask
                         & (LiveSession::STREAMTYPE_AUDIO
                         | LiveSession::STREAMTYPE_VIDEO))) {
-            int64_t delayUs = ALooper::GetNowUs() - startUs;
             mSession->addBandwidthMeasurement(bytesRead, delayUs);
-
             if (delayUs > 2000000ll) {
                 FLOGV("bytesRead %zd took %.2f seconds - abnormal bandwidth dip",
                         bytesRead, (double)delayUs / 1.0e6);
@@ -1227,13 +1261,6 @@
 
         connectHTTP = false;
 
-        if (bytesRead < 0) {
-            status_t err = bytesRead;
-            ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str());
-            notifyError(err);
-            return;
-        }
-
         CHECK(buffer != NULL);
 
         size_t size = buffer->size();
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
index 1f5e9b0..c8ca457 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.h
+++ b/media/libstagefright/httplive/PlaylistFetcher.h
@@ -49,6 +49,7 @@
         kWhatPreparationFailed,
         kWhatStartedAt,
         kWhatStopReached,
+        kWhatPlaylistFetched,
         kWhatMetadataDetected,
     };
 
@@ -61,8 +62,6 @@
 
     int32_t getFetcherID() const;
 
-    sp<DataSource> getDataSource();
-
     void startAsync(
             const sp<AnotherPacketSource> &audioSource,
             const sp<AnotherPacketSource> &videoSource,
@@ -74,12 +73,14 @@
             int32_t startDiscontinuitySeq = -1,
             LiveSession::SeekMode seekMode = LiveSession::kSeekModeExactPosition);
 
-    void pauseAsync(float thresholdRatio);
+    void pauseAsync(float thresholdRatio, bool disconnect);
 
     void stopAsync(bool clear = true);
 
     void resumeUntilAsync(const sp<AMessage> &params);
 
+    void fetchPlaylistAsync();
+
     uint32_t getStreamTypeMask() const {
         return mStreamTypeMask;
     }
@@ -100,6 +101,7 @@
         kWhatMonitorQueue   = 'moni',
         kWhatResumeUntil    = 'rsme',
         kWhatDownloadNext   = 'dlnx',
+        kWhatFetchPlaylist  = 'flst'
     };
 
     struct DownloadState;
@@ -114,7 +116,7 @@
     sp<AMessage> mNotify;
     sp<AMessage> mStartTimeUsNotify;
 
-    sp<HTTPBase> mHTTPDataSource;
+    sp<HTTPDownloader> mHTTPDownloader;
     sp<LiveSession> mSession;
     AString mURI;
 
@@ -197,7 +199,9 @@
 
     void postMonitorQueue(int64_t delayUs = 0, int64_t minDelayUs = 0);
     void cancelMonitorQueue();
-    void setStoppingThreshold(float thresholdRatio);
+    void setStoppingThreshold(float thresholdRatio, bool disconnect);
+    void resetStoppingThreshold(bool disconnect);
+    float getStoppingThreshold();
     bool shouldPauseDownload();
 
     int64_t delayUsToRefreshPlaylist() const;
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index f74b859..0878a1b 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -211,6 +211,11 @@
             mediaBuffer->meta_data()->setInt32(kKeyIsSyncFrame, isSync);
         }
 
+        sp<ABuffer> sei;
+        if (buffer->meta()->findBuffer("sei", &sei) && sei != NULL) {
+            mediaBuffer->meta_data()->setData(kKeySEI, 0, sei->data(), sei->size());
+        }
+
         *out = mediaBuffer;
         return OK;
     }
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index c7d9161..e1ddcbc 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -73,18 +73,18 @@
 class AudioBuffer;
 class AudioResampler;
 class FastMixer;
+class PassthruBufferProvider;
 class ServerProxy;
 
 // ----------------------------------------------------------------------------
 
-// AudioFlinger has a hard-coded upper limit of 2 channels for capture and playback.
-// There is support for > 2 channel tracks down-mixed to 2 channel output via a down-mix effect.
-// Adding full support for > 2 channel capture or playback would require more than simply changing
-// this #define.  There is an independent hard-coded upper limit in AudioMixer;
-// removing that AudioMixer limit would be necessary but insufficient to support > 2 channels.
-// The macro FCC_2 highlights some (but not all) places where there is are 2-channel assumptions.
+// The macro FCC_2 highlights some (but not all) places where there are are 2-channel assumptions.
+// This is typically due to legacy implementation of stereo input or output.
 // Search also for "2", "left", "right", "[0]", "[1]", ">> 16", "<< 16", etc.
 #define FCC_2 2     // FCC_2 = Fixed Channel Count 2
+// The macro FCC_8 highlights places where there are 8-channel assumptions.
+// This is typically due to audio mixer and resampler limitations.
+#define FCC_8 8     // FCC_8 = Fixed Channel Count 8
 
 static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
 
diff --git a/services/audioflinger/FastCapture.cpp b/services/audioflinger/FastCapture.cpp
index 9e7e8a4..79ac12b 100644
--- a/services/audioflinger/FastCapture.cpp
+++ b/services/audioflinger/FastCapture.cpp
@@ -105,7 +105,7 @@
             mFormat = mInputSource->format();
             mSampleRate = Format_sampleRate(mFormat);
             unsigned channelCount = Format_channelCount(mFormat);
-            ALOG_ASSERT(channelCount == 1 || channelCount == 2);
+            ALOG_ASSERT(channelCount >= 1 && channelCount <= FCC_8);
         }
         dumpState->mSampleRate = mSampleRate;
         eitherChanged = true;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index d3d77cd..4039564 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -56,6 +56,7 @@
 
 #include "AudioFlinger.h"
 #include "AudioMixer.h"
+#include "BufferProviders.h"
 #include "FastMixer.h"
 #include "FastCapture.h"
 #include "ServiceUtilities.h"
@@ -94,6 +95,10 @@
     return a < b ? a : b;
 }
 
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
 namespace android {
 
 // retry counts for buffer fill timeout
@@ -6246,7 +6251,11 @@
             // mDstChannelCount
             // mDstFrameSize
             mBuf(NULL), mBufFrames(0), mBufFrameSize(0),
-            mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpOutFrameCount(0)
+            mResampler(NULL),
+            mIsLegacyDownmix(false),
+            mIsLegacyUpmix(false),
+            mRequiresFloat(false),
+            mInputConverterProvider(NULL)
 {
     (void)updateParameters(srcChannelMask, srcFormat, srcSampleRate,
             dstChannelMask, dstFormat, dstSampleRate);
@@ -6255,13 +6264,18 @@
 AudioFlinger::RecordThread::RecordBufferConverter::~RecordBufferConverter() {
     free(mBuf);
     delete mResampler;
-    free(mRsmpOutBuffer);
+    delete mInputConverterProvider;
 }
 
 size_t AudioFlinger::RecordThread::RecordBufferConverter::convert(void *dst,
         AudioBufferProvider *provider, size_t frames)
 {
-    if (mSrcSampleRate == mDstSampleRate) {
+    if (mInputConverterProvider != NULL) {
+        mInputConverterProvider->setBufferProvider(provider);
+        provider = mInputConverterProvider;
+    }
+
+    if (mResampler == NULL) {
         ALOGVV("NO RESAMPLING sampleRate:%u mSrcFormat:%#x mDstFormat:%#x",
                 mSrcSampleRate, mSrcFormat, mDstFormat);
 
@@ -6273,8 +6287,8 @@
                 frames -= i; // cannot fill request.
                 break;
             }
-            // convert to destination buffer
-            convert(dst, buffer.raw, buffer.frameCount);
+            // format convert to destination buffer
+            convertNoResampler(dst, buffer.raw, buffer.frameCount);
 
             dst = (int8_t*)dst + buffer.frameCount * mDstFrameSize;
             i -= buffer.frameCount;
@@ -6284,20 +6298,17 @@
          ALOGVV("RESAMPLING mSrcSampleRate:%u mDstSampleRate:%u mSrcFormat:%#x mDstFormat:%#x",
                  mSrcSampleRate, mDstSampleRate, mSrcFormat, mDstFormat);
 
-        // reallocate mRsmpOutBuffer as needed; we will grow but never shrink
-        if (mRsmpOutFrameCount < frames) {
-            // FIXME why does each track need it's own mRsmpOutBuffer? can't they share?
-            free(mRsmpOutBuffer);
-            // resampler always outputs stereo (FOR NOW)
-            (void)posix_memalign(&mRsmpOutBuffer, 32, frames * FCC_2 * sizeof(int32_t) /*Q4.27*/);
-            mRsmpOutFrameCount = frames;
-        }
+         // reallocate buffer if needed
+         if (mBufFrameSize != 0 && mBufFrames < frames) {
+             free(mBuf);
+             mBufFrames = frames;
+             (void)posix_memalign(&mBuf, 32, mBufFrames * mBufFrameSize);
+         }
         // resampler accumulates, but we only have one source track
-        memset(mRsmpOutBuffer, 0, frames * FCC_2 * sizeof(int32_t));
-        frames = mResampler->resample((int32_t*)mRsmpOutBuffer, frames, provider);
-
-        // convert to destination buffer
-        convert(dst, mRsmpOutBuffer, frames);
+        memset(mBuf, 0, frames * mBufFrameSize);
+        frames = mResampler->resample((int32_t*)mBuf, frames, provider);
+        // format convert to destination buffer
+        convertResampler(dst, mBuf, frames);
     }
     return frames;
 }
@@ -6341,74 +6352,132 @@
     mDstChannelCount = audio_channel_count_from_in_mask(dstChannelMask);
     mDstFrameSize = mDstChannelCount * audio_bytes_per_sample(mDstFormat);
 
-    // do we need a format buffer?
-    if (mSrcFormat != mDstFormat && mDstChannelCount != mSrcChannelCount) {
+    // do we need to resample?
+    delete mResampler;
+    mResampler = NULL;
+    if (mSrcSampleRate != mDstSampleRate) {
+        mResampler = AudioResampler::create(AUDIO_FORMAT_PCM_FLOAT,
+                mSrcChannelCount, mDstSampleRate);
+        mResampler->setSampleRate(mSrcSampleRate);
+        mResampler->setVolume(AudioMixer::UNITY_GAIN_FLOAT, AudioMixer::UNITY_GAIN_FLOAT);
+    }
+
+    // are we running legacy channel conversion modes?
+    mIsLegacyDownmix = (mSrcChannelMask == AUDIO_CHANNEL_IN_STEREO
+                            || mSrcChannelMask == AUDIO_CHANNEL_IN_FRONT_BACK)
+                   && mDstChannelMask == AUDIO_CHANNEL_IN_MONO;
+    mIsLegacyUpmix = mSrcChannelMask == AUDIO_CHANNEL_IN_MONO
+                   && (mDstChannelMask == AUDIO_CHANNEL_IN_STEREO
+                            || mDstChannelMask == AUDIO_CHANNEL_IN_FRONT_BACK);
+
+    // do we need to process in float?
+    mRequiresFloat = mResampler != NULL || mIsLegacyDownmix || mIsLegacyUpmix;
+
+    // do we need a staging buffer to convert for destination (we can still optimize this)?
+    // we use mBufFrameSize > 0 to indicate both frame size as well as buffer necessity
+    if (mResampler != NULL) {
+        mBufFrameSize = max(mSrcChannelCount, FCC_2)
+                * audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT);
+    } else if ((mIsLegacyUpmix || mIsLegacyDownmix) && mDstFormat != AUDIO_FORMAT_PCM_FLOAT) {
+        mBufFrameSize = mDstChannelCount * audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT);
+    } else if (mSrcChannelMask != mDstChannelMask && mDstFormat != mSrcFormat) {
         mBufFrameSize = mDstChannelCount * audio_bytes_per_sample(mSrcFormat);
     } else {
         mBufFrameSize = 0;
     }
     mBufFrames = 0; // force the buffer to be resized.
 
-    // do we need to resample?
-    if (mSrcSampleRate != mDstSampleRate) {
-        if (mResampler != NULL) {
-            delete mResampler;
-        }
-        mResampler = AudioResampler::create(AUDIO_FORMAT_PCM_16_BIT,
-                mSrcChannelCount, mDstSampleRate); // may seem confusing...
-        mResampler->setSampleRate(mSrcSampleRate);
-        mResampler->setVolume(AudioMixer::UNITY_GAIN_FLOAT, AudioMixer::UNITY_GAIN_FLOAT);
+    // do we need an input converter buffer provider to give us float?
+    delete mInputConverterProvider;
+    mInputConverterProvider = NULL;
+    if (mRequiresFloat && mSrcFormat != AUDIO_FORMAT_PCM_FLOAT) {
+        mInputConverterProvider = new ReformatBufferProvider(
+                audio_channel_count_from_in_mask(mSrcChannelMask),
+                mSrcFormat,
+                AUDIO_FORMAT_PCM_FLOAT,
+                256 /* provider buffer frame count */);
+    }
+
+    // do we need a remixer to do channel mask conversion
+    if (!mIsLegacyDownmix && !mIsLegacyUpmix && mSrcChannelMask != mDstChannelMask) {
+        (void) memcpy_by_index_array_initialization_from_channel_mask(
+                mIdxAry, ARRAY_SIZE(mIdxAry), mDstChannelMask, mSrcChannelMask);
     }
     return NO_ERROR;
 }
 
-void AudioFlinger::RecordThread::RecordBufferConverter::convert(
-        void *dst, /*const*/ void *src, size_t frames)
+void AudioFlinger::RecordThread::RecordBufferConverter::convertNoResampler(
+        void *dst, const void *src, size_t frames)
 {
-    // check if a memcpy will do
-    if (mResampler == NULL
-            && mSrcChannelCount == mDstChannelCount
-            && mSrcFormat == mDstFormat) {
-        memcpy(dst, src,
-                frames * mDstChannelCount * audio_bytes_per_sample(mDstFormat));
-        return;
-    }
-    // reallocate buffer if needed
+    // src is native type unless there is legacy upmix or downmix, whereupon it is float.
     if (mBufFrameSize != 0 && mBufFrames < frames) {
         free(mBuf);
         mBufFrames = frames;
         (void)posix_memalign(&mBuf, 32, mBufFrames * mBufFrameSize);
     }
-    // do processing
-    if (mResampler != NULL) {
-        // src channel count is always >= 2.
+    // do we need to do legacy upmix and downmix?
+    if (mIsLegacyUpmix || mIsLegacyDownmix) {
         void *dstBuf = mBuf != NULL ? mBuf : dst;
-        // ditherAndClamp() works as long as all buffers returned by
-        // activeTrack->getNextBuffer() are 32 bit aligned which should be always true.
-        if (mDstChannelCount == 1) {
-            // the resampler always outputs stereo samples.
-            // FIXME: this rewrites back into src
-            ditherAndClamp((int32_t *)src, (const int32_t *)src, frames);
-            downmix_to_mono_i16_from_stereo_i16((int16_t *)dstBuf,
-                    (const int16_t *)src, frames);
-        } else {
-            ditherAndClamp((int32_t *)dstBuf, (const int32_t *)src, frames);
+        if (mIsLegacyUpmix) {
+            upmix_to_stereo_float_from_mono_float((float *)dstBuf,
+                    (const float *)src, frames);
+        } else /*mIsLegacyDownmix */ {
+            downmix_to_mono_float_from_stereo_float((float *)dstBuf,
+                    (const float *)src, frames);
         }
-    } else if (mSrcChannelCount != mDstChannelCount) {
+        if (mBuf != NULL) {
+            memcpy_by_audio_format(dst, mDstFormat, mBuf, AUDIO_FORMAT_PCM_FLOAT,
+                    frames * mDstChannelCount);
+        }
+        return;
+    }
+    // do we need to do channel mask conversion?
+    if (mSrcChannelMask != mDstChannelMask) {
         void *dstBuf = mBuf != NULL ? mBuf : dst;
+        memcpy_by_index_array(dstBuf, mDstChannelCount,
+                src, mSrcChannelCount, mIdxAry, audio_bytes_per_sample(mSrcFormat), frames);
+        if (dstBuf == dst) {
+            return; // format is the same
+        }
+    }
+    // convert to destination buffer
+    const void *convertBuf = mBuf != NULL ? mBuf : src;
+    memcpy_by_audio_format(dst, mDstFormat, convertBuf, mSrcFormat,
+            frames * mDstChannelCount);
+}
+
+void AudioFlinger::RecordThread::RecordBufferConverter::convertResampler(
+        void *dst, /*not-a-const*/ void *src, size_t frames)
+{
+    // src buffer format is ALWAYS float when entering this routine
+    if (mIsLegacyUpmix) {
+        ; // mono to stereo already handled by resampler
+    } else if (mIsLegacyDownmix
+            || (mSrcChannelMask == mDstChannelMask && mSrcChannelCount == 1)) {
+        // the resampler outputs stereo for mono input channel (a feature?)
+        // must convert to mono
+        downmix_to_mono_float_from_stereo_float((float *)src,
+                (const float *)src, frames);
+    } else if (mSrcChannelMask != mDstChannelMask) {
+        // convert to mono channel again for channel mask conversion (could be skipped
+        // with further optimization).
         if (mSrcChannelCount == 1) {
-            upmix_to_stereo_i16_from_mono_i16((int16_t *)dstBuf, (const int16_t *)src,
-                    frames);
-        } else {
-            downmix_to_mono_i16_from_stereo_i16((int16_t *)dstBuf,
-                    (const int16_t *)src, frames);
+            downmix_to_mono_float_from_stereo_float((float *)src,
+                (const float *)src, frames);
         }
+        // convert to destination format (in place, OK as float is larger than other types)
+        if (mDstFormat != AUDIO_FORMAT_PCM_FLOAT) {
+            memcpy_by_audio_format(src, mDstFormat, src, AUDIO_FORMAT_PCM_FLOAT,
+                    frames * mSrcChannelCount);
+        }
+        // channel convert and save to dst
+        memcpy_by_index_array(dst, mDstChannelCount,
+                src, mSrcChannelCount, mIdxAry, audio_bytes_per_sample(mDstFormat), frames);
+        return;
     }
-    if (mSrcFormat != mDstFormat) {
-        void *srcBuf = mBuf != NULL ? mBuf : src;
-        memcpy_by_audio_format(dst, mDstFormat, srcBuf, mSrcFormat,
-                frames * mDstChannelCount);
-    }
+    // convert to destination format and save to dst
+    memcpy_by_audio_format(dst, mDstFormat, src, AUDIO_FORMAT_PCM_FLOAT,
+            frames * mDstChannelCount);
 }
 
 bool AudioFlinger::RecordThread::checkForNewParameter_l(const String8& keyValuePair,
@@ -6421,6 +6490,10 @@
     audio_format_t reqFormat = mFormat;
     uint32_t samplingRate = mSampleRate;
     audio_channel_mask_t channelMask = audio_channel_in_mask_from_count(mChannelCount);
+    // possible that we are > 2 channels, use channel index mask
+    if (channelMask == AUDIO_CHANNEL_INVALID && mChannelCount <= FCC_8) {
+        audio_channel_mask_for_index_assignment_from_count(mChannelCount);
+    }
 
     AudioParameter param = AudioParameter(keyValuePair);
     int value;
@@ -6441,7 +6514,8 @@
     }
     if (param.getInt(String8(AudioParameter::keyChannels), value) == NO_ERROR) {
         audio_channel_mask_t mask = (audio_channel_mask_t) value;
-        if (mask != AUDIO_CHANNEL_IN_MONO && mask != AUDIO_CHANNEL_IN_STEREO) {
+        if (!audio_is_input_channel(mask) ||
+                audio_channel_count_from_in_mask(mask) > FCC_8) {
             status = BAD_VALUE;
         } else {
             channelMask = mask;
@@ -6566,10 +6640,13 @@
     mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common);
     mChannelMask = mInput->stream->common.get_channels(&mInput->stream->common);
     mChannelCount = audio_channel_count_from_in_mask(mChannelMask);
+    if (mChannelCount > FCC_8) {
+        ALOGE("HAL channel count %d > %d", mChannelCount, FCC_8);
+    }
     mHALFormat = mInput->stream->common.get_format(&mInput->stream->common);
     mFormat = mHALFormat;
-    if (mFormat != AUDIO_FORMAT_PCM_16_BIT) {
-        ALOGE("HAL format %#x not supported; must be AUDIO_FORMAT_PCM_16_BIT", mFormat);
+    if (!audio_is_linear_pcm(mFormat)) {
+        ALOGE("HAL format %#x is not linear pcm", mFormat);
     }
     mFrameSize = audio_stream_in_frame_size(mInput->stream);
     mBufferSize = mInput->stream->common.get_buffer_size(&mInput->stream->common);
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index ed2e4a1..b7c1ed1 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1130,8 +1130,11 @@
         }
 
     private:
-        // internal convert function for format and channel mask.
-        void convert(void *dst, /*const*/ void *src, size_t frames);
+        // format conversion when not using resampler
+        void convertNoResampler(void *dst, const void *src, size_t frames);
+
+        // format conversion when using resampler; modifies src in-place
+        void convertResampler(void *dst, /*not-a-const*/ void *src, size_t frames);
 
         // user provided information
         audio_channel_mask_t mSrcChannelMask;
@@ -1153,10 +1156,12 @@
 
         // resampler info
         AudioResampler      *mResampler;
-        // interleaved stereo pairs of fixed-point Q4.27 or float depending on resampler
-        void                *mRsmpOutBuffer;
-        // current allocated frame count for the above, which may be larger than needed
-        size_t               mRsmpOutFrameCount;
+
+        bool                 mIsLegacyDownmix;  // legacy stereo to mono conversion needed
+        bool                 mIsLegacyUpmix;    // legacy mono to stereo conversion needed
+        bool                 mRequiresFloat;    // data processing requires float (e.g. resampler)
+        PassthruBufferProvider *mInputConverterProvider;    // converts input to float
+        int8_t               mIdxAry[sizeof(uint32_t) * 8]; // used for channel mask conversion
     };
 
 #include "RecordTracks.h"
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioPort.h b/services/audiopolicy/common/managerdefinitions/include/AudioPort.h
index 1c2c27e..82e2c43 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioPort.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioPort.h
@@ -75,10 +75,8 @@
     audio_format_t pickFormat() const;
 
     static const audio_format_t sPcmFormatCompareTable[];
-    static int compareFormatsGoodToBad(
-            const audio_format_t *format1, const audio_format_t *format2) {
-        // compareFormats sorts from bad to good, we reverse it here
-        return compareFormats(*format2, *format1);
+    static int compareFormats(const audio_format_t *format1, const audio_format_t *format2) {
+        return compareFormats(*format1, *format2);
     }
     static int compareFormats(audio_format_t format1, audio_format_t format2);
 
diff --git a/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h b/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h
index f8c4d08..0b08430 100644
--- a/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h
+++ b/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h
@@ -38,7 +38,8 @@
     uint32_t value;
 };
 
-#define STRING_TO_ENUM(string) { #string, string }
+// TODO: move to a separate file. Should be in sync with audio.h.
+#define STRING_TO_ENUM(string) { #string, (uint32_t)string } // uint32_t cast removes warning
 #define NAME_TO_ENUM(name, value) { name, value }
 #ifndef ARRAY_SIZE
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
@@ -204,6 +205,17 @@
     STRING_TO_ENUM(AUDIO_CHANNEL_IN_FRONT_BACK),
 };
 
+const StringToEnum sIndexChannelsNameToEnumTable[] = {
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_1),
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_2),
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_3),
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_4),
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_5),
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_6),
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_7),
+    STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_8),
+};
+
 const StringToEnum sGainModeNameToEnumTable[] = {
     STRING_TO_ENUM(AUDIO_GAIN_MODE_JOINT),
     STRING_TO_ENUM(AUDIO_GAIN_MODE_CHANNELS),
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp
index f3978ec..64f883a 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp
@@ -146,7 +146,7 @@
                     break;
                 }
             }
-            if (!hasFormat) { // never import a channel mask twice
+            if (!hasFormat) { // never import a format twice
                 mFormats.add(format);
             }
         }
@@ -216,7 +216,12 @@
         }
         str = strtok(NULL, "|");
     }
-    mFormats.sort(compareFormatsGoodToBad);
+    // we sort from worst to best, so that AUDIO_FORMAT_DEFAULT is always the first entry.
+    // TODO: compareFormats could be a lambda to convert between pointer-to-format to format:
+    // [](const audio_format_t *format1, const audio_format_t *format2) {
+    //     return compareFormats(*format1, *format2);
+    // }
+    mFormats.sort(compareFormats);
 }
 
 void AudioPort::loadInChannels(char *name)
@@ -235,8 +240,14 @@
                 (audio_channel_mask_t)ConfigParsingUtils::stringToEnum(sInChannelsNameToEnumTable,
                                                    ARRAY_SIZE(sInChannelsNameToEnumTable),
                                                    str);
+        if (channelMask == 0) { // if not found, check the channel index table
+            channelMask = (audio_channel_mask_t)
+                      ConfigParsingUtils::stringToEnum(sIndexChannelsNameToEnumTable,
+                              ARRAY_SIZE(sIndexChannelsNameToEnumTable),
+                              str);
+        }
         if (channelMask != 0) {
-            ALOGV("loadInChannels() adding channelMask %04x", channelMask);
+            ALOGV("loadInChannels() adding channelMask %#x", channelMask);
             mChannelMasks.add(channelMask);
         }
         str = strtok(NULL, "|");
@@ -441,30 +452,87 @@
     }
 
     const bool isRecordThread = mType == AUDIO_PORT_TYPE_MIX && mRole == AUDIO_PORT_ROLE_SINK;
+    const bool isIndex = audio_channel_mask_get_representation(channelMask)
+            == AUDIO_CHANNEL_REPRESENTATION_INDEX;
+    int bestMatch = 0;
     for (size_t i = 0; i < mChannelMasks.size(); i ++) {
-        // FIXME Does not handle multi-channel automatic conversions yet
         audio_channel_mask_t supported = mChannelMasks[i];
         if (supported == channelMask) {
+            // Exact matches always taken.
             if (updatedChannelMask != NULL) {
                 *updatedChannelMask = channelMask;
             }
             return NO_ERROR;
         }
-        if (isRecordThread) {
-            // This uses hard-coded knowledge that AudioFlinger can silently down-mix and up-mix.
-            // FIXME Abstract this out to a table.
-            if (((supported == AUDIO_CHANNEL_IN_FRONT_BACK || supported == AUDIO_CHANNEL_IN_STEREO)
-                    && channelMask == AUDIO_CHANNEL_IN_MONO) ||
-                (supported == AUDIO_CHANNEL_IN_MONO && (channelMask == AUDIO_CHANNEL_IN_FRONT_BACK
-                    || channelMask == AUDIO_CHANNEL_IN_STEREO))) {
+
+        // AUDIO_CHANNEL_NONE (value: 0) is used for dynamic channel support
+        if (isRecordThread && supported != AUDIO_CHANNEL_NONE) {
+            // Approximate (best) match:
+            // The match score measures how well the supported channel mask matches the
+            // desired mask, where increasing-is-better.
+            //
+            // TODO: Some tweaks may be needed.
+            // Should be a static function of the data processing library.
+            //
+            // In priority:
+            // match score = 1000 if legacy channel conversion equivalent (always prefer this)
+            // OR
+            // match score += 100 if the channel mask representations match
+            // match score += number of channels matched.
+            //
+            // If there are no matched channels, the mask may still be accepted
+            // but the playback or record will be silent.
+            const bool isSupportedIndex = (audio_channel_mask_get_representation(supported)
+                    == AUDIO_CHANNEL_REPRESENTATION_INDEX);
+            int match;
+            if (isIndex && isSupportedIndex) {
+                // index equivalence
+                match = 100 + __builtin_popcount(
+                        audio_channel_mask_get_bits(channelMask)
+                            & audio_channel_mask_get_bits(supported));
+            } else if (isIndex && !isSupportedIndex) {
+                const uint32_t equivalentBits =
+                        (1 << audio_channel_count_from_in_mask(supported)) - 1 ;
+                match = __builtin_popcount(
+                        audio_channel_mask_get_bits(channelMask) & equivalentBits);
+            } else if (!isIndex && isSupportedIndex) {
+                const uint32_t equivalentBits =
+                        (1 << audio_channel_count_from_in_mask(channelMask)) - 1;
+                match = __builtin_popcount(
+                        equivalentBits & audio_channel_mask_get_bits(supported));
+            } else {
+                // positional equivalence
+                match = 100 + __builtin_popcount(
+                        audio_channel_mask_get_bits(channelMask)
+                            & audio_channel_mask_get_bits(supported));
+                switch (supported) {
+                case AUDIO_CHANNEL_IN_FRONT_BACK:
+                case AUDIO_CHANNEL_IN_STEREO:
+                    if (channelMask == AUDIO_CHANNEL_IN_MONO) {
+                        match = 1000;
+                    }
+                    break;
+                case AUDIO_CHANNEL_IN_MONO:
+                    if (channelMask == AUDIO_CHANNEL_IN_FRONT_BACK
+                            || channelMask == AUDIO_CHANNEL_IN_STEREO) {
+                        match = 1000;
+                    }
+                    break;
+                default:
+                    break;
+                }
+            }
+            if (match > bestMatch) {
+                bestMatch = match;
                 if (updatedChannelMask != NULL) {
                     *updatedChannelMask = supported;
+                } else {
+                    return NO_ERROR; // any match will do in this case.
                 }
-                return NO_ERROR;
             }
         }
     }
-    return BAD_VALUE;
+    return bestMatch > 0 ? NO_ERROR : BAD_VALUE;
 }
 
 status_t AudioPort::checkExactFormat(audio_format_t format) const
@@ -495,11 +563,13 @@
             mType == AUDIO_PORT_TYPE_MIX && mRole == AUDIO_PORT_ROLE_SINK
             && audio_is_linear_pcm(format);
 
-    for (size_t i = 0; i < mFormats.size(); ++i) {
+    // iterate from best format to worst format (reverse order)
+    for (ssize_t i = mFormats.size() - 1; i >= 0 ; --i) {
         if (mFormats[i] == format ||
-                (checkInexact && audio_is_linear_pcm(mFormats[i]))) {
-            // for inexact checks we take the first linear pcm format since
-            // mFormats is sorted from best PCM format to worst PCM format.
+                (checkInexact
+                        && mFormats[i] != AUDIO_FORMAT_DEFAULT
+                        && audio_is_linear_pcm(mFormats[i]))) {
+            // for inexact checks we take the first linear pcm format due to sorting.
             if (updatedFormat != NULL) {
                 *updatedFormat = mFormats[i];
             }
@@ -726,10 +796,15 @@
             const char *formatStr = ConfigParsingUtils::enumToString(sFormatNameToEnumTable,
                                                  ARRAY_SIZE(sFormatNameToEnumTable),
                                                  mFormats[i]);
-            if (i == 0 && strcmp(formatStr, "") == 0) {
+            const bool isEmptyStr = formatStr[0] == 0;
+            if (i == 0 && isEmptyStr) {
                 snprintf(buffer, SIZE, "Dynamic");
             } else {
-                snprintf(buffer, SIZE, "%s", formatStr);
+                if (isEmptyStr) {
+                    snprintf(buffer, SIZE, "%#x", mFormats[i]);
+                } else {
+                    snprintf(buffer, SIZE, "%s", formatStr);
+                }
             }
             result.append(buffer);
             result.append(i == (mFormats.size() - 1) ? "" : ", ");
diff --git a/services/radio/RadioService.cpp b/services/radio/RadioService.cpp
index a6c2bdf..cd0f5f3 100644
--- a/services/radio/RadioService.cpp
+++ b/services/radio/RadioService.cpp
@@ -146,7 +146,7 @@
     radio = module->addClient(client, config, withAudio);
 
     if (radio == 0) {
-        NO_INIT;
+        return NO_INIT;
     }
     return NO_ERROR;
 }
@@ -500,13 +500,12 @@
         if (audio) {
             notifyDeviceConnection(true, "");
         }
+        ALOGV("addClient() DONE moduleClient %p", moduleClient.get());
     } else {
+        ALOGW("%s open_tuner failed with error %d", __FUNCTION__, ret);
         moduleClient.clear();
     }
 
-
-    ALOGV("addClient() DONE moduleClient %p", moduleClient.get());
-
     return moduleClient;
 }