Merge "MTP: Write initial data to correct file offset in SendPartialObject"
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 2b935ed..2aae64d 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -30,6 +30,7 @@
 #include <binder/ProcessState.h>
 #include <media/IMediaPlayerService.h>
 #include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
 #include "include/LiveSession.h"
 #include "include/NuCachedSource2.h"
 #include <media/stagefright/AudioPlayer.h>
@@ -1004,7 +1005,7 @@
                     looper = new ALooper;
                     looper->start();
                 }
-                liveSession = new LiveSession;
+                liveSession = new LiveSession(NULL /* notify */);
                 looper->registerHandler(liveSession);
 
                 liveSession->connect(uri.string());
diff --git a/include/media/IMediaLogService.h b/include/media/IMediaLogService.h
new file mode 100644
index 0000000..1f5777e
--- /dev/null
+++ b/include/media/IMediaLogService.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IMEDIALOGSERVICE_H
+#define ANDROID_IMEDIALOGSERVICE_H
+
+#include <binder/IInterface.h>
+#include <binder/IMemory.h>
+#include <binder/Parcel.h>
+
+namespace android {
+
+class IMediaLogService: public IInterface
+{
+public:
+    DECLARE_META_INTERFACE(MediaLogService);
+
+    virtual void    registerWriter(const sp<IMemory>& shared, size_t size, const char *name) = 0;
+    virtual void    unregisterWriter(const sp<IMemory>& shared) = 0;
+
+};
+
+class BnMediaLogService: public BnInterface<IMediaLogService>
+{
+public:
+    virtual status_t    onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+                                uint32_t flags = 0);
+};
+
+}   // namespace android
+
+#endif  // ANDROID_IMEDIALOGSERVICE_H
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index df1c46b..317b6f0 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -54,6 +54,8 @@
     void signalResume();
     void initiateShutdown(bool keepComponentAllocated = false);
 
+    void signalSetParameters(const sp<AMessage> &msg);
+
     void initiateAllocateComponent(const sp<AMessage> &msg);
     void initiateConfigureComponent(const sp<AMessage> &msg);
     void initiateStart();
@@ -105,6 +107,7 @@
         kWhatConfigureComponent      = 'conf',
         kWhatStart                   = 'star',
         kWhatRequestIDRFrame         = 'ridr',
+        kWhatSetParameters           = 'setP',
     };
 
     enum {
@@ -270,6 +273,7 @@
             status_t internalError = UNKNOWN_ERROR);
 
     status_t requestIDRFrame();
+    status_t setParameters(const sp<AMessage> &params);
 
     DISALLOW_EVIL_CONSTRUCTORS(ACodec);
 };
diff --git a/include/media/stagefright/MediaCodec.h b/include/media/stagefright/MediaCodec.h
index 88aabf6..3f0d3b3 100644
--- a/include/media/stagefright/MediaCodec.h
+++ b/include/media/stagefright/MediaCodec.h
@@ -115,6 +115,8 @@
 
     status_t getName(AString *componentName) const;
 
+    status_t setParameters(const sp<AMessage> &params);
+
 protected:
     virtual ~MediaCodec();
     virtual void onMessageReceived(const sp<AMessage> &msg);
@@ -157,6 +159,7 @@
         kWhatRequestIDRFrame                = 'ridr',
         kWhatRequestActivityNotification    = 'racN',
         kWhatGetName                        = 'getN',
+        kWhatSetParameters                  = 'setP',
     };
 
     enum {
@@ -230,6 +233,8 @@
 
     void postActivityNotificationIfPossible();
 
+    status_t onSetParameters(const sp<AMessage> &params);
+
     DISALLOW_EVIL_CONSTRUCTORS(MediaCodec);
 };
 
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index a35d562..52fa3e1 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -23,6 +23,7 @@
     AudioRecord.cpp \
     AudioSystem.cpp \
     mediaplayer.cpp \
+    IMediaLogService.cpp \
     IMediaPlayerService.cpp \
     IMediaPlayerClient.cpp \
     IMediaRecorderClient.cpp \
diff --git a/media/libmedia/IMediaLogService.cpp b/media/libmedia/IMediaLogService.cpp
new file mode 100644
index 0000000..33239a7
--- /dev/null
+++ b/media/libmedia/IMediaLogService.cpp
@@ -0,0 +1,94 @@
+/*
+**
+** Copyright 2007, 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_TAG "IMediaLogService"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <binder/Parcel.h>
+#include <media/IMediaLogService.h>
+
+namespace android {
+
+enum {
+    REGISTER_WRITER = IBinder::FIRST_CALL_TRANSACTION,
+    UNREGISTER_WRITER,
+};
+
+class BpMediaLogService : public BpInterface<IMediaLogService>
+{
+public:
+    BpMediaLogService(const sp<IBinder>& impl)
+        : BpInterface<IMediaLogService>(impl)
+    {
+    }
+
+    virtual void    registerWriter(const sp<IMemory>& shared, size_t size, const char *name) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IMediaLogService::getInterfaceDescriptor());
+        data.writeStrongBinder(shared->asBinder());
+        data.writeInt32((int32_t) size);
+        data.writeCString(name);
+        status_t status = remote()->transact(REGISTER_WRITER, data, &reply);
+        // FIXME ignores status
+    }
+
+    virtual void    unregisterWriter(const sp<IMemory>& shared) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IMediaLogService::getInterfaceDescriptor());
+        data.writeStrongBinder(shared->asBinder());
+        status_t status = remote()->transact(UNREGISTER_WRITER, data, &reply);
+        // FIXME ignores status
+    }
+
+};
+
+IMPLEMENT_META_INTERFACE(MediaLogService, "android.media.IMediaLogService");
+
+// ----------------------------------------------------------------------
+
+status_t BnMediaLogService::onTransact(
+    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+    switch (code) {
+
+        case REGISTER_WRITER: {
+            CHECK_INTERFACE(IMediaLogService, data, reply);
+            sp<IMemory> shared = interface_cast<IMemory>(data.readStrongBinder());
+            size_t size = (size_t) data.readInt32();
+            const char *name = data.readCString();
+            registerWriter(shared, size, name);
+            return NO_ERROR;
+        }
+
+        case UNREGISTER_WRITER: {
+            CHECK_INTERFACE(IMediaLogService, data, reply);
+            sp<IMemory> shared = interface_cast<IMemory>(data.readStrongBinder());
+            unregisterWriter(shared);
+            return NO_ERROR;
+        }
+
+        default:
+            return BBinder::onTransact(code, data, reply, flags);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
diff --git a/media/libmediaplayerservice/MediaPlayerFactory.cpp b/media/libmediaplayerservice/MediaPlayerFactory.cpp
index 3f69c11..1fb8b1a 100644
--- a/media/libmediaplayerservice/MediaPlayerFactory.cpp
+++ b/media/libmediaplayerservice/MediaPlayerFactory.cpp
@@ -100,7 +100,7 @@
     }                                                   \
                                                         \
     if (0.0 == bestScore) {                             \
-        bestScore = getDefaultPlayerType();             \
+        ret = getDefaultPlayerType();                   \
     }                                                   \
                                                         \
     return ret;
@@ -215,6 +215,10 @@
             if (strstr(url,"m3u8")) {
                 return kOurScore;
             }
+
+            if ((len >= 4 && !strcasecmp(".sdp", &url[len - 4])) || strstr(url, ".sdp?")) {
+                return kOurScore;
+            }
         }
 
         if (!strncasecmp("rtsp://", url, 7)) {
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index f281879..b04e7a6 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -32,11 +32,13 @@
 namespace android {
 
 NuPlayer::GenericSource::GenericSource(
+        const sp<AMessage> &notify,
         const char *url,
         const KeyedVector<String8, String8> *headers,
         bool uidValid,
         uid_t uid)
-    : mDurationUs(0ll),
+    : Source(notify),
+      mDurationUs(0ll),
       mAudioIsVorbis(false) {
     DataSource::RegisterDefaultSniffers();
 
@@ -48,8 +50,10 @@
 }
 
 NuPlayer::GenericSource::GenericSource(
+        const sp<AMessage> &notify,
         int fd, int64_t offset, int64_t length)
-    : mDurationUs(0ll),
+    : Source(notify),
+      mDurationUs(0ll),
       mAudioIsVorbis(false) {
     DataSource::RegisterDefaultSniffers();
 
@@ -102,6 +106,26 @@
 NuPlayer::GenericSource::~GenericSource() {
 }
 
+void NuPlayer::GenericSource::prepareAsync() {
+    if (mVideoTrack.mSource != NULL) {
+        sp<MetaData> meta = mVideoTrack.mSource->getFormat();
+
+        int32_t width, height;
+        CHECK(meta->findInt32(kKeyWidth, &width));
+        CHECK(meta->findInt32(kKeyHeight, &height));
+
+        notifyVideoSizeChanged(width, height);
+    }
+
+    notifyFlagsChanged(
+            FLAG_CAN_PAUSE
+            | FLAG_CAN_SEEK_BACKWARD
+            | FLAG_CAN_SEEK_FORWARD
+            | FLAG_CAN_SEEK);
+
+    notifyPrepared();
+}
+
 void NuPlayer::GenericSource::start() {
     ALOGI("start");
 
@@ -258,8 +282,4 @@
     }
 }
 
-uint32_t NuPlayer::GenericSource::flags() const {
-    return FLAG_SEEKABLE;
-}
-
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h
index e1ce2c1..2da680c 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.h
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.h
@@ -32,12 +32,17 @@
 
 struct NuPlayer::GenericSource : public NuPlayer::Source {
     GenericSource(
+            const sp<AMessage> &notify,
             const char *url,
             const KeyedVector<String8, String8> *headers,
             bool uidValid = false,
             uid_t uid = 0);
 
-    GenericSource(int fd, int64_t offset, int64_t length);
+    GenericSource(
+            const sp<AMessage> &notify,
+            int fd, int64_t offset, int64_t length);
+
+    virtual void prepareAsync();
 
     virtual void start();
 
@@ -48,8 +53,6 @@
     virtual status_t getDuration(int64_t *durationUs);
     virtual status_t seekTo(int64_t seekTimeUs);
 
-    virtual uint32_t flags() const;
-
 protected:
     virtual ~GenericSource();
 
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index 5dcca12..655ee55 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -34,10 +34,12 @@
 namespace android {
 
 NuPlayer::HTTPLiveSource::HTTPLiveSource(
+        const sp<AMessage> &notify,
         const char *url,
         const KeyedVector<String8, String8> *headers,
         bool uidValid, uid_t uid)
-    : mURL(url),
+    : Source(notify),
+      mURL(url),
       mUIDValid(uidValid),
       mUID(uid),
       mFlags(0),
@@ -64,12 +66,15 @@
     }
 }
 
-void NuPlayer::HTTPLiveSource::start() {
+void NuPlayer::HTTPLiveSource::prepareAsync() {
     mLiveLooper = new ALooper;
     mLiveLooper->setName("http live");
     mLiveLooper->start();
 
+    sp<AMessage> notify = new AMessage(kWhatSessionNotify, id());
+
     mLiveSession = new LiveSession(
+            notify,
             (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0,
             mUIDValid, mUID);
 
@@ -81,6 +86,9 @@
     mTSParser = new ATSParser;
 }
 
+void NuPlayer::HTTPLiveSource::start() {
+}
+
 sp<MetaData> NuPlayer::HTTPLiveSource::getFormatMeta(bool audio) {
     ATSParser::SourceType type =
         audio ? ATSParser::AUDIO : ATSParser::VIDEO;
@@ -192,17 +200,58 @@
     return OK;
 }
 
-uint32_t NuPlayer::HTTPLiveSource::flags() const {
-    uint32_t flags = 0;
-    if (mLiveSession->isSeekable()) {
-        flags |= FLAG_SEEKABLE;
-    }
+void NuPlayer::HTTPLiveSource::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatSessionNotify:
+        {
+            onSessionNotify(msg);
+            break;
+        }
 
-    if (mLiveSession->hasDynamicDuration()) {
-        flags |= FLAG_DYNAMIC_DURATION;
+        default:
+            Source::onMessageReceived(msg);
+            break;
     }
+}
 
-    return flags;
+void NuPlayer::HTTPLiveSource::onSessionNotify(const sp<AMessage> &msg) {
+    int32_t what;
+    CHECK(msg->findInt32("what", &what));
+
+    switch (what) {
+        case LiveSession::kWhatPrepared:
+        {
+            notifyVideoSizeChanged(0, 0);
+
+            uint32_t flags = FLAG_CAN_PAUSE;
+            if (mLiveSession->isSeekable()) {
+                flags |= FLAG_CAN_SEEK;
+                flags |= FLAG_CAN_SEEK_BACKWARD;
+                flags |= FLAG_CAN_SEEK_FORWARD;
+            }
+
+            if (mLiveSession->hasDynamicDuration()) {
+                flags |= FLAG_DYNAMIC_DURATION;
+            }
+
+            notifyFlagsChanged(flags);
+
+            notifyPrepared();
+            break;
+        }
+
+        case LiveSession::kWhatPreparationFailed:
+        {
+            status_t err;
+            CHECK(msg->findInt32("err", &err));
+
+            notifyPrepared(err);
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
 }
 
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
index 79f4ab8..067d1da 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
@@ -28,11 +28,13 @@
 
 struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
     HTTPLiveSource(
+            const sp<AMessage> &notify,
             const char *url,
             const KeyedVector<String8, String8> *headers,
             bool uidValid = false,
             uid_t uid = 0);
 
+    virtual void prepareAsync();
     virtual void start();
 
     virtual status_t feedMoreTSData();
@@ -42,19 +44,23 @@
     virtual status_t getDuration(int64_t *durationUs);
     virtual status_t seekTo(int64_t seekTimeUs);
 
-    virtual uint32_t flags() const;
-
 protected:
     virtual ~HTTPLiveSource();
 
     virtual sp<MetaData> getFormatMeta(bool audio);
 
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
 private:
     enum Flags {
         // Don't log any URLs.
         kFlagIncognito = 1,
     };
 
+    enum {
+        kWhatSessionNotify,
+    };
+
     AString mURL;
     KeyedVector<String8, String8> mExtraHeaders;
     bool mUIDValid;
@@ -66,6 +72,8 @@
     sp<LiveSession> mLiveSession;
     sp<ATSParser> mTSParser;
 
+    void onSessionNotify(const sp<AMessage> &msg);
+
     DISALLOW_EVIL_CONSTRUCTORS(HTTPLiveSource);
 };
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 517fb34..30eb4b9 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -112,6 +112,7 @@
 
 NuPlayer::NuPlayer()
     : mUIDValid(false),
+      mSourceFlags(0),
       mVideoIsAVC(false),
       mAudioEOS(false),
       mVideoEOS(false),
@@ -142,15 +143,17 @@
     mDriver = driver;
 }
 
-void NuPlayer::setDataSource(const sp<IStreamSource> &source) {
+void NuPlayer::setDataSourceAsync(const sp<IStreamSource> &source) {
     sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
 
+    sp<AMessage> notify = new AMessage(kWhatSourceNotify, id());
+
     char prop[PROPERTY_VALUE_MAX];
     if (property_get("media.stagefright.use-mp4source", prop, NULL)
             && (!strcmp(prop, "1") || !strcasecmp(prop, "true"))) {
-        msg->setObject("source", new MP4Source(source));
+        msg->setObject("source", new MP4Source(notify, source));
     } else {
-        msg->setObject("source", new StreamingSource(source));
+        msg->setObject("source", new StreamingSource(notify, source));
     }
 
     msg->post();
@@ -172,31 +175,45 @@
     return false;
 }
 
-void NuPlayer::setDataSource(
+void NuPlayer::setDataSourceAsync(
         const char *url, const KeyedVector<String8, String8> *headers) {
     sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
+    size_t len = strlen(url);
+
+    sp<AMessage> notify = new AMessage(kWhatSourceNotify, id());
 
     sp<Source> source;
     if (IsHTTPLiveURL(url)) {
-        source = new HTTPLiveSource(url, headers, mUIDValid, mUID);
+        source = new HTTPLiveSource(notify, url, headers, mUIDValid, mUID);
     } else if (!strncasecmp(url, "rtsp://", 7)) {
-        source = new RTSPSource(url, headers, mUIDValid, mUID);
+        source = new RTSPSource(notify, url, headers, mUIDValid, mUID);
+    } else if ((!strncasecmp(url, "http://", 7)
+                || !strncasecmp(url, "https://", 8))
+                    && ((len >= 4 && !strcasecmp(".sdp", &url[len - 4]))
+                    || strstr(url, ".sdp?"))) {
+        source = new RTSPSource(notify, url, headers, mUIDValid, mUID, true);
     } else {
-        source = new GenericSource(url, headers, mUIDValid, mUID);
+        source = new GenericSource(notify, url, headers, mUIDValid, mUID);
     }
 
     msg->setObject("source", source);
     msg->post();
 }
 
-void NuPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {
     sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
 
-    sp<Source> source = new GenericSource(fd, offset, length);
+    sp<AMessage> notify = new AMessage(kWhatSourceNotify, id());
+
+    sp<Source> source = new GenericSource(notify, fd, offset, length);
     msg->setObject("source", source);
     msg->post();
 }
 
+void NuPlayer::prepareAsync() {
+    (new AMessage(kWhatPrepare, id()))->post();
+}
+
 void NuPlayer::setVideoSurfaceTextureAsync(
         const sp<IGraphicBufferProducer> &bufferProducer) {
     sp<AMessage> msg = new AMessage(kWhatSetVideoNativeWindow, id());
@@ -273,6 +290,20 @@
             CHECK(msg->findObject("source", &obj));
 
             mSource = static_cast<Source *>(obj.get());
+
+            looper()->registerHandler(mSource);
+
+            CHECK(mDriver != NULL);
+            sp<NuPlayerDriver> driver = mDriver.promote();
+            if (driver != NULL) {
+                driver->notifySetDataSourceCompleted(OK);
+            }
+            break;
+        }
+
+        case kWhatPrepare:
+        {
+            mSource->prepareAsync();
             break;
         }
 
@@ -389,9 +420,7 @@
                     && (mAudioDecoder != NULL || mVideoDecoder != NULL)) {
                 // This is the first time we've found anything playable.
 
-                uint32_t flags = mSource->flags();
-
-                if (flags & Source::FLAG_DYNAMIC_DURATION) {
+                if (mSourceFlags & Source::FLAG_DYNAMIC_DURATION) {
                     schedulePollDuration();
                 }
             }
@@ -703,6 +732,7 @@
         case kWhatPause:
         {
             CHECK(mRenderer != NULL);
+            mSource->pause();
             mRenderer->pause();
             break;
         }
@@ -710,10 +740,17 @@
         case kWhatResume:
         {
             CHECK(mRenderer != NULL);
+            mSource->resume();
             mRenderer->resume();
             break;
         }
 
+        case kWhatSourceNotify:
+        {
+            onSourceNotify(msg);
+            break;
+        }
+
         default:
             TRESPASS();
             break;
@@ -1169,6 +1206,9 @@
 
     if (mSource != NULL) {
         mSource->stop();
+
+        looper()->unregisterHandler(mSource->id());
+
         mSource.clear();
     }
 
@@ -1210,4 +1250,94 @@
     }
 }
 
+void NuPlayer::onSourceNotify(const sp<AMessage> &msg) {
+    int32_t what;
+    CHECK(msg->findInt32("what", &what));
+
+    switch (what) {
+        case Source::kWhatPrepared:
+        {
+            int32_t err;
+            CHECK(msg->findInt32("err", &err));
+
+            sp<NuPlayerDriver> driver = mDriver.promote();
+            if (driver != NULL) {
+                driver->notifyPrepareCompleted(err);
+            }
+            break;
+        }
+
+        case Source::kWhatFlagsChanged:
+        {
+            uint32_t flags;
+            CHECK(msg->findInt32("flags", (int32_t *)&flags));
+
+            if ((mSourceFlags & Source::FLAG_DYNAMIC_DURATION)
+                    && (!(flags & Source::FLAG_DYNAMIC_DURATION))) {
+                cancelPollDuration();
+            } else if (!(mSourceFlags & Source::FLAG_DYNAMIC_DURATION)
+                    && (flags & Source::FLAG_DYNAMIC_DURATION)
+                    && (mAudioDecoder != NULL || mVideoDecoder != NULL)) {
+                schedulePollDuration();
+            }
+
+            mSourceFlags = flags;
+            break;
+        }
+
+        case Source::kWhatVideoSizeChanged:
+        {
+            int32_t width, height;
+            CHECK(msg->findInt32("width", &width));
+            CHECK(msg->findInt32("height", &height));
+
+            notifyListener(MEDIA_SET_VIDEO_SIZE, width, height);
+            break;
+        }
+
+        case Source::kWhatBufferingStart:
+        {
+            notifyListener(MEDIA_INFO, MEDIA_INFO_BUFFERING_START, 0);
+            break;
+        }
+
+        case Source::kWhatBufferingEnd:
+        {
+            notifyListener(MEDIA_INFO, MEDIA_INFO_BUFFERING_END, 0);
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void NuPlayer::Source::notifyFlagsChanged(uint32_t flags) {
+    sp<AMessage> notify = dupNotify();
+    notify->setInt32("what", kWhatFlagsChanged);
+    notify->setInt32("flags", flags);
+    notify->post();
+}
+
+void NuPlayer::Source::notifyVideoSizeChanged(int32_t width, int32_t height) {
+    sp<AMessage> notify = dupNotify();
+    notify->setInt32("what", kWhatVideoSizeChanged);
+    notify->setInt32("width", width);
+    notify->setInt32("height", height);
+    notify->post();
+}
+
+void NuPlayer::Source::notifyPrepared(status_t err) {
+    sp<AMessage> notify = dupNotify();
+    notify->setInt32("what", kWhatPrepared);
+    notify->setInt32("err", err);
+    notify->post();
+}
+
+void NuPlayer::Source::onMessageReceived(const sp<AMessage> &msg) {
+    TRESPASS();
+}
+
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 09fc0ba..50d0462 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -35,12 +35,14 @@
 
     void setDriver(const wp<NuPlayerDriver> &driver);
 
-    void setDataSource(const sp<IStreamSource> &source);
+    void setDataSourceAsync(const sp<IStreamSource> &source);
 
-    void setDataSource(
+    void setDataSourceAsync(
             const char *url, const KeyedVector<String8, String8> *headers);
 
-    void setDataSource(int fd, int64_t offset, int64_t length);
+    void setDataSourceAsync(int fd, int64_t offset, int64_t length);
+
+    void prepareAsync();
 
     void setVideoSurfaceTextureAsync(
             const sp<IGraphicBufferProducer> &bufferProducer);
@@ -82,6 +84,7 @@
 
     enum {
         kWhatSetDataSource              = '=DaS',
+        kWhatPrepare                    = 'prep',
         kWhatSetVideoNativeWindow       = '=NaW',
         kWhatSetAudioSink               = '=AuS',
         kWhatMoreDataQueued             = 'more',
@@ -95,12 +98,14 @@
         kWhatPause                      = 'paus',
         kWhatResume                     = 'rsme',
         kWhatPollDuration               = 'polD',
+        kWhatSourceNotify               = 'srcN',
     };
 
     wp<NuPlayerDriver> mDriver;
     bool mUIDValid;
     uid_t mUID;
     sp<Source> mSource;
+    uint32_t mSourceFlags;
     sp<NativeWindowWrapper> mNativeWindow;
     sp<MediaPlayerBase::AudioSink> mAudioSink;
     sp<Decoder> mVideoDecoder;
@@ -172,6 +177,8 @@
     void performScanSources();
     void performSetSurface(const sp<NativeWindowWrapper> &wrapper);
 
+    void onSourceNotify(const sp<AMessage> &msg);
+
     DISALLOW_EVIL_CONSTRUCTORS(NuPlayer);
 };
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 7043404..3c63e80 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -21,21 +21,25 @@
 #include "NuPlayerDriver.h"
 
 #include "NuPlayer.h"
+#include "NuPlayerSource.h"
 
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/MetaData.h>
 
 namespace android {
 
 NuPlayerDriver::NuPlayerDriver()
-    : mResetInProgress(false),
+    : mState(STATE_IDLE),
+      mIsAsyncPrepare(false),
+      mAsyncResult(UNKNOWN_ERROR),
       mSetSurfaceInProgress(false),
       mDurationUs(-1),
       mPositionUs(-1),
       mNumFramesTotal(0),
       mNumFramesDropped(0),
       mLooper(new ALooper),
-      mState(UNINITIALIZED),
+      mPlayerFlags(0),
       mAtEOS(false),
       mStartupSeekTimeUs(-1) {
     mLooper->setName("NuPlayerDriver Looper");
@@ -67,43 +71,76 @@
 
 status_t NuPlayerDriver::setDataSource(
         const char *url, const KeyedVector<String8, String8> *headers) {
-    CHECK_EQ((int)mState, (int)UNINITIALIZED);
+    Mutex::Autolock autoLock(mLock);
 
-    mPlayer->setDataSource(url, headers);
+    if (mState != STATE_IDLE) {
+        return INVALID_OPERATION;
+    }
 
-    mState = STOPPED;
+    mState = STATE_SET_DATASOURCE_PENDING;
 
-    return OK;
+    mPlayer->setDataSourceAsync(url, headers);
+
+    while (mState == STATE_SET_DATASOURCE_PENDING) {
+        mCondition.wait(mLock);
+    }
+
+    return mAsyncResult;
 }
 
 status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
-    CHECK_EQ((int)mState, (int)UNINITIALIZED);
+    Mutex::Autolock autoLock(mLock);
 
-    mPlayer->setDataSource(fd, offset, length);
+    if (mState != STATE_IDLE) {
+        return INVALID_OPERATION;
+    }
 
-    mState = STOPPED;
+    mState = STATE_SET_DATASOURCE_PENDING;
 
-    return OK;
+    mPlayer->setDataSourceAsync(fd, offset, length);
+
+    while (mState == STATE_SET_DATASOURCE_PENDING) {
+        mCondition.wait(mLock);
+    }
+
+    return mAsyncResult;
 }
 
 status_t NuPlayerDriver::setDataSource(const sp<IStreamSource> &source) {
-    CHECK_EQ((int)mState, (int)UNINITIALIZED);
+    Mutex::Autolock autoLock(mLock);
 
-    mPlayer->setDataSource(source);
+    if (mState != STATE_IDLE) {
+        return INVALID_OPERATION;
+    }
 
-    mState = STOPPED;
+    mState = STATE_SET_DATASOURCE_PENDING;
 
-    return OK;
+    mPlayer->setDataSourceAsync(source);
+
+    while (mState == STATE_SET_DATASOURCE_PENDING) {
+        mCondition.wait(mLock);
+    }
+
+    return mAsyncResult;
 }
 
 status_t NuPlayerDriver::setVideoSurfaceTexture(
         const sp<IGraphicBufferProducer> &bufferProducer) {
     Mutex::Autolock autoLock(mLock);
 
-    if (mResetInProgress) {
+    if (mSetSurfaceInProgress) {
         return INVALID_OPERATION;
     }
 
+    switch (mState) {
+        case STATE_SET_DATASOURCE_PENDING:
+        case STATE_RESET_IN_PROGRESS:
+            return INVALID_OPERATION;
+
+        default:
+            break;
+    }
+
     mSetSurfaceInProgress = true;
 
     mPlayer->setVideoSurfaceTextureAsync(bufferProducer);
@@ -116,23 +153,61 @@
 }
 
 status_t NuPlayerDriver::prepare() {
-    sendEvent(MEDIA_SET_VIDEO_SIZE, 0, 0);
-    return OK;
+    Mutex::Autolock autoLock(mLock);
+    return prepare_l();
+}
+
+status_t NuPlayerDriver::prepare_l() {
+    switch (mState) {
+        case STATE_UNPREPARED:
+            mState = STATE_PREPARING;
+
+            // Make sure we're not posting any notifications, success or
+            // failure information is only communicated through our result
+            // code.
+            mIsAsyncPrepare = false;
+            mPlayer->prepareAsync();
+            while (mState == STATE_PREPARING) {
+                mCondition.wait(mLock);
+            }
+            return (mState == STATE_PREPARED) ? OK : UNKNOWN_ERROR;
+        default:
+            return INVALID_OPERATION;
+    };
 }
 
 status_t NuPlayerDriver::prepareAsync() {
-    status_t err = prepare();
+    Mutex::Autolock autoLock(mLock);
 
-    notifyListener(MEDIA_PREPARED);
-
-    return err;
+    switch (mState) {
+        case STATE_UNPREPARED:
+            mState = STATE_PREPARING;
+            mIsAsyncPrepare = true;
+            mPlayer->prepareAsync();
+            return OK;
+        default:
+            return INVALID_OPERATION;
+    };
 }
 
 status_t NuPlayerDriver::start() {
+    Mutex::Autolock autoLock(mLock);
+
     switch (mState) {
-        case UNINITIALIZED:
-            return INVALID_OPERATION;
-        case STOPPED:
+        case STATE_UNPREPARED:
+        {
+            status_t err = prepare_l();
+
+            if (err != OK) {
+                return err;
+            }
+
+            CHECK_EQ(mState, STATE_PREPARED);
+
+            // fall through
+        }
+
+        case STATE_PREPARED:
         {
             mAtEOS = false;
             mPlayer->start();
@@ -146,21 +221,23 @@
 
                 mStartupSeekTimeUs = -1;
             }
-
             break;
         }
-        case PLAYING:
-            return OK;
-        default:
-        {
-            CHECK_EQ((int)mState, (int)PAUSED);
 
+        case STATE_RUNNING:
+            break;
+
+        case STATE_PAUSED:
+        {
             mPlayer->resume();
             break;
         }
+
+        default:
+            return INVALID_OPERATION;
     }
 
-    mState = PLAYING;
+    mState = STATE_RUNNING;
 
     return OK;
 }
@@ -170,43 +247,44 @@
 }
 
 status_t NuPlayerDriver::pause() {
+    Mutex::Autolock autoLock(mLock);
+
     switch (mState) {
-        case UNINITIALIZED:
-            return INVALID_OPERATION;
-        case STOPPED:
+        case STATE_PAUSED:
+        case STATE_PREPARED:
             return OK;
-        case PLAYING:
+
+        case STATE_RUNNING:
             mPlayer->pause();
             break;
+
         default:
-        {
-            CHECK_EQ((int)mState, (int)PAUSED);
-            return OK;
-        }
+            return INVALID_OPERATION;
     }
 
-    mState = PAUSED;
+    mState = STATE_PAUSED;
 
     return OK;
 }
 
 bool NuPlayerDriver::isPlaying() {
-    return mState == PLAYING && !mAtEOS;
+    return mState == STATE_RUNNING && !mAtEOS;
 }
 
 status_t NuPlayerDriver::seekTo(int msec) {
+    Mutex::Autolock autoLock(mLock);
+
     int64_t seekTimeUs = msec * 1000ll;
 
     switch (mState) {
-        case UNINITIALIZED:
-            return INVALID_OPERATION;
-        case STOPPED:
+        case STATE_PREPARED:
         {
             mStartupSeekTimeUs = seekTimeUs;
             break;
         }
-        case PLAYING:
-        case PAUSED:
+
+        case STATE_RUNNING:
+        case STATE_PAUSED:
         {
             mAtEOS = false;
             mPlayer->seekToAsync(seekTimeUs);
@@ -214,8 +292,7 @@
         }
 
         default:
-            TRESPASS();
-            break;
+            return INVALID_OPERATION;
     }
 
     return OK;
@@ -247,17 +324,28 @@
 
 status_t NuPlayerDriver::reset() {
     Mutex::Autolock autoLock(mLock);
-    mResetInProgress = true;
 
+    switch (mState) {
+        case STATE_IDLE:
+            return OK;
+
+        case STATE_SET_DATASOURCE_PENDING:
+        case STATE_RESET_IN_PROGRESS:
+            return INVALID_OPERATION;
+
+        default:
+            break;
+    }
+
+    mState = STATE_RESET_IN_PROGRESS;
     mPlayer->resetAsync();
 
-    while (mResetInProgress) {
+    while (mState == STATE_RESET_IN_PROGRESS) {
         mCondition.wait(mLock);
     }
 
     mDurationUs = -1;
     mPositionUs = -1;
-    mState = UNINITIALIZED;
     mStartupSeekTimeUs = -1;
 
     return OK;
@@ -311,20 +399,45 @@
 
 status_t NuPlayerDriver::getMetadata(
         const media::Metadata::Filter& ids, Parcel *records) {
-    return INVALID_OPERATION;
+    Mutex::Autolock autoLock(mLock);
+
+    using media::Metadata;
+
+    Metadata meta(records);
+
+    meta.appendBool(
+            Metadata::kPauseAvailable,
+            mPlayerFlags & NuPlayer::Source::FLAG_CAN_PAUSE);
+
+    meta.appendBool(
+            Metadata::kSeekBackwardAvailable,
+            mPlayerFlags & NuPlayer::Source::FLAG_CAN_SEEK_BACKWARD);
+
+    meta.appendBool(
+            Metadata::kSeekForwardAvailable,
+            mPlayerFlags & NuPlayer::Source::FLAG_CAN_SEEK_FORWARD);
+
+    meta.appendBool(
+            Metadata::kSeekAvailable,
+            mPlayerFlags & NuPlayer::Source::FLAG_CAN_SEEK);
+
+    return OK;
 }
 
 void NuPlayerDriver::notifyResetComplete() {
     Mutex::Autolock autoLock(mLock);
-    CHECK(mResetInProgress);
-    mResetInProgress = false;
+
+    CHECK_EQ(mState, STATE_RESET_IN_PROGRESS);
+    mState = STATE_IDLE;
     mCondition.broadcast();
 }
 
 void NuPlayerDriver::notifySetSurfaceComplete() {
     Mutex::Autolock autoLock(mLock);
+
     CHECK(mSetSurfaceInProgress);
     mSetSurfaceInProgress = false;
+
     mCondition.broadcast();
 }
 
@@ -376,4 +489,42 @@
     sendEvent(msg, ext1, ext2);
 }
 
+void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) {
+    Mutex::Autolock autoLock(mLock);
+
+    CHECK_EQ(mState, STATE_SET_DATASOURCE_PENDING);
+
+    mAsyncResult = err;
+    mState = (err == OK) ? STATE_UNPREPARED : STATE_IDLE;
+    mCondition.broadcast();
+}
+
+void NuPlayerDriver::notifyPrepareCompleted(status_t err) {
+    Mutex::Autolock autoLock(mLock);
+
+    CHECK_EQ(mState, STATE_PREPARING);
+
+    mAsyncResult = err;
+
+    if (err == OK) {
+        if (mIsAsyncPrepare) {
+            notifyListener(MEDIA_PREPARED);
+        }
+        mState = STATE_PREPARED;
+    } else {
+        if (mIsAsyncPrepare) {
+            notifyListener(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
+        }
+        mState = STATE_UNPREPARED;
+    }
+
+    mCondition.broadcast();
+}
+
+void NuPlayerDriver::notifyFlagsChanged(uint32_t flags) {
+    Mutex::Autolock autoLock(mLock);
+
+    mPlayerFlags = flags;
+}
+
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 553c406..5df0cfb 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -61,6 +61,8 @@
 
     virtual status_t dump(int fd, const Vector<String16> &args) const;
 
+    void notifySetDataSourceCompleted(status_t err);
+    void notifyPrepareCompleted(status_t err);
     void notifyResetComplete();
     void notifySetSurfaceComplete();
     void notifyDuration(int64_t durationUs);
@@ -68,17 +70,33 @@
     void notifySeekComplete();
     void notifyFrameStats(int64_t numFramesTotal, int64_t numFramesDropped);
     void notifyListener(int msg, int ext1 = 0, int ext2 = 0);
+    void notifyFlagsChanged(uint32_t flags);
 
 protected:
     virtual ~NuPlayerDriver();
 
 private:
+    enum State {
+        STATE_IDLE,
+        STATE_SET_DATASOURCE_PENDING,
+        STATE_UNPREPARED,
+        STATE_PREPARING,
+        STATE_PREPARED,
+        STATE_RUNNING,
+        STATE_PAUSED,
+        STATE_RESET_IN_PROGRESS,
+    };
+
     mutable Mutex mLock;
     Condition mCondition;
 
+    State mState;
+
+    bool mIsAsyncPrepare;
+    status_t mAsyncResult;
+
     // The following are protected through "mLock"
     // >>>
-    bool mResetInProgress;
     bool mSetSurfaceInProgress;
     int64_t mDurationUs;
     int64_t mPositionUs;
@@ -88,19 +106,14 @@
 
     sp<ALooper> mLooper;
     sp<NuPlayer> mPlayer;
+    uint32_t mPlayerFlags;
 
-    enum State {
-        UNINITIALIZED,
-        STOPPED,
-        PLAYING,
-        PAUSED
-    };
-
-    State mState;
     bool mAtEOS;
 
     int64_t mStartupSeekTimeUs;
 
+    status_t prepare_l();
+
     DISALLOW_EVIL_CONSTRUCTORS(NuPlayerDriver);
 };
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 8a75f83..1ba76a5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -512,9 +512,15 @@
     entry.mFinalResult = finalResult;
 
     if (audio) {
+        if (mAudioQueue.empty() && mSyncQueues) {
+            syncQueuesDone();
+        }
         mAudioQueue.push_back(entry);
         postDrainAudioQueue();
     } else {
+        if (mVideoQueue.empty() && mSyncQueues) {
+            syncQueuesDone();
+        }
         mVideoQueue.push_back(entry);
         postDrainVideoQueue();
     }
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
index a635340..8622abe 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
@@ -20,20 +20,42 @@
 
 #include "NuPlayer.h"
 
+#include <media/stagefright/foundation/AMessage.h>
+
 namespace android {
 
 struct ABuffer;
+struct MetaData;
 
-struct NuPlayer::Source : public RefBase {
+struct NuPlayer::Source : public AHandler {
     enum Flags {
-        FLAG_SEEKABLE           = 1,
-        FLAG_DYNAMIC_DURATION   = 2,
+        FLAG_CAN_PAUSE          = 1,
+        FLAG_CAN_SEEK_BACKWARD  = 2,  // the "10 sec back button"
+        FLAG_CAN_SEEK_FORWARD   = 4,  // the "10 sec forward button"
+        FLAG_CAN_SEEK           = 8,  // the "seek bar"
+        FLAG_DYNAMIC_DURATION   = 16,
     };
 
-    Source() {}
+    enum {
+        kWhatPrepared,
+        kWhatFlagsChanged,
+        kWhatVideoSizeChanged,
+        kWhatBufferingStart,
+        kWhatBufferingEnd,
+    };
+
+    // The provides message is used to notify the player about various
+    // events.
+    Source(const sp<AMessage> &notify)
+        : mNotify(notify) {
+    }
+
+    virtual void prepareAsync() = 0;
 
     virtual void start() = 0;
     virtual void stop() {}
+    virtual void pause() {}
+    virtual void resume() {}
 
     // Returns OK iff more data was available,
     // an error or ERROR_END_OF_STREAM if not.
@@ -52,14 +74,22 @@
         return INVALID_OPERATION;
     }
 
-    virtual uint32_t flags() const = 0;
-
 protected:
     virtual ~Source() {}
 
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
     virtual sp<MetaData> getFormatMeta(bool audio) { return NULL; }
 
+    sp<AMessage> dupNotify() const { return mNotify->dup(); }
+
+    void notifyFlagsChanged(uint32_t flags);
+    void notifyVideoSizeChanged(int32_t width, int32_t height);
+    void notifyPrepared(status_t err = OK);
+
 private:
+    sp<AMessage> mNotify;
+
     DISALLOW_EVIL_CONSTRUCTORS(Source);
 };
 
diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
index afaa5db..a5ff0ca 100644
--- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
@@ -22,26 +22,35 @@
 
 #include "AnotherPacketSource.h"
 #include "MyHandler.h"
+#include "SDPLoader.h"
 
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
 
 namespace android {
 
+const int64_t kNearEOSTimeoutUs = 2000000ll; // 2 secs
+
 NuPlayer::RTSPSource::RTSPSource(
+        const sp<AMessage> &notify,
         const char *url,
         const KeyedVector<String8, String8> *headers,
         bool uidValid,
-        uid_t uid)
-    : mURL(url),
+        uid_t uid,
+        bool isSDP)
+    : Source(notify),
+      mURL(url),
       mUIDValid(uidValid),
       mUID(uid),
       mFlags(0),
+      mIsSDP(isSDP),
       mState(DISCONNECTED),
       mFinalResult(OK),
       mDisconnectReplyID(0),
-      mStartingUp(true),
-      mSeekGeneration(0) {
+      mBuffering(true),
+      mSeekGeneration(0),
+      mEOSTimeoutAudio(0),
+      mEOSTimeoutVideo(0) {
     if (headers) {
         mExtraHeaders = *headers;
 
@@ -60,7 +69,7 @@
    mLooper->stop();
 }
 
-void NuPlayer::RTSPSource::start() {
+void NuPlayer::RTSPSource::prepareAsync() {
     if (mLooper == NULL) {
         mLooper = new ALooper;
         mLooper->setName("rtsp");
@@ -71,16 +80,33 @@
     }
 
     CHECK(mHandler == NULL);
+    CHECK(mSDPLoader == NULL);
 
     sp<AMessage> notify = new AMessage(kWhatNotify, mReflector->id());
 
-    mHandler = new MyHandler(mURL.c_str(), notify, mUIDValid, mUID);
-    mLooper->registerHandler(mHandler);
-
     CHECK_EQ(mState, (int)DISCONNECTED);
     mState = CONNECTING;
 
-    mHandler->connect();
+    if (mIsSDP) {
+        mSDPLoader = new SDPLoader(notify,
+                (mFlags & kFlagIncognito) ? SDPLoader::kFlagIncognito : 0,
+                mUIDValid, mUID);
+
+        mSDPLoader->load(
+                mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
+    } else {
+        mHandler = new MyHandler(mURL.c_str(), notify, mUIDValid, mUID);
+        mLooper->registerHandler(mHandler);
+
+        mHandler->connect();
+    }
+
+    sp<AMessage> notifyStart = dupNotify();
+    notifyStart->setInt32("what", kWhatBufferingStart);
+    notifyStart->post();
+}
+
+void NuPlayer::RTSPSource::start() {
 }
 
 void NuPlayer::RTSPSource::stop() {
@@ -93,6 +119,25 @@
     msg->postAndAwaitResponse(&dummy);
 }
 
+void NuPlayer::RTSPSource::pause() {
+    int64_t mediaDurationUs = 0;
+    getDuration(&mediaDurationUs);
+    for (size_t index = 0; index < mTracks.size(); index++) {
+        TrackInfo *info = &mTracks.editItemAt(index);
+        sp<AnotherPacketSource> source = info->mSource;
+
+        // Check if EOS or ERROR is received
+        if (source != NULL && source->isFinished(mediaDurationUs)) {
+            return;
+        }
+    }
+    mHandler->pause();
+}
+
+void NuPlayer::RTSPSource::resume() {
+    mHandler->resume();
+}
+
 status_t NuPlayer::RTSPSource::feedMoreTSData() {
     return mFinalResult;
 }
@@ -113,6 +158,13 @@
 
     static const int64_t kMinDurationUs = 2000000ll;
 
+    int64_t mediaDurationUs = 0;
+    getDuration(&mediaDurationUs);
+    if ((mAudioTrack != NULL && mAudioTrack->isFinished(mediaDurationUs))
+            || (mVideoTrack != NULL && mVideoTrack->isFinished(mediaDurationUs))) {
+        return true;
+    }
+
     status_t err;
     int64_t durationUs;
     if (mAudioTrack != NULL
@@ -138,12 +190,16 @@
 
 status_t NuPlayer::RTSPSource::dequeueAccessUnit(
         bool audio, sp<ABuffer> *accessUnit) {
-    if (mStartingUp) {
+    if (mBuffering) {
         if (!haveSufficientDataOnAllTracks()) {
             return -EWOULDBLOCK;
         }
 
-        mStartingUp = false;
+        mBuffering = false;
+
+        sp<AMessage> notify = dupNotify();
+        notify->setInt32("what", kWhatBufferingEnd);
+        notify->post();
     }
 
     sp<AnotherPacketSource> source = getSource(audio);
@@ -154,9 +210,51 @@
 
     status_t finalResult;
     if (!source->hasBufferAvailable(&finalResult)) {
-        return finalResult == OK ? -EWOULDBLOCK : finalResult;
+        if (finalResult == OK) {
+            int64_t mediaDurationUs = 0;
+            getDuration(&mediaDurationUs);
+            sp<AnotherPacketSource> otherSource = getSource(!audio);
+            status_t otherFinalResult;
+
+            // If other source already signaled EOS, this source should also signal EOS
+            if (otherSource != NULL &&
+                    !otherSource->hasBufferAvailable(&otherFinalResult) &&
+                    otherFinalResult == ERROR_END_OF_STREAM) {
+                source->signalEOS(ERROR_END_OF_STREAM);
+                return ERROR_END_OF_STREAM;
+            }
+
+            // If this source has detected near end, give it some time to retrieve more
+            // data before signaling EOS
+            if (source->isFinished(mediaDurationUs)) {
+                int64_t eosTimeout = audio ? mEOSTimeoutAudio : mEOSTimeoutVideo;
+                if (eosTimeout == 0) {
+                    setEOSTimeout(audio, ALooper::GetNowUs());
+                } else if ((ALooper::GetNowUs() - eosTimeout) > kNearEOSTimeoutUs) {
+                    setEOSTimeout(audio, 0);
+                    source->signalEOS(ERROR_END_OF_STREAM);
+                    return ERROR_END_OF_STREAM;
+                }
+                return -EWOULDBLOCK;
+            }
+
+            if (!(otherSource != NULL && otherSource->isFinished(mediaDurationUs))) {
+                // We should not enter buffering mode
+                // if any of the sources already have detected EOS.
+                mBuffering = true;
+
+                sp<AMessage> notify = dupNotify();
+                notify->setInt32("what", kWhatBufferingStart);
+                notify->post();
+            }
+
+            return -EWOULDBLOCK;
+        }
+        return finalResult;
     }
 
+    setEOSTimeout(audio, 0);
+
     return source->dequeueAccessUnit(accessUnit);
 }
 
@@ -171,6 +269,14 @@
     return audio ? mAudioTrack : mVideoTrack;
 }
 
+void NuPlayer::RTSPSource::setEOSTimeout(bool audio, int64_t timeout) {
+    if (audio) {
+        mEOSTimeoutAudio = timeout;
+    } else {
+        mEOSTimeoutVideo = timeout;
+    }
+}
+
 status_t NuPlayer::RTSPSource::getDuration(int64_t *durationUs) {
     *durationUs = 0ll;
 
@@ -211,10 +317,6 @@
     mHandler->seek(seekTimeUs);
 }
 
-uint32_t NuPlayer::RTSPSource::flags() const {
-    return FLAG_SEEKABLE;
-}
-
 void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) {
     if (msg->what() == kWhatDisconnect) {
         uint32_t replyID;
@@ -246,17 +348,35 @@
 
     switch (what) {
         case MyHandler::kWhatConnected:
+        {
             onConnected();
+
+            notifyVideoSizeChanged(0, 0);
+
+            uint32_t flags = 0;
+
+            if (mHandler->isSeekable()) {
+                flags = FLAG_CAN_PAUSE | FLAG_CAN_SEEK;
+
+                // Seeking 10secs forward or backward is a very expensive
+                // operation for rtsp, so let's not enable that.
+                // The user can always use the seek bar.
+            }
+
+            notifyFlagsChanged(flags);
+            notifyPrepared();
             break;
+        }
 
         case MyHandler::kWhatDisconnected:
+        {
             onDisconnected(msg);
             break;
+        }
 
         case MyHandler::kWhatSeekDone:
         {
             mState = CONNECTED;
-            mStartingUp = true;
             break;
         }
 
@@ -406,6 +526,12 @@
             break;
         }
 
+        case SDPLoader::kWhatSDPLoaded:
+        {
+            onSDPLoaded(msg);
+            break;
+        }
+
         default:
             TRESPASS();
     }
@@ -459,6 +585,52 @@
     mState = CONNECTED;
 }
 
+void NuPlayer::RTSPSource::onSDPLoaded(const sp<AMessage> &msg) {
+    status_t err;
+    CHECK(msg->findInt32("result", &err));
+
+    mSDPLoader.clear();
+
+    if (mDisconnectReplyID != 0) {
+        err = UNKNOWN_ERROR;
+    }
+
+    if (err == OK) {
+        sp<ASessionDescription> desc;
+        sp<RefBase> obj;
+        CHECK(msg->findObject("description", &obj));
+        desc = static_cast<ASessionDescription *>(obj.get());
+
+        AString rtspUri;
+        if (!desc->findAttribute(0, "a=control", &rtspUri)) {
+            ALOGE("Unable to find url in SDP");
+            err = UNKNOWN_ERROR;
+        } else {
+            sp<AMessage> notify = new AMessage(kWhatNotify, mReflector->id());
+
+            mHandler = new MyHandler(rtspUri.c_str(), notify, mUIDValid, mUID);
+            mLooper->registerHandler(mHandler);
+
+            mHandler->loadSDP(desc);
+        }
+    }
+
+    if (err != OK) {
+        if (mState == CONNECTING) {
+            // We're still in the preparation phase, signal that it
+            // failed.
+            notifyPrepared(err);
+        }
+
+        mState = DISCONNECTED;
+        mFinalResult = err;
+
+        if (mDisconnectReplyID != 0) {
+            finishDisconnectIfPossible();
+        }
+    }
+}
+
 void NuPlayer::RTSPSource::onDisconnected(const sp<AMessage> &msg) {
     status_t err;
     CHECK(msg->findInt32("result", &err));
@@ -467,6 +639,12 @@
     mLooper->unregisterHandler(mHandler->id());
     mHandler.clear();
 
+    if (mState == CONNECTING) {
+        // We're still in the preparation phase, signal that it
+        // failed.
+        notifyPrepared(err);
+    }
+
     mState = DISCONNECTED;
     mFinalResult = err;
 
@@ -477,7 +655,11 @@
 
 void NuPlayer::RTSPSource::finishDisconnectIfPossible() {
     if (mState != DISCONNECTED) {
-        mHandler->disconnect();
+        if (mHandler != NULL) {
+            mHandler->disconnect();
+        } else if (mSDPLoader != NULL) {
+            mSDPLoader->cancel();
+        }
         return;
     }
 
diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.h b/media/libmediaplayerservice/nuplayer/RTSPSource.h
index 779d791..8cf34a0 100644
--- a/media/libmediaplayerservice/nuplayer/RTSPSource.h
+++ b/media/libmediaplayerservice/nuplayer/RTSPSource.h
@@ -29,16 +29,22 @@
 struct ALooper;
 struct AnotherPacketSource;
 struct MyHandler;
+struct SDPLoader;
 
 struct NuPlayer::RTSPSource : public NuPlayer::Source {
     RTSPSource(
+            const sp<AMessage> &notify,
             const char *url,
             const KeyedVector<String8, String8> *headers,
             bool uidValid = false,
-            uid_t uid = 0);
+            uid_t uid = 0,
+            bool isSDP = false);
 
+    virtual void prepareAsync();
     virtual void start();
     virtual void stop();
+    virtual void pause();
+    virtual void resume();
 
     virtual status_t feedMoreTSData();
 
@@ -47,8 +53,6 @@
     virtual status_t getDuration(int64_t *durationUs);
     virtual status_t seekTo(int64_t seekTimeUs);
 
-    virtual uint32_t flags() const;
-
     void onMessageReceived(const sp<AMessage> &msg);
 
 protected:
@@ -89,14 +93,16 @@
     bool mUIDValid;
     uid_t mUID;
     uint32_t mFlags;
+    bool mIsSDP;
     State mState;
     status_t mFinalResult;
     uint32_t mDisconnectReplyID;
-    bool mStartingUp;
+    bool mBuffering;
 
     sp<ALooper> mLooper;
     sp<AHandlerReflector<RTSPSource> > mReflector;
     sp<MyHandler> mHandler;
+    sp<SDPLoader> mSDPLoader;
 
     Vector<TrackInfo> mTracks;
     sp<AnotherPacketSource> mAudioTrack;
@@ -106,9 +112,13 @@
 
     int32_t mSeekGeneration;
 
+    int64_t mEOSTimeoutAudio;
+    int64_t mEOSTimeoutVideo;
+
     sp<AnotherPacketSource> getSource(bool audio);
 
     void onConnected();
+    void onSDPLoaded(const sp<AMessage> &msg);
     void onDisconnected(const sp<AMessage> &msg);
     void finishDisconnectIfPossible();
 
@@ -116,6 +126,8 @@
 
     bool haveSufficientDataOnAllTracks();
 
+    void setEOSTimeout(bool audio, int64_t timeout);
+
     DISALLOW_EVIL_CONSTRUCTORS(RTSPSource);
 };
 
diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
index 7159404..df03f86 100644
--- a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
@@ -32,14 +32,23 @@
 
 namespace android {
 
-NuPlayer::StreamingSource::StreamingSource(const sp<IStreamSource> &source)
-    : mSource(source),
+NuPlayer::StreamingSource::StreamingSource(
+        const sp<AMessage> &notify,
+        const sp<IStreamSource> &source)
+    : Source(notify),
+      mSource(source),
       mFinalResult(OK) {
 }
 
 NuPlayer::StreamingSource::~StreamingSource() {
 }
 
+void NuPlayer::StreamingSource::prepareAsync() {
+    notifyVideoSizeChanged(0, 0);
+    notifyFlagsChanged(0);
+    notifyPrepared();
+}
+
 void NuPlayer::StreamingSource::start() {
     mStreamListener = new NuPlayerStreamListener(mSource, 0);
 
@@ -173,9 +182,5 @@
     return err;
 }
 
-uint32_t NuPlayer::StreamingSource::flags() const {
-    return 0;
-}
-
 }  // namespace android
 
diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.h b/media/libmediaplayerservice/nuplayer/StreamingSource.h
index a27b58a..80b061c 100644
--- a/media/libmediaplayerservice/nuplayer/StreamingSource.h
+++ b/media/libmediaplayerservice/nuplayer/StreamingSource.h
@@ -27,16 +27,17 @@
 struct ATSParser;
 
 struct NuPlayer::StreamingSource : public NuPlayer::Source {
-    StreamingSource(const sp<IStreamSource> &source);
+    StreamingSource(
+            const sp<AMessage> &notify,
+            const sp<IStreamSource> &source);
 
+    virtual void prepareAsync();
     virtual void start();
 
     virtual status_t feedMoreTSData();
 
     virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
 
-    virtual uint32_t flags() const;
-
 protected:
     virtual ~StreamingSource();
 
diff --git a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp
index a62d5a2..d31d947 100644
--- a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp
+++ b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp
@@ -104,8 +104,10 @@
     DISALLOW_EVIL_CONSTRUCTORS(StreamSource);
 };
 
-MP4Source::MP4Source(const sp<IStreamSource> &source)
-    : mSource(source),
+MP4Source::MP4Source(
+        const sp<AMessage> &notify, const sp<IStreamSource> &source)
+    : Source(notify),
+      mSource(source),
       mLooper(new ALooper),
       mParser(new FragmentedMP4Parser),
       mEOS(false) {
@@ -115,6 +117,12 @@
 MP4Source::~MP4Source() {
 }
 
+void MP4Source::prepareAsync() {
+    notifyVideoSizeChanged(0, 0);
+    notifyFlagsChanged(0);
+    notifyPrepared();
+}
+
 void MP4Source::start() {
     mLooper->start(false /* runOnCallingThread */);
     mParser->start(new StreamSource(mSource));
@@ -133,8 +141,4 @@
     return mParser->dequeueAccessUnit(audio, accessUnit);
 }
 
-uint32_t MP4Source::flags() const {
-    return 0;
-}
-
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h
index abca236..a6ef622 100644
--- a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h
+++ b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h
@@ -24,8 +24,9 @@
 struct FragmentedMP4Parser;
 
 struct MP4Source : public NuPlayer::Source {
-    MP4Source(const sp<IStreamSource> &source);
+    MP4Source(const sp<AMessage> &notify, const sp<IStreamSource> &source);
 
+    virtual void prepareAsync();
     virtual void start();
 
     virtual status_t feedMoreTSData();
@@ -35,8 +36,6 @@
     virtual status_t dequeueAccessUnit(
             bool audio, sp<ABuffer> *accessUnit);
 
-    virtual uint32_t flags() const;
-
 protected:
     virtual ~MP4Source();
 
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 7b27843..a6cc4eb 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -374,6 +374,12 @@
     msg->post();
 }
 
+void ACodec::signalSetParameters(const sp<AMessage> &params) {
+    sp<AMessage> msg = new AMessage(kWhatSetParameters, id());
+    msg->setMessage("params", params);
+    msg->post();
+}
+
 void ACodec::initiateAllocateComponent(const sp<AMessage> &msg) {
     msg->setWhat(kWhatAllocateComponent);
     msg->setTarget(id());
@@ -3550,6 +3556,23 @@
             break;
         }
 
+        case kWhatSetParameters:
+        {
+            sp<AMessage> params;
+            CHECK(msg->findMessage("params", &params));
+
+            status_t err = mCodec->setParameters(params);
+
+            sp<AMessage> reply;
+            if (msg->findMessage("reply", &reply)) {
+                reply->setInt32("err", err);
+                reply->post();
+            }
+
+            handled = true;
+            break;
+        }
+
         default:
             handled = BaseState::onMessageReceived(msg);
             break;
@@ -3558,6 +3581,31 @@
     return handled;
 }
 
+status_t ACodec::setParameters(const sp<AMessage> &params) {
+    int32_t videoBitrate;
+    if (params->findInt32("videoBitrate", &videoBitrate)) {
+        OMX_VIDEO_CONFIG_BITRATETYPE configParams;
+        InitOMXParams(&configParams);
+        configParams.nPortIndex = kPortIndexOutput;
+        configParams.nEncodeBitrate = videoBitrate;
+
+        status_t err = mOMX->setConfig(
+                mNode,
+                OMX_IndexConfigVideoBitrate,
+                &configParams,
+                sizeof(configParams));
+
+        if (err != OK) {
+            ALOGE("setConfig(OMX_IndexConfigVideoBitrate, %d) failed w/ err %d",
+                   videoBitrate, err);
+
+            return err;
+        }
+    }
+
+    return OK;
+}
+
 bool ACodec::ExecutingState::onOMXEvent(
         OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
     switch (event) {
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 23ce088..0f4d866 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -2511,6 +2511,7 @@
         if (err != OK) {
             ALOGW("Failed to set scaling mode: %d", err);
         }
+        return err;
     }
     return OK;
 }
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index cb8a651..77aceb7 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -1203,6 +1203,23 @@
             break;
         }
 
+        case kWhatSetParameters:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> params;
+            CHECK(msg->findMessage("params", &params));
+
+            status_t err = onSetParameters(params);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+
+            response->postReply(replyID);
+            break;
+        }
+
         default:
             TRESPASS();
     }
@@ -1556,4 +1573,18 @@
     }
 }
 
+status_t MediaCodec::setParameters(const sp<AMessage> &params) {
+    sp<AMessage> msg = new AMessage(kWhatSetParameters, id());
+    msg->setMessage("params", params);
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::onSetParameters(const sp<AMessage> &params) {
+    mCodec->signalSetParameters(params);
+
+    return OK;
+}
+
 }  // namespace android
diff --git a/media/libstagefright/foundation/ALooperRoster.cpp b/media/libstagefright/foundation/ALooperRoster.cpp
index dff931d..ad10d2b 100644
--- a/media/libstagefright/foundation/ALooperRoster.cpp
+++ b/media/libstagefright/foundation/ALooperRoster.cpp
@@ -82,7 +82,8 @@
     ssize_t index = mHandlers.indexOfKey(msg->target());
 
     if (index < 0) {
-        ALOGW("failed to post message. Target handler not registered.");
+        ALOGW("failed to post message '%s'. Target handler not registered.",
+              msg->debugString().c_str());
         return -ENOENT;
     }
 
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 733753b..962b01c 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -40,10 +40,13 @@
 
 namespace android {
 
-LiveSession::LiveSession(uint32_t flags, bool uidValid, uid_t uid)
-    : mFlags(flags),
+LiveSession::LiveSession(
+        const sp<AMessage> &notify, uint32_t flags, bool uidValid, uid_t uid)
+    : mNotify(notify),
+      mFlags(flags),
       mUIDValid(uidValid),
       mUID(uid),
+      mInPreparationPhase(true),
       mDataSource(new LiveDataSource),
       mHTTPDataSource(
               HTTPBase::Create(
@@ -179,7 +182,7 @@
     if (playlist == NULL) {
         ALOGE("unable to fetch master playlist '%s'.", url.c_str());
 
-        mDataSource->queueEOS(ERROR_IO);
+        signalEOS(ERROR_IO);
         return;
     }
 
@@ -207,7 +210,7 @@
 void LiveSession::onDisconnect() {
     ALOGI("onDisconnect");
 
-    mDataSource->queueEOS(ERROR_END_OF_STREAM);
+    signalEOS(ERROR_END_OF_STREAM);
 
     Mutex::Autolock autoLock(mLock);
     mDisconnectPending = false;
@@ -561,7 +564,8 @@
                 // unchanged from the last time we tried.
             } else {
                 ALOGE("failed to load playlist at url '%s'", url.c_str());
-                mDataSource->queueEOS(ERROR_IO);
+                signalEOS(ERROR_IO);
+
                 return;
             }
         } else {
@@ -704,7 +708,7 @@
                  mSeqNumber, firstSeqNumberInPlaylist,
                  firstSeqNumberInPlaylist + mPlaylist->size() - 1);
 
-            mDataSource->queueEOS(ERROR_END_OF_STREAM);
+            signalEOS(ERROR_END_OF_STREAM);
             return;
         }
     }
@@ -737,7 +741,7 @@
     status_t err = fetchFile(uri.c_str(), &buffer, range_offset, range_length);
     if (err != OK) {
         ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str());
-        mDataSource->queueEOS(err);
+        signalEOS(err);
         return;
     }
 
@@ -748,7 +752,7 @@
     if (err != OK) {
         ALOGE("decryptBuffer failed w/ error %d", err);
 
-        mDataSource->queueEOS(err);
+        signalEOS(err);
         return;
     }
 
@@ -760,7 +764,7 @@
         mBandwidthItems.removeAt(bandwidthIndex);
 
         if (mBandwidthItems.isEmpty()) {
-            mDataSource->queueEOS(ERROR_UNSUPPORTED);
+            signalEOS(ERROR_UNSUPPORTED);
             return;
         }
 
@@ -824,11 +828,42 @@
     postMonitorQueue();
 }
 
+void LiveSession::signalEOS(status_t err) {
+    if (mInPreparationPhase && mNotify != NULL) {
+        sp<AMessage> notify = mNotify->dup();
+
+        notify->setInt32(
+                "what",
+                err == ERROR_END_OF_STREAM
+                    ? kWhatPrepared : kWhatPreparationFailed);
+
+        if (err != ERROR_END_OF_STREAM) {
+            notify->setInt32("err", err);
+        }
+
+        notify->post();
+
+        mInPreparationPhase = false;
+    }
+
+    mDataSource->queueEOS(err);
+}
+
 void LiveSession::onMonitorQueue() {
     if (mSeekTimeUs >= 0
             || mDataSource->countQueuedBuffers() < kMaxNumQueuedFragments) {
         onDownloadNext();
     } else {
+        if (mInPreparationPhase) {
+            if (mNotify != NULL) {
+                sp<AMessage> notify = mNotify->dup();
+                notify->setInt32("what", kWhatPrepared);
+                notify->post();
+            }
+
+            mInPreparationPhase = false;
+        }
+
         postMonitorQueue(1000000ll);
     }
 }
diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h
index f329cc9..db44a33 100644
--- a/media/libstagefright/include/LiveSession.h
+++ b/media/libstagefright/include/LiveSession.h
@@ -35,7 +35,9 @@
         // Don't log any URLs.
         kFlagIncognito = 1,
     };
-    LiveSession(uint32_t flags = 0, bool uidValid = false, uid_t uid = 0);
+    LiveSession(
+            const sp<AMessage> &notify,
+            uint32_t flags = 0, bool uidValid = false, uid_t uid = 0);
 
     sp<DataSource> getDataSource();
 
@@ -53,6 +55,12 @@
     bool isSeekable() const;
     bool hasDynamicDuration() const;
 
+    // Posted notification's "what" field will carry one of the following:
+    enum {
+        kWhatPrepared,
+        kWhatPreparationFailed,
+    };
+
 protected:
     virtual ~LiveSession();
 
@@ -76,10 +84,13 @@
         unsigned long mBandwidth;
     };
 
+    sp<AMessage> mNotify;
     uint32_t mFlags;
     bool mUIDValid;
     uid_t mUID;
 
+    bool mInPreparationPhase;
+
     sp<LiveDataSource> mDataSource;
 
     sp<HTTPBase> mHTTPDataSource;
@@ -144,6 +155,8 @@
     // This is computed by summing the durations of all segments before it.
     int64_t getSegmentStartTimeUs(int32_t seqNumber) const;
 
+    void signalEOS(status_t err);
+
     DISALLOW_EVIL_CONSTRUCTORS(LiveSession);
 };
 
diff --git a/media/libstagefright/include/SDPLoader.h b/media/libstagefright/include/SDPLoader.h
new file mode 100644
index 0000000..ca59dc0
--- /dev/null
+++ b/media/libstagefright/include/SDPLoader.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 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 SDP_LOADER_H_
+
+#define SDP_LOADER_H_
+
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <utils/String8.h>
+
+namespace android {
+
+struct HTTPBase;
+
+struct SDPLoader : public AHandler {
+    enum Flags {
+        // Don't log any URLs.
+        kFlagIncognito = 1,
+    };
+    enum {
+        kWhatSDPLoaded = 'sdpl'
+    };
+    SDPLoader(const sp<AMessage> &notify, uint32_t flags = 0, bool uidValid = false, uid_t uid = 0);
+
+    void load(const char* url, const KeyedVector<String8, String8> *headers);
+
+    void cancel();
+
+protected:
+    virtual ~SDPLoader() {}
+
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    enum {
+        kWhatLoad = 'load',
+    };
+
+    void onLoad(const sp<AMessage> &msg);
+
+    sp<AMessage> mNotify;
+    const char* mUrl;
+    uint32_t mFlags;
+    bool mUIDValid;
+    uid_t mUID;
+    sp<ALooper> mNetLooper;
+    bool mCancelled;
+
+    sp<HTTPBase> mHTTPDataSource;
+
+    DISALLOW_EVIL_CONSTRUCTORS(SDPLoader);
+};
+
+}  // namespace android
+
+#endif  // SDP_LOADER_H_
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index a605a05..3de3a61 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -28,9 +28,12 @@
 
 namespace android {
 
+const int64_t kNearEOSMarkUs = 2000000ll; // 2 secs
+
 AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
     : mIsAudio(false),
       mFormat(meta),
+      mLastQueuedTimeUs(0),
       mEOSResult(OK) {
     const char *mime;
     CHECK(meta->findCString(kKeyMIMEType, &mime));
@@ -141,9 +144,8 @@
         return;
     }
 
-    int64_t timeUs;
-    CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
-    ALOGV("queueAccessUnit timeUs=%lld us (%.2f secs)", timeUs, timeUs / 1E6);
+    CHECK(buffer->meta()->findInt64("timeUs", &mLastQueuedTimeUs));
+    ALOGV("queueAccessUnit timeUs=%lld us (%.2f secs)", mLastQueuedTimeUs, mLastQueuedTimeUs / 1E6);
 
     Mutex::Autolock autoLock(mLock);
     mBuffers.push_back(buffer);
@@ -171,6 +173,7 @@
     }
 
     mEOSResult = OK;
+    mLastQueuedTimeUs = 0;
 
     sp<ABuffer> buffer = new ABuffer(0);
     buffer->meta()->setInt32("discontinuity", static_cast<int32_t>(type));
@@ -247,4 +250,15 @@
     return OK;
 }
 
+bool AnotherPacketSource::isFinished(int64_t duration) const {
+    if (duration > 0) {
+        int64_t diff = duration - mLastQueuedTimeUs;
+        if (diff < kNearEOSMarkUs && diff > -kNearEOSMarkUs) {
+            ALOGV("Detecting EOS due to near end");
+            return true;
+        }
+    }
+    return (mEOSResult != OK);
+}
+
 }  // namespace android
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index d685b98..1db4068 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -58,6 +58,8 @@
 
     status_t dequeueAccessUnit(sp<ABuffer> *buffer);
 
+    bool isFinished(int64_t duration) const;
+
 protected:
     virtual ~AnotherPacketSource();
 
@@ -67,6 +69,7 @@
 
     bool mIsAudio;
     sp<MetaData> mFormat;
+    int64_t mLastQueuedTimeUs;
     List<sp<ABuffer> > mBuffers;
     status_t mEOSResult;
 
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
index 49e2daf..9e2724d 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -17,6 +17,7 @@
         ARTPWriter.cpp              \
         ARTSPConnection.cpp         \
         ASessionDescription.cpp     \
+        SDPLoader.cpp               \
 
 LOCAL_C_INCLUDES:= \
 	$(TOP)/frameworks/av/media/libstagefright/include \
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 96c7683..5d760d3 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -52,6 +52,8 @@
 
 static int64_t kDefaultKeepAliveTimeoutUs = 60000000ll;
 
+static int64_t kPauseDelayUs = 3000000ll;
+
 namespace android {
 
 static void MakeUserAgentString(AString *s) {
@@ -129,13 +131,16 @@
           mNumAccessUnitsReceived(0),
           mCheckPending(false),
           mCheckGeneration(0),
+          mCheckTimeoutGeneration(0),
           mTryTCPInterleaving(false),
           mTryFakeRTCP(false),
           mReceivedFirstRTCPPacket(false),
           mReceivedFirstRTPPacket(false),
-          mSeekable(false),
+          mSeekable(true),
           mKeepAliveTimeoutUs(kDefaultKeepAliveTimeoutUs),
-          mKeepAliveGeneration(0) {
+          mKeepAliveGeneration(0),
+          mPausing(false),
+          mPauseGeneration(0) {
         mNetLooper->setName("rtsp net");
         mNetLooper->start(false /* runOnCallingThread */,
                           false /* canCallJava */,
@@ -173,6 +178,39 @@
         mConn->connect(mOriginalSessionURL.c_str(), reply);
     }
 
+    void loadSDP(const sp<ASessionDescription>& desc) {
+        looper()->registerHandler(mConn);
+        (1 ? mNetLooper : looper())->registerHandler(mRTPConn);
+
+        sp<AMessage> notify = new AMessage('biny', id());
+        mConn->observeBinaryData(notify);
+
+        sp<AMessage> reply = new AMessage('sdpl', id());
+        reply->setObject("description", desc);
+        mConn->connect(mOriginalSessionURL.c_str(), reply);
+    }
+
+    AString getControlURL(sp<ASessionDescription> desc) {
+        AString sessionLevelControlURL;
+        if (mSessionDesc->findAttribute(
+                0,
+                "a=control",
+                &sessionLevelControlURL)) {
+            if (sessionLevelControlURL.compare("*") == 0) {
+                return mBaseURL;
+            } else {
+                AString controlURL;
+                CHECK(MakeURL(
+                        mBaseURL.c_str(),
+                        sessionLevelControlURL.c_str(),
+                        &controlURL));
+                return controlURL;
+            }
+        } else {
+            return mSessionURL;
+        }
+    }
+
     void disconnect() {
         (new AMessage('abor', id()))->post();
     }
@@ -180,6 +218,24 @@
     void seek(int64_t timeUs) {
         sp<AMessage> msg = new AMessage('seek', id());
         msg->setInt64("time", timeUs);
+        mPauseGeneration++;
+        msg->post();
+    }
+
+    bool isSeekable() const {
+        return mSeekable;
+    }
+
+    void pause() {
+        sp<AMessage> msg = new AMessage('paus', id());
+        mPauseGeneration++;
+        msg->setInt32("pausecheck", mPauseGeneration);
+        msg->post(kPauseDelayUs);
+    }
+
+    void resume() {
+        sp<AMessage> msg = new AMessage('resu', id());
+        mPauseGeneration++;
         msg->post();
     }
 
@@ -348,6 +404,39 @@
         return true;
     }
 
+    static bool isLiveStream(const sp<ASessionDescription> &desc) {
+        AString attrLiveStream;
+        if (desc->findAttribute(0, "a=LiveStream", &attrLiveStream)) {
+            ssize_t semicolonPos = attrLiveStream.find(";", 2);
+
+            const char* liveStreamValue;
+            if (semicolonPos < 0) {
+                liveStreamValue = attrLiveStream.c_str();
+            } else {
+                AString valString;
+                valString.setTo(attrLiveStream,
+                        semicolonPos + 1,
+                        attrLiveStream.size() - semicolonPos - 1);
+                liveStreamValue = valString.c_str();
+            }
+
+            uint32_t value = strtoul(liveStreamValue, NULL, 10);
+            if (value == 1) {
+                ALOGV("found live stream");
+                return true;
+            }
+        } else {
+            // It is a live stream if no duration is returned
+            int64_t durationUs;
+            if (!desc->getDurationUs(&durationUs)) {
+                ALOGV("No duration found, assume live stream");
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     virtual void onMessageReceived(const sp<AMessage> &msg) {
         switch (msg->what()) {
             case 'conn':
@@ -445,6 +534,8 @@
                                 }
                             }
 
+                            mSeekable = !isLiveStream(mSessionDesc);
+
                             if (!mBaseURL.startsWith("rtsp://")) {
                                 // Some misbehaving servers specify a relative
                                 // URL in one of the locations above, combine
@@ -464,6 +555,8 @@
                                 mBaseURL = tmp;
                             }
 
+                            mControlURL = getControlURL(mSessionDesc);
+
                             if (mSessionDesc->countTracks() < 2) {
                                 // There's no actual tracks in this session.
                                 // The first "track" is merely session meta
@@ -486,6 +579,51 @@
                 break;
             }
 
+            case 'sdpl':
+            {
+                int32_t result;
+                CHECK(msg->findInt32("result", &result));
+
+                ALOGI("SDP connection request completed with result %d (%s)",
+                     result, strerror(-result));
+
+                if (result == OK) {
+                    sp<RefBase> obj;
+                    CHECK(msg->findObject("description", &obj));
+                    mSessionDesc =
+                        static_cast<ASessionDescription *>(obj.get());
+
+                    if (!mSessionDesc->isValid()) {
+                        ALOGE("Failed to parse session description.");
+                        result = ERROR_MALFORMED;
+                    } else {
+                        mBaseURL = mSessionURL;
+
+                        mSeekable = !isLiveStream(mSessionDesc);
+
+                        mControlURL = getControlURL(mSessionDesc);
+
+                        if (mSessionDesc->countTracks() < 2) {
+                            // There's no actual tracks in this session.
+                            // The first "track" is merely session meta
+                            // data.
+
+                            ALOGW("Session doesn't contain any playable "
+                                 "tracks. Aborting.");
+                            result = ERROR_UNSUPPORTED;
+                        } else {
+                            setupTrack(1);
+                        }
+                    }
+                }
+
+                if (result != OK) {
+                    sp<AMessage> reply = new AMessage('disc', id());
+                    mConn->disconnect(reply);
+                }
+                break;
+            }
+
             case 'setu':
             {
                 size_t index;
@@ -603,7 +741,7 @@
                     postKeepAlive();
 
                     AString request = "PLAY ";
-                    request.append(mSessionURL);
+                    request.append(mControlURL);
                     request.append(" RTSP/1.0\r\n");
 
                     request.append("Session: ");
@@ -641,6 +779,8 @@
                         parsePlayResponse(response);
 
                         sp<AMessage> timeout = new AMessage('tiou', id());
+                        mCheckTimeoutGeneration++;
+                        timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
                         timeout->post(kStartupTimeoutUs);
                     }
                 }
@@ -730,7 +870,8 @@
                 mNumAccessUnitsReceived = 0;
                 mReceivedFirstRTCPPacket = false;
                 mReceivedFirstRTPPacket = false;
-                mSeekable = false;
+                mPausing = false;
+                mSeekable = true;
 
                 sp<AMessage> reply = new AMessage('tear', id());
 
@@ -851,9 +992,16 @@
                 int32_t eos;
                 if (msg->findInt32("eos", &eos)) {
                     ALOGI("received BYE on track index %d", trackIndex);
-#if 0
-                    track->mPacketSource->signalEOS(ERROR_END_OF_STREAM);
-#endif
+                    if (!mAllTracksHaveTime && dataReceivedOnAllChannels()) {
+                        ALOGI("No time established => fake existing data");
+
+                        track->mEOSReceived = true;
+                        mTryFakeRTCP = true;
+                        mReceivedFirstRTCPPacket = true;
+                        fakeTimestamps();
+                    } else {
+                        postQueueEOS(trackIndex, ERROR_END_OF_STREAM);
+                    }
                     return;
                 }
 
@@ -881,6 +1029,115 @@
                 break;
             }
 
+            case 'paus':
+            {
+                int32_t generation;
+                CHECK(msg->findInt32("pausecheck", &generation));
+                if (generation != mPauseGeneration) {
+                    ALOGV("Ignoring outdated pause message.");
+                    break;
+                }
+
+                if (!mSeekable) {
+                    ALOGW("This is a live stream, ignoring pause request.");
+                    break;
+                }
+                mCheckPending = true;
+                ++mCheckGeneration;
+                mPausing = true;
+
+                AString request = "PAUSE ";
+                request.append(mControlURL);
+                request.append(" RTSP/1.0\r\n");
+
+                request.append("Session: ");
+                request.append(mSessionID);
+                request.append("\r\n");
+
+                request.append("\r\n");
+
+                sp<AMessage> reply = new AMessage('pau2', id());
+                mConn->sendRequest(request.c_str(), reply);
+                break;
+            }
+
+            case 'pau2':
+            {
+                int32_t result;
+                CHECK(msg->findInt32("result", &result));
+                mCheckTimeoutGeneration++;
+
+                ALOGI("PAUSE completed with result %d (%s)",
+                     result, strerror(-result));
+                break;
+            }
+
+            case 'resu':
+            {
+                if (mPausing && mSeekPending) {
+                    // If seeking, Play will be sent from see1 instead
+                    break;
+                }
+
+                if (!mPausing) {
+                    // Dont send PLAY if we have not paused
+                    break;
+                }
+                AString request = "PLAY ";
+                request.append(mControlURL);
+                request.append(" RTSP/1.0\r\n");
+
+                request.append("Session: ");
+                request.append(mSessionID);
+                request.append("\r\n");
+
+                request.append("\r\n");
+
+                sp<AMessage> reply = new AMessage('res2', id());
+                mConn->sendRequest(request.c_str(), reply);
+                break;
+            }
+
+            case 'res2':
+            {
+                int32_t result;
+                CHECK(msg->findInt32("result", &result));
+
+                ALOGI("PLAY completed with result %d (%s)",
+                     result, strerror(-result));
+
+                mCheckPending = false;
+                postAccessUnitTimeoutCheck();
+
+                if (result == OK) {
+                    sp<RefBase> obj;
+                    CHECK(msg->findObject("response", &obj));
+                    sp<ARTSPResponse> response =
+                        static_cast<ARTSPResponse *>(obj.get());
+
+                    if (response->mStatusCode != 200) {
+                        result = UNKNOWN_ERROR;
+                    } else {
+                        parsePlayResponse(response);
+
+                        // Post new timeout in order to make sure to use
+                        // fake timestamps if no new Sender Reports arrive
+                        sp<AMessage> timeout = new AMessage('tiou', id());
+                        mCheckTimeoutGeneration++;
+                        timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
+                        timeout->post(kStartupTimeoutUs);
+                    }
+                }
+
+                if (result != OK) {
+                    ALOGE("resume failed, aborting.");
+                    (new AMessage('abor', id()))->post();
+                }
+
+                mPausing = false;
+                break;
+            }
+
             case 'seek':
             {
                 if (!mSeekable) {
@@ -902,8 +1159,17 @@
                 mCheckPending = true;
                 ++mCheckGeneration;
 
+                sp<AMessage> reply = new AMessage('see1', id());
+                reply->setInt64("time", timeUs);
+
+                if (mPausing) {
+                    // PAUSE already sent
+                    ALOGI("Pause already sent");
+                    reply->post();
+                    break;
+                }
                 AString request = "PAUSE ";
-                request.append(mSessionURL);
+                request.append(mControlURL);
                 request.append(" RTSP/1.0\r\n");
 
                 request.append("Session: ");
@@ -912,8 +1178,6 @@
 
                 request.append("\r\n");
 
-                sp<AMessage> reply = new AMessage('see1', id());
-                reply->setInt64("time", timeUs);
                 mConn->sendRequest(request.c_str(), reply);
                 break;
             }
@@ -925,6 +1189,7 @@
                     TrackInfo *info = &mTracks.editItemAt(i);
 
                     postQueueSeekDiscontinuity(i);
+                    info->mEOSReceived = false;
 
                     info->mRTPAnchor = 0;
                     info->mNTPAnchorUs = -1;
@@ -933,11 +1198,18 @@
                 mAllTracksHaveTime = false;
                 mNTPAnchorUs = -1;
 
+                // Start new timeoutgeneration to avoid getting timeout
+                // before PLAY response arrive
+                sp<AMessage> timeout = new AMessage('tiou', id());
+                mCheckTimeoutGeneration++;
+                timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
+                timeout->post(kStartupTimeoutUs);
+
                 int64_t timeUs;
                 CHECK(msg->findInt64("time", &timeUs));
 
                 AString request = "PLAY ";
-                request.append(mSessionURL);
+                request.append(mControlURL);
                 request.append(" RTSP/1.0\r\n");
 
                 request.append("Session: ");
@@ -957,7 +1229,10 @@
 
             case 'see2':
             {
-                CHECK(mSeekPending);
+                if (mTracks.size() == 0) {
+                    // We have already hit abor, break
+                    break;
+                }
 
                 int32_t result;
                 CHECK(msg->findInt32("result", &result));
@@ -979,6 +1254,13 @@
                     } else {
                         parsePlayResponse(response);
 
+                        // Post new timeout in order to make sure to use
+                        // fake timestamps if no new Sender Reports arrive
+                        sp<AMessage> timeout = new AMessage('tiou', id());
+                        mCheckTimeoutGeneration++;
+                        timeout->setInt32("tioucheck", mCheckTimeoutGeneration);
+                        timeout->post(kStartupTimeoutUs);
+
                         ssize_t i = response->mHeaders.indexOfKey("rtp-info");
                         CHECK_GE(i, 0);
 
@@ -993,6 +1275,7 @@
                     (new AMessage('abor', id()))->post();
                 }
 
+                mPausing = false;
                 mSeekPending = false;
 
                 sp<AMessage> msg = mNotify->dup();
@@ -1015,8 +1298,17 @@
 
             case 'tiou':
             {
+                int32_t timeoutGenerationCheck;
+                CHECK(msg->findInt32("tioucheck", &timeoutGenerationCheck));
+                if (timeoutGenerationCheck != mCheckTimeoutGeneration) {
+                    // This is an outdated message. Ignore.
+                    // This typically happens if a lot of seeks are
+                    // performed, since new timeout messages now are
+                    // posted at seek as well.
+                    break;
+                }
                 if (!mReceivedFirstRTCPPacket) {
-                    if (mReceivedFirstRTPPacket && !mTryFakeRTCP) {
+                    if (dataReceivedOnAllChannels() && !mTryFakeRTCP) {
                         ALOGW("We received RTP packets but no RTCP packets, "
                              "using fake timestamps.");
 
@@ -1090,7 +1382,6 @@
     }
 
     void parsePlayResponse(const sp<ARTSPResponse> &response) {
-        mSeekable = false;
         if (mTracks.size() == 0) {
             ALOGV("parsePlayResponse: late packets ignored.");
             return;
@@ -1165,8 +1456,6 @@
 
             ++n;
         }
-
-        mSeekable = true;
     }
 
     sp<MetaData> getTrackFormat(size_t index, int32_t *timeScale) {
@@ -1196,6 +1485,7 @@
         uint32_t mRTPAnchor;
         int64_t mNTPAnchorUs;
         int32_t mTimeScale;
+        bool mEOSReceived;
 
         uint32_t mNormalPlayTimeRTP;
         int64_t mNormalPlayTimeUs;
@@ -1218,6 +1508,7 @@
     AString mSessionURL;
     AString mSessionHost;
     AString mBaseURL;
+    AString mControlURL;
     AString mSessionID;
     bool mSetupTracksSuccessful;
     bool mSeekPending;
@@ -1231,6 +1522,7 @@
     int64_t mNumAccessUnitsReceived;
     bool mCheckPending;
     int32_t mCheckGeneration;
+    int32_t mCheckTimeoutGeneration;
     bool mTryTCPInterleaving;
     bool mTryFakeRTCP;
     bool mReceivedFirstRTCPPacket;
@@ -1238,6 +1530,8 @@
     bool mSeekable;
     int64_t mKeepAliveTimeoutUs;
     int32_t mKeepAliveGeneration;
+    bool mPausing;
+    int32_t mPauseGeneration;
 
     Vector<TrackInfo> mTracks;
 
@@ -1284,6 +1578,7 @@
                 formatDesc.c_str(), &timescale, &numChannels);
 
         info->mTimeScale = timescale;
+        info->mEOSReceived = false;
 
         ALOGV("track #%d URL=%s", mTracks.size(), trackURL.c_str());
 
@@ -1376,6 +1671,17 @@
         }
     }
 
+    bool dataReceivedOnAllChannels() {
+        TrackInfo *track;
+        for (size_t i = 0; i < mTracks.size(); ++i) {
+            track = &mTracks.editItemAt(i);
+            if (track->mPackets.empty()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     void onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime) {
         ALOGV("onTimeUpdate track %d, rtpTime = 0x%08x, ntpTime = 0x%016llx",
              trackIndex, rtpTime, ntpTime);
@@ -1406,6 +1712,27 @@
                 ALOGI("Time now established for all tracks.");
             }
         }
+        if (mAllTracksHaveTime && dataReceivedOnAllChannels()) {
+            // Time is now established, lets start timestamping immediately
+            for (size_t i = 0; i < mTracks.size(); ++i) {
+                TrackInfo *trackInfo = &mTracks.editItemAt(i);
+                while (!trackInfo->mPackets.empty()) {
+                    sp<ABuffer> accessUnit = *trackInfo->mPackets.begin();
+                    trackInfo->mPackets.erase(trackInfo->mPackets.begin());
+
+                    if (addMediaTimestamp(i, trackInfo, accessUnit)) {
+                        postQueueAccessUnit(i, accessUnit);
+                    }
+                }
+            }
+            for (size_t i = 0; i < mTracks.size(); ++i) {
+                TrackInfo *trackInfo = &mTracks.editItemAt(i);
+                if (trackInfo->mEOSReceived) {
+                    postQueueEOS(i, ERROR_END_OF_STREAM);
+                    trackInfo->mEOSReceived = false;
+                }
+            }
+        }
     }
 
     void onAccessUnitComplete(
@@ -1450,6 +1777,11 @@
         if (addMediaTimestamp(trackIndex, track, accessUnit)) {
             postQueueAccessUnit(trackIndex, accessUnit);
         }
+
+        if (track->mEOSReceived) {
+            postQueueEOS(trackIndex, ERROR_END_OF_STREAM);
+            track->mEOSReceived = false;
+        }
     }
 
     bool addMediaTimestamp(
diff --git a/media/libstagefright/rtsp/SDPLoader.cpp b/media/libstagefright/rtsp/SDPLoader.cpp
new file mode 100644
index 0000000..ed3fa7e
--- /dev/null
+++ b/media/libstagefright/rtsp/SDPLoader.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 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 "SDPLoader"
+#include <utils/Log.h>
+
+#include "SDPLoader.h"
+
+#include "ASessionDescription.h"
+#include "HTTPBase.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+#define DEFAULT_SDP_SIZE 100000
+
+namespace android {
+
+SDPLoader::SDPLoader(const sp<AMessage> &notify, uint32_t flags, bool uidValid, uid_t uid)
+    : mNotify(notify),
+      mFlags(flags),
+      mUIDValid(uidValid),
+      mUID(uid),
+      mNetLooper(new ALooper),
+      mCancelled(false),
+      mHTTPDataSource(
+              HTTPBase::Create(
+                  (mFlags & kFlagIncognito)
+                    ? HTTPBase::kFlagIncognito
+                    : 0)) {
+    if (mUIDValid) {
+        mHTTPDataSource->setUID(mUID);
+    }
+
+    mNetLooper->setName("sdp net");
+    mNetLooper->start(false /* runOnCallingThread */,
+                      false /* canCallJava */,
+                      PRIORITY_HIGHEST);
+}
+
+void SDPLoader::load(const char *url, const KeyedVector<String8, String8> *headers) {
+    mNetLooper->registerHandler(this);
+
+    sp<AMessage> msg = new AMessage(kWhatLoad, id());
+    msg->setString("url", url);
+
+    if (headers != NULL) {
+        msg->setPointer(
+                "headers",
+                new KeyedVector<String8, String8>(*headers));
+    }
+
+    msg->post();
+}
+
+void SDPLoader::cancel() {
+    mCancelled = true;
+    sp<HTTPBase> HTTPDataSource = mHTTPDataSource;
+    HTTPDataSource->disconnect();
+}
+
+void SDPLoader::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatLoad:
+            onLoad(msg);
+            break;
+
+        default:
+            TRESPASS();
+            break;
+    }
+}
+
+void SDPLoader::onLoad(const sp<AMessage> &msg) {
+    status_t err = OK;
+    sp<ASessionDescription> desc = NULL;
+    AString url;
+    CHECK(msg->findString("url", &url));
+
+    KeyedVector<String8, String8> *headers = NULL;
+    msg->findPointer("headers", (void **)&headers);
+
+    if (!(mFlags & kFlagIncognito)) {
+        ALOGI("onLoad '%s'", url.c_str());
+    } else {
+        ALOGI("onLoad <URL suppressed>");
+    }
+
+    if (!mCancelled) {
+        err = mHTTPDataSource->connect(url.c_str(), headers);
+
+        if (err != OK) {
+            ALOGE("connect() returned %d", err);
+        }
+    }
+
+    if (headers != NULL) {
+        delete headers;
+        headers = NULL;
+    }
+
+    off64_t sdpSize;
+    if (err == OK && !mCancelled) {
+        err = mHTTPDataSource->getSize(&sdpSize);
+
+        if (err != OK) {
+            //We did not get the size of the sdp file, default to a large value
+            sdpSize = DEFAULT_SDP_SIZE;
+            err = OK;
+        }
+    }
+
+    sp<ABuffer> buffer = new ABuffer(sdpSize);
+
+    if (err == OK && !mCancelled) {
+        ssize_t readSize = mHTTPDataSource->readAt(0, buffer->data(), sdpSize);
+
+        if (readSize < 0) {
+            ALOGE("Failed to read SDP, error code = %ld", readSize);
+            err = UNKNOWN_ERROR;
+        } else {
+            desc = new ASessionDescription;
+
+            if (desc == NULL || !desc->setTo(buffer->data(), (size_t)readSize)) {
+                err = UNKNOWN_ERROR;
+                ALOGE("Failed to parse SDP");
+            }
+        }
+    }
+
+    mHTTPDataSource.clear();
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatSDPLoaded);
+    notify->setInt32("result", err);
+    notify->setObject("description", desc);
+    notify->post();
+}
+
+}  // namespace android
diff --git a/media/libstagefright/wifi-display/ANetworkSession.cpp b/media/libstagefright/wifi-display/ANetworkSession.cpp
index 62a6e7f..06f71f4 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.cpp
+++ b/media/libstagefright/wifi-display/ANetworkSession.cpp
@@ -1091,7 +1091,6 @@
                                   clientSocket);
 
                             sp<Session> clientSession =
-                                // using socket sd as sessionID
                                 new Session(
                                         mNextSessionID++,
                                         Session::CONNECTED,
diff --git a/media/libstagefright/wifi-display/sink/RTPSink.cpp b/media/libstagefright/wifi-display/sink/RTPSink.cpp
index ad75373..3c90a1e 100644
--- a/media/libstagefright/wifi-display/sink/RTPSink.cpp
+++ b/media/libstagefright/wifi-display/sink/RTPSink.cpp
@@ -250,9 +250,13 @@
     : mNetSession(netSession),
       mSurfaceTex(bufferProducer),
       mNotify(notify),
+      mUsingTCPTransport(false),
+      mUsingTCPInterleaving(false),
       mRTPPort(0),
       mRTPSessionID(0),
       mRTCPSessionID(0),
+      mRTPClientSessionID(0),
+      mRTCPClientSessionID(0),
       mFirstArrivalTimeUs(-1ll),
       mNumPacketsReceived(0ll),
       mRegression(1000),
@@ -260,6 +264,14 @@
 }
 
 RTPSink::~RTPSink() {
+    if (mRTCPClientSessionID != 0) {
+        mNetSession->destroySession(mRTCPClientSessionID);
+    }
+
+    if (mRTPClientSessionID != 0) {
+        mNetSession->destroySession(mRTPClientSessionID);
+    }
+
     if (mRTCPSessionID != 0) {
         mNetSession->destroySession(mRTCPSessionID);
     }
@@ -269,8 +281,11 @@
     }
 }
 
-status_t RTPSink::init(bool useTCPInterleaving) {
-    if (useTCPInterleaving) {
+status_t RTPSink::init(bool usingTCPTransport, bool usingTCPInterleaving) {
+    mUsingTCPTransport = usingTCPTransport;
+    mUsingTCPInterleaving = usingTCPInterleaving;
+
+    if (usingTCPInterleaving) {
         return OK;
     }
 
@@ -280,8 +295,16 @@
     sp<AMessage> rtcpNotify = new AMessage(kWhatRTCPNotify, id());
     for (clientRtp = 15550;; clientRtp += 2) {
         int32_t rtpSession;
-        status_t err = mNetSession->createUDPSession(
-                    clientRtp, rtpNotify, &rtpSession);
+        status_t err;
+        struct in_addr ifaceAddr;
+        if (usingTCPTransport) {
+            ifaceAddr.s_addr = INADDR_ANY;
+            err = mNetSession->createTCPDatagramSession(
+                        ifaceAddr, clientRtp, rtpNotify, &rtpSession);
+        } else {
+            err = mNetSession->createUDPSession(
+                        clientRtp, rtpNotify, &rtpSession);
+        }
 
         if (err != OK) {
             ALOGI("failed to create RTP socket on port %d", clientRtp);
@@ -289,8 +312,13 @@
         }
 
         int32_t rtcpSession;
-        err = mNetSession->createUDPSession(
-                clientRtp + 1, rtcpNotify, &rtcpSession);
+        if (usingTCPTransport) {
+            err = mNetSession->createTCPDatagramSession(
+                    ifaceAddr, clientRtp + 1, rtcpNotify, &rtcpSession);
+        } else {
+            err = mNetSession->createUDPSession(
+                    clientRtp + 1, rtcpNotify, &rtcpSession);
+        }
 
         if (err == OK) {
             mRTPPort = clientRtp;
@@ -367,6 +395,24 @@
                     break;
                 }
 
+                case ANetworkSession::kWhatClientConnected:
+                {
+                    int32_t sessionID;
+                    CHECK(msg->findInt32("sessionID", &sessionID));
+                    ALOGI("TCP session %d now connected", sessionID);
+
+                    int32_t serverPort;
+                    CHECK(msg->findInt32("server-port", &serverPort));
+
+                    if (serverPort == mRTPPort) {
+                        mRTPClientSessionID = sessionID;
+                    } else {
+                        CHECK_EQ(serverPort, mRTPPort + 1);
+                        mRTCPClientSessionID = sessionID;
+                    }
+                    break;
+                }
+
                 default:
                     TRESPASS();
             }
@@ -676,7 +722,9 @@
             mRTCPSessionID, buf->data(), buf->size());
 #endif
 
-    scheduleSendRR();
+    if (!mUsingTCPTransport) {
+        scheduleSendRR();
+    }
 
     return OK;
 }
@@ -779,6 +827,11 @@
 }
 
 void RTPSink::onPacketLost(const sp<AMessage> &msg) {
+    if (mUsingTCPTransport) {
+        ALOGW("huh? lost a packet even though using reliable transport?");
+        return;
+    }
+
     uint32_t srcId;
     CHECK(msg->findInt32("ssrc", (int32_t *)&srcId));
 
@@ -787,12 +840,12 @@
 
     int32_t blp = 0;
 
-    sp<ABuffer> buf = new ABuffer(1500);
+    sp<ABuffer> buf = new ABuffer(16);
     buf->setRange(0, 0);
 
     uint8_t *ptr = buf->data();
     ptr[0] = 0x80 | 1;  // generic NACK
-    ptr[1] = 205;  // RTPFB
+    ptr[1] = 205;  // TSFB
     ptr[2] = 0;
     ptr[3] = 3;
     ptr[4] = 0xde;  // sender SSRC
diff --git a/media/libstagefright/wifi-display/sink/RTPSink.h b/media/libstagefright/wifi-display/sink/RTPSink.h
index 6e40185..4706c6d 100644
--- a/media/libstagefright/wifi-display/sink/RTPSink.h
+++ b/media/libstagefright/wifi-display/sink/RTPSink.h
@@ -48,7 +48,7 @@
     // If TCP interleaving is used, no UDP sockets are created, instead
     // incoming RTP/RTCP packets (arriving on the RTSP control connection)
     // are manually injected by WifiDisplaySink.
-    status_t init(bool useTCPInterleaving);
+    status_t init(bool usingTCPTransport, bool usingTCPInterleaving);
 
     status_t connect(
             const char *host, int32_t remoteRtpPort, int32_t remoteRtcpPort);
@@ -78,9 +78,16 @@
     sp<AMessage> mNotify;
     KeyedVector<uint32_t, sp<Source> > mSources;
 
+    bool mUsingTCPTransport;
+    bool mUsingTCPInterleaving;
+
     int32_t mRTPPort;
-    int32_t mRTPSessionID;
-    int32_t mRTCPSessionID;
+
+    int32_t mRTPSessionID;   // in TCP unicast mode these are just server
+    int32_t mRTCPSessionID;  // sockets. No data is transferred through them.
+
+    int32_t mRTPClientSessionID;  // in TCP unicast mode
+    int32_t mRTCPClientSessionID;
 
     int64_t mFirstArrivalTimeUs;
     int64_t mNumPacketsReceived;
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
index 5628dec..376b0df 100644
--- a/media/libstagefright/wifi-display/source/Converter.cpp
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -54,6 +54,7 @@
       ,mFirstSilentFrameUs(-1ll)
       ,mInSilentMode(false)
 #endif
+      ,mPrevVideoBitrate(-1)
     {
     AString mime;
     CHECK(mInputFormat->findString("mime", &mime));
@@ -185,6 +186,7 @@
 
     int32_t audioBitrate = getBitrate("media.wfd.audio-bitrate", 128000);
     int32_t videoBitrate = getBitrate("media.wfd.video-bitrate", 5000000);
+    mPrevVideoBitrate = videoBitrate;
 
     ALOGI("using audio bitrate of %d bps, video bitrate of %d bps",
           audioBitrate, videoBitrate);
@@ -606,6 +608,18 @@
 }
 
 status_t Converter::doMoreWork() {
+    if (mIsVideo) {
+        int32_t videoBitrate = getBitrate("media.wfd.video-bitrate", 5000000);
+        if (videoBitrate != mPrevVideoBitrate) {
+            sp<AMessage> params = new AMessage;
+
+            params->setInt32("videoBitrate", videoBitrate);
+            mEncoder->setParameters(params);
+
+            mPrevVideoBitrate = videoBitrate;
+        }
+    }
+
     status_t err;
 
     for (;;) {
diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h
index 3357d61..57802bd 100644
--- a/media/libstagefright/wifi-display/source/Converter.h
+++ b/media/libstagefright/wifi-display/source/Converter.h
@@ -100,6 +100,8 @@
 
     sp<ABuffer> mPartialAudioAU;
 
+    int32_t mPrevVideoBitrate;
+
     status_t initEncoder();
     void releaseEncoder();
 
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index 91dc1fa..ede4e60 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -29,8 +29,6 @@
 #include "WifiDisplaySource.h"
 
 #include <binder/IServiceManager.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
 #include <media/IHDCP.h>
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -41,10 +39,8 @@
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MediaExtractor.h>
 #include <media/stagefright/MediaSource.h>
 #include <media/stagefright/MetaData.h>
-#include <media/stagefright/MPEG2TSWriter.h>
 #include <media/stagefright/SurfaceMediaSource.h>
 #include <media/stagefright/Utils.h>
 
@@ -546,6 +542,18 @@
                 onFinishPlay2();
             } else if (what == Sender::kWhatSessionDead) {
                 notifySessionDead();
+            } else if (what == Sender::kWhatBinaryData) {
+                sp<AMessage> notify = mNotify->dup();
+                notify->setInt32("what", kWhatBinaryData);
+
+                int32_t channel;
+                CHECK(msg->findInt32("channel", &channel));
+                notify->setInt32("channel", channel);
+
+                sp<ABuffer> data;
+                CHECK(msg->findBuffer("data", &data));
+                notify->setBuffer("data", data);
+                notify->post();
             } else {
                 TRESPASS();
             }
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
index 981d5f9..825ebc6 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -597,15 +597,6 @@
 status_t WifiDisplaySource::sendM4(int32_t sessionID) {
     CHECK_EQ(sessionID, mClientSessionID);
 
-    AString transportString = "UDP";
-
-    char val[PROPERTY_VALUE_MAX];
-    if (property_get("media.wfd.enable-tcp", val, NULL)
-            && (!strcasecmp("true", val) || !strcmp("1", val))) {
-        ALOGI("Using TCP transport.");
-        transportString = "TCP";
-    }
-
     AString body;
 
     if (mSinkSupportsVideo) {
@@ -630,11 +621,11 @@
 
     body.append(
             StringPrintf(
-                "wfd_presentation_URL: rtsp://%s/wfd1.0/streamid=0 none\r\n"
-                "wfd_client_rtp_ports: RTP/AVP/%s;unicast %d 0 mode=play\r\n",
-                mClientInfo.mLocalIP.c_str(),
-                transportString.c_str(),
-                mChosenRTPPort));
+                "wfd_presentation_URL: rtsp://%s/wfd1.0/streamid=0 none\r\n",
+                mClientInfo.mLocalIP.c_str()));
+
+    body.append(mWfdClientRtpPorts);
+    body.append("\r\n");
 
     AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
     AppendCommonResponse(&request, mNextCSeq);
@@ -797,18 +788,29 @@
         return ERROR_MALFORMED;
     }
 
-    unsigned port0, port1;
+    unsigned port0 = 0, port1 = 0;
     if (sscanf(value.c_str(),
                "RTP/AVP/UDP;unicast %u %u mode=play",
                &port0,
-               &port1) != 2
-        || port0 == 0 || port0 > 65535 || port1 != 0) {
-        ALOGE("Sink chose its wfd_client_rtp_ports poorly (%s)",
+               &port1) == 2
+        || sscanf(value.c_str(),
+               "RTP/AVP/TCP;unicast %u %u mode=play",
+               &port0,
+               &port1) == 2) {
+            if (port0 == 0 || port0 > 65535 || port1 != 0) {
+                ALOGE("Sink chose its wfd_client_rtp_ports poorly (%s)",
+                      value.c_str());
+
+                return ERROR_MALFORMED;
+            }
+    } else if (strcmp(value.c_str(), "RTP/AVP/TCP;interleaved mode=play")) {
+        ALOGE("Unsupported value for wfd_client_rtp_ports (%s)",
               value.c_str());
 
-        return ERROR_MALFORMED;
+        return ERROR_UNSUPPORTED;
     }
 
+    mWfdClientRtpPorts = value;
     mChosenRTPPort = port0;
 
     if (!params->findParameter("wfd_video_formats", &value)) {
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
index fec2c6d..724462c 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -119,6 +119,7 @@
 
     uint32_t mStopReplyID;
 
+    AString mWfdClientRtpPorts;
     int32_t mChosenRTPPort;  // extracted from "wfd_client_rtp_ports"
 
     bool mSinkSupportsVideo;
diff --git a/media/mediaserver/Android.mk b/media/mediaserver/Android.mk
index 8c3cc5e..0a0f4db 100644
--- a/media/mediaserver/Android.mk
+++ b/media/mediaserver/Android.mk
@@ -7,12 +7,17 @@
 LOCAL_SHARED_LIBRARIES := \
 	libaudioflinger \
 	libcameraservice \
+	libmedialogservice \
+	libcutils \
+	libnbaio \
+	libmedia \
 	libmediaplayerservice \
 	libutils \
 	libbinder
 
 LOCAL_C_INCLUDES := \
     frameworks/av/media/libmediaplayerservice \
+    frameworks/av/services/medialog \
     frameworks/av/services/audioflinger \
     frameworks/av/services/camera/libcameraservice
 
diff --git a/media/mediaserver/main_mediaserver.cpp b/media/mediaserver/main_mediaserver.cpp
index ddd5b84..0862952 100644
--- a/media/mediaserver/main_mediaserver.cpp
+++ b/media/mediaserver/main_mediaserver.cpp
@@ -18,14 +18,19 @@
 #define LOG_TAG "mediaserver"
 //#define LOG_NDEBUG 0
 
+#include <fcntl.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
+#include <cutils/properties.h>
 #include <utils/Log.h>
 
 // from LOCAL_C_INCLUDES
 #include "AudioFlinger.h"
 #include "CameraService.h"
+#include "MediaLogService.h"
 #include "MediaPlayerService.h"
 #include "AudioPolicyService.h"
 
@@ -34,13 +39,95 @@
 int main(int argc, char** argv)
 {
     signal(SIGPIPE, SIG_IGN);
-    sp<ProcessState> proc(ProcessState::self());
-    sp<IServiceManager> sm = defaultServiceManager();
-    ALOGI("ServiceManager: %p", sm.get());
-    AudioFlinger::instantiate();
-    MediaPlayerService::instantiate();
-    CameraService::instantiate();
-    AudioPolicyService::instantiate();
-    ProcessState::self()->startThreadPool();
-    IPCThreadState::self()->joinThreadPool();
+    char value[PROPERTY_VALUE_MAX];
+    bool doLog = (property_get("ro.test_harness", value, "0") > 0) && (atoi(value) == 1);
+    pid_t childPid;
+    // FIXME The advantage of making the process containing media.log service the parent process of
+    // the process that contains all the other real services, is that it allows us to collect more
+    // detailed information such as signal numbers, stop and continue, resource usage, etc.
+    // But it is also more complex.  Consider replacing this by independent processes, and using
+    // binder on death notification instead.
+    if (doLog && (childPid = fork()) != 0) {
+        // media.log service
+        //prctl(PR_SET_NAME, (unsigned long) "media.log", 0, 0, 0);
+        // unfortunately ps ignores PR_SET_NAME for the main thread, so use this ugly hack
+        strcpy(argv[0], "media.log");
+        sp<ProcessState> proc(ProcessState::self());
+        MediaLogService::instantiate();
+        ProcessState::self()->startThreadPool();
+        for (;;) {
+            siginfo_t info;
+            int ret = waitid(P_PID, childPid, &info, WEXITED | WSTOPPED | WCONTINUED);
+            if (ret == EINTR) {
+                continue;
+            }
+            if (ret < 0) {
+                break;
+            }
+            char buffer[32];
+            const char *code;
+            switch (info.si_code) {
+            case CLD_EXITED:
+                code = "CLD_EXITED";
+                break;
+            case CLD_KILLED:
+                code = "CLD_KILLED";
+                break;
+            case CLD_DUMPED:
+                code = "CLD_DUMPED";
+                break;
+            case CLD_STOPPED:
+                code = "CLD_STOPPED";
+                break;
+            case CLD_TRAPPED:
+                code = "CLD_TRAPPED";
+                break;
+            case CLD_CONTINUED:
+                code = "CLD_CONTINUED";
+                break;
+            default:
+                snprintf(buffer, sizeof(buffer), "unknown (%d)", info.si_code);
+                code = buffer;
+                break;
+            }
+            struct rusage usage;
+            getrusage(RUSAGE_CHILDREN, &usage);
+            ALOG(LOG_ERROR, "media.log", "pid %d status %d code %s user %ld.%03lds sys %ld.%03lds",
+                    info.si_pid, info.si_status, code,
+                    usage.ru_utime.tv_sec, usage.ru_utime.tv_usec / 1000,
+                    usage.ru_stime.tv_sec, usage.ru_stime.tv_usec / 1000);
+            sp<IServiceManager> sm = defaultServiceManager();
+            sp<IBinder> binder = sm->getService(String16("media.log"));
+            if (binder != 0) {
+                Vector<String16> args;
+                binder->dump(-1, args);
+            }
+            switch (info.si_code) {
+            case CLD_EXITED:
+            case CLD_KILLED:
+            case CLD_DUMPED: {
+                ALOG(LOG_INFO, "media.log", "exiting");
+                _exit(0);
+                // not reached
+                }
+            default:
+                break;
+            }
+        }
+    } else {
+        // all other services
+        if (doLog) {
+            prctl(PR_SET_PDEATHSIG, SIGKILL);   // if parent media.log dies before me, kill me also
+            setpgid(0, 0);                      // but if I die first, don't kill my parent
+        }
+        sp<ProcessState> proc(ProcessState::self());
+        sp<IServiceManager> sm = defaultServiceManager();
+        ALOGI("ServiceManager: %p", sm.get());
+        AudioFlinger::instantiate();
+        MediaPlayerService::instantiate();
+        CameraService::instantiate();
+        AudioPolicyService::instantiate();
+        ProcessState::self()->startThreadPool();
+        IPCThreadState::self()->joinThreadPool();
+    }
 }
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 5f5b041..47c2772 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -59,6 +59,8 @@
 #include <common_time/cc_helper.h>
 //#include <common_time/local_clock.h>
 
+#include <media/IMediaLogService.h>
+
 // ----------------------------------------------------------------------------
 
 // Note: the following macro is used for extremely verbose logging message.  In
@@ -127,6 +129,11 @@
       mMode(AUDIO_MODE_INVALID),
       mBtNrecIsOff(false)
 {
+    char value[PROPERTY_VALUE_MAX];
+    bool doLog = (property_get("ro.test_harness", value, "0") > 0) && (atoi(value) == 1);
+    if (doLog) {
+        mLogMemoryDealer = new MemoryDealer(kLogMemorySize, "LogWriters");
+    }
 }
 
 void AudioFlinger::onFirstRef()
@@ -323,6 +330,17 @@
         if (locked) {
             mLock.unlock();
         }
+
+        // append a copy of media.log here by forwarding fd to it, but don't attempt
+        // to lookup the service if it's not running, as it will block for a second
+        if (mLogMemoryDealer != 0) {
+            sp<IBinder> binder = defaultServiceManager()->getService(String16("media.log"));
+            if (binder != 0) {
+                fdprintf(fd, "\nmedia.log:\n");
+                Vector<String16> args;
+                binder->dump(fd, args);
+            }
+        }
     }
     return NO_ERROR;
 }
@@ -340,6 +358,38 @@
     return client;
 }
 
+sp<NBLog::Writer> AudioFlinger::newWriter_l(size_t size, const char *name)
+{
+    if (mLogMemoryDealer == 0) {
+        return new NBLog::Writer();
+    }
+    sp<IMemory> shared = mLogMemoryDealer->allocate(NBLog::Timeline::sharedSize(size));
+    sp<NBLog::Writer> writer = new NBLog::Writer(size, shared);
+    sp<IBinder> binder = defaultServiceManager()->getService(String16("media.log"));
+    if (binder != 0) {
+        interface_cast<IMediaLogService>(binder)->registerWriter(shared, size, name);
+    }
+    return writer;
+}
+
+void AudioFlinger::unregisterWriter(const sp<NBLog::Writer>& writer)
+{
+    if (writer == 0) {
+        return;
+    }
+    sp<IMemory> iMemory(writer->getIMemory());
+    if (iMemory == 0) {
+        return;
+    }
+    sp<IBinder> binder = defaultServiceManager()->getService(String16("media.log"));
+    if (binder != 0) {
+        interface_cast<IMediaLogService>(binder)->unregisterWriter(iMemory);
+        // Now the media.log remote reference to IMemory is gone.
+        // When our last local reference to IMemory also drops to zero,
+        // the IMemory destructor will deallocate the region from mMemoryDealer.
+    }
+}
+
 // IAudioFlinger interface
 
 
@@ -1600,14 +1650,14 @@
         // Start record thread
         // RecorThread require both input and output device indication to forward to audio
         // pre processing modules
-        audio_devices_t device = (*pDevices) | primaryOutputDevice_l();
-
         thread = new RecordThread(this,
                                   input,
                                   reqSamplingRate,
                                   reqChannels,
                                   id,
-                                  device, teeSink);
+                                  primaryOutputDevice_l(),
+                                  *pDevices,
+                                  teeSink);
         mRecordThreads.add(id, thread);
         ALOGV("openInput() created record thread: ID %d thread %p", id, thread);
         if (pSamplingRate != NULL) *pSamplingRate = reqSamplingRate;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index a7f5b9e..c3f08f6 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -53,6 +53,8 @@
 
 #include <powermanager/IPowerManager.h>
 
+#include <media/nbaio/NBLog.h>
+
 namespace android {
 
 class audio_track_cblk_t;
@@ -222,6 +224,13 @@
 
     // end of IAudioFlinger interface
 
+    sp<NBLog::Writer>   newWriter_l(size_t size, const char *name);
+    void                unregisterWriter(const sp<NBLog::Writer>& writer);
+private:
+    static const size_t kLogMemorySize = 10 * 1024;
+    sp<MemoryDealer>    mLogMemoryDealer;   // == 0 when NBLog is disabled
+public:
+
     class SyncEvent;
 
     typedef void (*sync_event_callback_t)(const wp<SyncEvent>& event) ;
diff --git a/services/audioflinger/FastMixer.cpp b/services/audioflinger/FastMixer.cpp
index 9283f53..80e37ca 100644
--- a/services/audioflinger/FastMixer.cpp
+++ b/services/audioflinger/FastMixer.cpp
@@ -92,6 +92,7 @@
     struct timespec measuredWarmupTs = {0, 0};  // how long did it take for warmup to complete
     uint32_t warmupCycles = 0;  // counter of number of loop cycles required to warmup
     NBAIO_Sink* teeSink = NULL; // if non-NULL, then duplicate write() to this non-blocking sink
+    NBLog::Writer dummyLogWriter, *logWriter = &dummyLogWriter;
 
     for (;;) {
 
@@ -119,9 +120,12 @@
         FastMixerState::Command command = next->mCommand;
         if (next != current) {
 
+            logWriter->log("next != current");
+
             // As soon as possible of learning of a new dump area, start using it
             dumpState = next->mDumpState != NULL ? next->mDumpState : &dummyDumpState;
             teeSink = next->mTeeSink;
+            logWriter = next->mNBLogWriter != NULL ? next->mNBLogWriter : &dummyLogWriter;
 
             // We want to always have a valid reference to the previous (non-idle) state.
             // However, the state queue only guarantees access to current and previous states.
@@ -163,6 +167,7 @@
                 ALOG_ASSERT(coldFutexAddr != NULL);
                 int32_t old = android_atomic_dec(coldFutexAddr);
                 if (old <= 0) {
+                    logWriter->log("wait");
                     __futex_syscall4(coldFutexAddr, FUTEX_WAIT_PRIVATE, old - 1, NULL);
                 }
                 // This may be overly conservative; there could be times that the normal mixer
@@ -181,6 +186,7 @@
             }
             continue;
         case FastMixerState::EXIT:
+            logWriter->log("exit");
             delete mixer;
             delete[] mixBuffer;
             return false;
@@ -258,11 +264,15 @@
             unsigned currentTrackMask = current->mTrackMask;
             dumpState->mTrackMask = currentTrackMask;
             if (current->mFastTracksGen != fastTracksGen) {
+                logWriter->logf("gen %d", current->mFastTracksGen);
                 ALOG_ASSERT(mixBuffer != NULL);
                 int name;
 
                 // process removed tracks first to avoid running out of track names
                 unsigned removedTracks = previousTrackMask & ~currentTrackMask;
+                if (removedTracks) {
+                    logWriter->logf("removed %#x", removedTracks);
+                }
                 while (removedTracks != 0) {
                     i = __builtin_ctz(removedTracks);
                     removedTracks &= ~(1 << i);
@@ -282,6 +292,9 @@
 
                 // now process added tracks
                 unsigned addedTracks = currentTrackMask & ~previousTrackMask;
+                if (addedTracks) {
+                    logWriter->logf("added %#x", addedTracks);
+                }
                 while (addedTracks != 0) {
                     i = __builtin_ctz(addedTracks);
                     addedTracks &= ~(1 << i);
@@ -312,6 +325,9 @@
                 // finally process modified tracks; these use the same slot
                 // but may have a different buffer provider or volume provider
                 unsigned modifiedTracks = currentTrackMask & previousTrackMask;
+                if (modifiedTracks) {
+                    logWriter->logf("modified %#x", modifiedTracks);
+                }
                 while (modifiedTracks != 0) {
                     i = __builtin_ctz(modifiedTracks);
                     modifiedTracks &= ~(1 << i);
@@ -455,6 +471,7 @@
         struct timespec newTs;
         int rc = clock_gettime(CLOCK_MONOTONIC, &newTs);
         if (rc == 0) {
+            logWriter->logTimestamp(newTs);
             if (oldTsValid) {
                 time_t sec = newTs.tv_sec - oldTs.tv_sec;
                 long nsec = newTs.tv_nsec - oldTs.tv_nsec;
diff --git a/services/audioflinger/FastMixerState.cpp b/services/audioflinger/FastMixerState.cpp
index 6305a83..c45c81b 100644
--- a/services/audioflinger/FastMixerState.cpp
+++ b/services/audioflinger/FastMixerState.cpp
@@ -31,7 +31,7 @@
 FastMixerState::FastMixerState() :
     mFastTracksGen(0), mTrackMask(0), mOutputSink(NULL), mOutputSinkGen(0),
     mFrameCount(0), mCommand(INITIAL), mColdFutexAddr(NULL), mColdGen(0),
-    mDumpState(NULL), mTeeSink(NULL)
+    mDumpState(NULL), mTeeSink(NULL), mNBLogWriter(NULL)
 {
 }
 
diff --git a/services/audioflinger/FastMixerState.h b/services/audioflinger/FastMixerState.h
index 6e53f21..f6e7903 100644
--- a/services/audioflinger/FastMixerState.h
+++ b/services/audioflinger/FastMixerState.h
@@ -20,6 +20,7 @@
 #include <system/audio.h>
 #include <media/ExtendedAudioBufferProvider.h>
 #include <media/nbaio/NBAIO.h>
+#include <media/nbaio/NBLog.h>
 
 namespace android {
 
@@ -77,6 +78,7 @@
     // This might be a one-time configuration rather than per-state
     FastMixerDumpState* mDumpState; // if non-NULL, then update dump state periodically
     NBAIO_Sink* mTeeSink;       // if non-NULL, then duplicate write()s to this non-blocking sink
+    NBLog::Writer* mNBLogWriter; // non-blocking logger
 };  // struct FastMixerState
 
 }   // namespace android
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 744a7df..ba848d7 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -936,6 +936,7 @@
         mFastTrackAvailMask(((1 << FastMixerState::kMaxFastTracks) - 1) & ~1)
 {
     snprintf(mName, kNameLength, "AudioOut_%X", id);
+    mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mName);
 
     // Assumes constructor is called by AudioFlinger with it's mLock held, but
     // it would be safer to explicitly pass initial masterVolume/masterMute as
@@ -971,6 +972,7 @@
 
 AudioFlinger::PlaybackThread::~PlaybackThread()
 {
+    mAudioFlinger->unregisterWriter(mNBLogWriter);
     delete [] mMixBuffer;
 }
 
@@ -1247,6 +1249,7 @@
     if (status) {
         *status = lStatus;
     }
+    mNBLogWriter->logf("createTrack_l");
     return track;
 }
 
@@ -1314,6 +1317,7 @@
 // addTrack_l() must be called with ThreadBase::mLock held
 status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
 {
+    mNBLogWriter->logf("addTrack_l mName=%d", track->mName);
     status_t status = ALREADY_EXISTS;
 
     // set retry count for buffer fill
@@ -1347,6 +1351,7 @@
 // destroyTrack_l() must be called with ThreadBase::mLock held
 void AudioFlinger::PlaybackThread::destroyTrack_l(const sp<Track>& track)
 {
+    mNBLogWriter->logf("destroyTrack_l mName=%d", track->mName);
     track->mState = TrackBase::TERMINATED;
     // active tracks are removed by threadLoop()
     if (mActiveTracks.indexOf(track) < 0) {
@@ -1356,6 +1361,7 @@
 
 void AudioFlinger::PlaybackThread::removeTrack_l(const sp<Track>& track)
 {
+    mNBLogWriter->logf("removeTrack_l mName=%d", track->mName);
     track->triggerEvents(AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE);
     mTracks.remove(track);
     deleteTrackName_l(track->name());
@@ -1892,6 +1898,11 @@
 
     acquireWakeLock();
 
+    // mNBLogWriter->log can only be called while thread mutex mLock is held.
+    // So if you need to log when mutex is unlocked, set logString to a non-NULL string,
+    // and then that string will be logged at the next convenient opportunity.
+    const char *logString = NULL;
+
     while (!exitPending())
     {
         cpuStats.sample(myName);
@@ -1904,6 +1915,12 @@
 
             Mutex::Autolock _l(mLock);
 
+            if (logString != NULL) {
+                mNBLogWriter->logTimestamp();
+                mNBLogWriter->log(logString);
+                logString = NULL;
+            }
+
             if (checkForNewParameters_l()) {
                 cacheParameters_l();
             }
@@ -1917,6 +1934,7 @@
 
                     threadLoop_standby();
 
+                    mNBLogWriter->log("standby");
                     mStandby = true;
                 }
 
@@ -2012,6 +2030,9 @@
         // since we can't guarantee the destructors won't acquire that
         // same lock.  This will also mutate and push a new fast mixer state.
         threadLoop_removeTracks(tracksToRemove);
+        if (tracksToRemove.size() > 0) {
+            logString = "remove";
+        }
         tracksToRemove.clear();
 
         // FIXME I don't understand the need for this here;
@@ -2143,6 +2164,8 @@
         state->mColdGen++;
         state->mDumpState = &mFastMixerDumpState;
         state->mTeeSink = mTeeSink.get();
+        mFastMixerNBLogWriter = audioFlinger->newWriter_l(kFastMixerLogSize, "FastMixer");
+        state->mNBLogWriter = mFastMixerNBLogWriter.get();
         sq->end();
         sq->push(FastMixerStateQueue::BLOCK_UNTIL_PUSHED);
 
@@ -2219,6 +2242,7 @@
         }
 #endif
     }
+    mAudioFlinger->unregisterWriter(mFastMixerNBLogWriter);
     delete mAudioMixer;
 }
 
@@ -2846,6 +2870,7 @@
     if (CC_UNLIKELY(count)) {
         for (size_t i=0 ; i<count ; i++) {
             const sp<Track>& track = tracksToRemove->itemAt(i);
+            mNBLogWriter->logf("prepareTracks_l remove name=%u", track->name());
             mActiveTracks.remove(track);
             if (track->mainBuffer() != mMixBuffer) {
                 chain = getEffectChain_l(track->sessionId());
@@ -3222,6 +3247,9 @@
     // remove all the tracks that need to be...
     if (CC_UNLIKELY(trackToRemove != 0)) {
         tracksToRemove->add(trackToRemove);
+#if 0
+        mNBLogWriter->logf("prepareTracks_l remove name=%u", trackToRemove->name());
+#endif
         mActiveTracks.remove(trackToRemove);
         if (!mEffectChains.isEmpty()) {
             ALOGV("stopping track on chain %p for session Id: %d", mEffectChains[0].get(),
@@ -3545,9 +3573,10 @@
                                          uint32_t sampleRate,
                                          audio_channel_mask_t channelMask,
                                          audio_io_handle_t id,
-                                         audio_devices_t device,
+                                         audio_devices_t outDevice,
+                                         audio_devices_t inDevice,
                                          const sp<NBAIO_Sink>& teeSink) :
-    ThreadBase(audioFlinger, id, AUDIO_DEVICE_NONE, device, RECORD),
+    ThreadBase(audioFlinger, id, outDevice, inDevice, RECORD),
     mInput(input), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL),
     // mRsmpInIndex and mInputBytes set by readInputParameters()
     mReqChannelCount(popcount(channelMask)),
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 06a1c8c..fa1e336 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -315,6 +315,8 @@
                 // keyed by session ID, the second by type UUID timeLow field
                 KeyedVector< int, KeyedVector< int, sp<SuspendedSessionDesc> > >
                                         mSuspendedSessions;
+                static const size_t     kLogSize = 512;
+                sp<NBLog::Writer>       mNBLogWriter;
 };
 
 // --- PlaybackThread ---
@@ -544,6 +546,8 @@
     sp<NBAIO_Sink>          mTeeSink;
     sp<NBAIO_Source>        mTeeSource;
     uint32_t                mScreenState;   // cached copy of gScreenState
+    static const size_t     kFastMixerLogSize = 8 * 1024;
+    sp<NBLog::Writer>       mFastMixerNBLogWriter;
 public:
     virtual     bool        hasFastMixer() const = 0;
     virtual     FastTrackUnderruns getFastTrackUnderruns(size_t fastIndex) const
@@ -698,7 +702,8 @@
                     uint32_t sampleRate,
                     audio_channel_mask_t channelMask,
                     audio_io_handle_t id,
-                    audio_devices_t device,
+                    audio_devices_t outDevice,
+                    audio_devices_t inDevice,
                     const sp<NBAIO_Sink>& teeSink);
             virtual     ~RecordThread();
 
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index c5f0ed7..315cbbc 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -569,6 +569,7 @@
     sp<ThreadBase> thread = mThread.promote();
     if (thread != 0) {
         Mutex::Autolock _l(thread->mLock);
+        thread->mNBLogWriter->logf("start mName=%d", mName);
         track_state state = mState;
         // here the track could be either new, or restarted
         // in both cases "unstop" the track
@@ -611,6 +612,7 @@
     sp<ThreadBase> thread = mThread.promote();
     if (thread != 0) {
         Mutex::Autolock _l(thread->mLock);
+        thread->mNBLogWriter->logf("stop mName=%d", mName);
         track_state state = mState;
         if (state == RESUMING || state == ACTIVE || state == PAUSING || state == PAUSED) {
             // If the track is not active (PAUSED and buffers full), flush buffers
@@ -647,6 +649,7 @@
     sp<ThreadBase> thread = mThread.promote();
     if (thread != 0) {
         Mutex::Autolock _l(thread->mLock);
+        thread->mNBLogWriter->logf("pause mName=%d", mName);
         if (mState == ACTIVE || mState == RESUMING) {
             mState = PAUSING;
             ALOGV("ACTIVE/RESUMING => PAUSING (%d) on thread %p", mName, thread.get());
@@ -670,6 +673,7 @@
     sp<ThreadBase> thread = mThread.promote();
     if (thread != 0) {
         Mutex::Autolock _l(thread->mLock);
+        thread->mNBLogWriter->logf("flush mName=%d", mName);
         if (mState != STOPPING_1 && mState != STOPPING_2 && mState != STOPPED && mState != PAUSED &&
                 mState != PAUSING && mState != IDLE && mState != FLUSHED) {
             return;
diff --git a/services/medialog/Android.mk b/services/medialog/Android.mk
new file mode 100644
index 0000000..559b1ed
--- /dev/null
+++ b/services/medialog/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := MediaLogService.cpp
+
+LOCAL_SHARED_LIBRARIES := libmedia libbinder libutils libnbaio
+
+LOCAL_MODULE:= libmedialogservice
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/services/medialog/MediaLogService.cpp b/services/medialog/MediaLogService.cpp
new file mode 100644
index 0000000..2332b3e
--- /dev/null
+++ b/services/medialog/MediaLogService.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 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_TAG "MediaLog"
+//#define LOG_NDEBUG 0
+
+#include <sys/mman.h>
+#include <utils/Log.h>
+#include <media/nbaio/NBLog.h>
+#include <private/android_filesystem_config.h>
+#include "MediaLogService.h"
+
+namespace android {
+
+void MediaLogService::registerWriter(const sp<IMemory>& shared, size_t size, const char *name)
+{
+    if (IPCThreadState::self()->getCallingUid() != AID_MEDIA || shared == 0 ||
+            size < kMinSize || size > kMaxSize || name == NULL ||
+            shared->size() < NBLog::Timeline::sharedSize(size)) {
+        return;
+    }
+    sp<NBLog::Reader> reader(new NBLog::Reader(size, shared));
+    NamedReader namedReader(reader, name);
+    Mutex::Autolock _l(mLock);
+    mNamedReaders.add(namedReader);
+}
+
+void MediaLogService::unregisterWriter(const sp<IMemory>& shared)
+{
+    if (IPCThreadState::self()->getCallingUid() != AID_MEDIA || shared == 0) {
+        return;
+    }
+    Mutex::Autolock _l(mLock);
+    for (size_t i = 0; i < mNamedReaders.size(); ) {
+        if (mNamedReaders[i].reader()->isIMemory(shared)) {
+            mNamedReaders.removeAt(i);
+        } else {
+            i++;
+        }
+    }
+}
+
+status_t MediaLogService::dump(int fd, const Vector<String16>& args)
+{
+    Vector<NamedReader> namedReaders;
+    {
+        Mutex::Autolock _l(mLock);
+        namedReaders = mNamedReaders;
+    }
+    for (size_t i = 0; i < namedReaders.size(); i++) {
+        const NamedReader& namedReader = namedReaders[i];
+        if (fd >= 0) {
+            fdprintf(fd, "\n%s:\n", namedReader.name());
+        } else {
+            ALOGI("%s:", namedReader.name());
+        }
+        namedReader.reader()->dump(fd, 0 /*indent*/);
+    }
+    return NO_ERROR;
+}
+
+status_t MediaLogService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+        uint32_t flags)
+{
+    return BnMediaLogService::onTransact(code, data, reply, flags);
+}
+
+}   // namespace android
diff --git a/services/medialog/MediaLogService.h b/services/medialog/MediaLogService.h
new file mode 100644
index 0000000..2d89a41
--- /dev/null
+++ b/services/medialog/MediaLogService.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_LOG_SERVICE_H
+#define ANDROID_MEDIA_LOG_SERVICE_H
+
+#include <binder/BinderService.h>
+#include <media/IMediaLogService.h>
+#include <media/nbaio/NBLog.h>
+
+namespace android {
+
+class MediaLogService : public BinderService<MediaLogService>, public BnMediaLogService
+{
+    friend class BinderService<MediaLogService>;    // for MediaLogService()
+public:
+    MediaLogService() : BnMediaLogService() { }
+    virtual ~MediaLogService() { }
+    virtual void onFirstRef() { }
+
+    static const char*  getServiceName() { return "media.log"; }
+
+    static const size_t kMinSize = 0x100;
+    static const size_t kMaxSize = 0x10000;
+    virtual void        registerWriter(const sp<IMemory>& shared, size_t size, const char *name);
+    virtual void        unregisterWriter(const sp<IMemory>& shared);
+
+    virtual status_t    dump(int fd, const Vector<String16>& args);
+    virtual status_t    onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+                                uint32_t flags);
+
+private:
+    Mutex               mLock;
+    class NamedReader {
+    public:
+        NamedReader() : mReader(0) { mName[0] = '\0'; } // for Vector
+        NamedReader(const sp<NBLog::Reader>& reader, const char *name) : mReader(reader)
+            { strlcpy(mName, name, sizeof(mName)); }
+        ~NamedReader() { }
+        const sp<NBLog::Reader>&  reader() const { return mReader; }
+        const char*               name() const { return mName; }
+    private:
+        sp<NBLog::Reader>   mReader;
+        static const size_t kMaxName = 32;
+        char                mName[kMaxName];
+    };
+    Vector<NamedReader> mNamedReaders;
+};
+
+}   // namespace android
+
+#endif  // ANDROID_MEDIA_LOG_SERVICE_H