Initial checkin of support for acting as a wifi display source

Change-Id: I08f17efa0c7d007e17408feb7d4fbef0a19f531a
diff --git a/include/media/IMediaPlayerService.h b/include/media/IMediaPlayerService.h
index 76c45a0..dbcdf92 100644
--- a/include/media/IMediaPlayerService.h
+++ b/include/media/IMediaPlayerService.h
@@ -50,6 +50,8 @@
     virtual sp<IOMX>            getOMX() = 0;
     virtual sp<ICrypto>         makeCrypto() = 0;
 
+    virtual status_t enableRemoteDisplay(bool enable) = 0;
+
     // codecs and audio devices usage tracking for the battery app
     enum BatteryDataBits {
         // tracking audio codec
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index 2371619..500dde6 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -25,6 +25,8 @@
 #include <media/stagefright/SkipCutBuffer.h>
 #include <OMX_Audio.h>
 
+#define TRACK_BUFFER_TIMING     0
+
 namespace android {
 
 struct ABuffer;
@@ -127,6 +129,15 @@
         sp<GraphicBuffer> mGraphicBuffer;
     };
 
+#if TRACK_BUFFER_TIMING
+    struct BufferStats {
+        int64_t mEmptyBufferTimeUs;
+        int64_t mFillBufferDoneTimeUs;
+    };
+
+    KeyedVector<int64_t, BufferStats> mBufferStats;
+#endif
+
     sp<AMessage> mNotify;
 
     sp<UninitializedState> mUninitializedState;
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index 9120617..41969b1 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -38,6 +38,7 @@
     CREATE_METADATA_RETRIEVER,
     GET_OMX,
     MAKE_CRYPTO,
+    ENABLE_REMOTE_DISPLAY,
     ADD_BATTERY_DATA,
     PULL_BATTERY_DATA
 };
@@ -120,6 +121,14 @@
         return interface_cast<ICrypto>(reply.readStrongBinder());
     }
 
+    virtual status_t enableRemoteDisplay(bool enable) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
+        data.writeInt32(enable);
+        remote()->transact(ENABLE_REMOTE_DISPLAY, data, &reply);
+        return reply.readInt32();
+    }
+
     virtual void addBatteryData(uint32_t params) {
         Parcel data, reply;
         data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
@@ -206,6 +215,12 @@
             reply->writeStrongBinder(crypto->asBinder());
             return NO_ERROR;
         } break;
+        case ENABLE_REMOTE_DISPLAY: {
+            CHECK_INTERFACE(IMediaPlayerService, data, reply);
+            bool enable = data.readInt32();
+            reply->writeInt32(enableRemoteDisplay(enable));
+            return NO_ERROR;
+        } break;
         case ADD_BATTERY_DATA: {
             CHECK_INTERFACE(IMediaPlayerService, data, reply);
             uint32_t params = data.readInt32();
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index 1373d3c..c7227b0 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -9,45 +9,47 @@
 LOCAL_SRC_FILES:=               \
     ActivityManager.cpp         \
     Crypto.cpp                  \
-    MediaRecorderClient.cpp     \
     MediaPlayerFactory.cpp      \
     MediaPlayerService.cpp      \
+    MediaRecorderClient.cpp     \
     MetadataRetrieverClient.cpp \
-    TestPlayerStub.cpp          \
-    MidiMetadataRetriever.cpp   \
     MidiFile.cpp                \
+    MidiMetadataRetriever.cpp   \
+    RemoteDisplay.cpp           \
     StagefrightPlayer.cpp       \
-    StagefrightRecorder.cpp
+    StagefrightRecorder.cpp     \
+    TestPlayerStub.cpp          \
 
-LOCAL_SHARED_LIBRARIES :=     		\
-	libcutils             			\
-	libutils              			\
-	libbinder             			\
-	libvorbisidec         			\
-	libsonivox            			\
-	libmedia              			\
-	libmedia_native       			\
-	libcamera_client      			\
-	libstagefright        			\
-	libstagefright_omx    			\
-	libstagefright_foundation       \
-	libgui                          \
-	libdl
+LOCAL_SHARED_LIBRARIES :=       \
+    libbinder                   \
+    libcamera_client            \
+    libcutils                   \
+    libdl                       \
+    libgui                      \
+    libmedia                    \
+    libmedia_native             \
+    libsonivox                  \
+    libstagefright              \
+    libstagefright_foundation   \
+    libstagefright_omx          \
+    libstagefright_wfd          \
+    libutils                    \
+    libvorbisidec               \
 
-LOCAL_STATIC_LIBRARIES := \
-        libstagefright_nuplayer                 \
-        libstagefright_rtsp                     \
+LOCAL_STATIC_LIBRARIES :=       \
+    libstagefright_nuplayer     \
+    libstagefright_rtsp         \
 
-LOCAL_C_INCLUDES :=                                               \
-	$(call include-path-for, graphics corecg)                       \
-	$(TOP)/frameworks/av/media/libstagefright/include               \
-	$(TOP)/frameworks/av/media/libstagefright/rtsp                  \
-	$(TOP)/frameworks/native/include/media/openmax                  \
-	$(TOP)/external/tremolo/Tremolo                                 \
+LOCAL_C_INCLUDES :=                                                 \
+    $(call include-path-for, graphics corecg)                       \
+    $(TOP)/frameworks/av/media/libstagefright/include               \
+    $(TOP)/frameworks/av/media/libstagefright/rtsp                  \
+    $(TOP)/frameworks/av/media/libstagefright/wifi-display          \
+    $(TOP)/frameworks/native/include/media/openmax                  \
+    $(TOP)/external/tremolo/Tremolo                                 \
 
 LOCAL_MODULE:= libmediaplayerservice
 
 include $(BUILD_SHARED_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 6346363..5fe446f 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -70,6 +70,7 @@
 #include <OMX.h>
 
 #include "Crypto.h"
+#include "RemoteDisplay.h"
 
 namespace {
 using android::media::Metadata;
@@ -278,6 +279,28 @@
     return new Crypto;
 }
 
+status_t MediaPlayerService::enableRemoteDisplay(bool enable) {
+    Mutex::Autolock autoLock(mLock);
+
+    if (enable && mRemoteDisplay == NULL) {
+        mRemoteDisplay = new RemoteDisplay;
+
+        status_t err = mRemoteDisplay->start();
+
+        if (err != OK) {
+            mRemoteDisplay.clear();
+            return err;
+        }
+
+        return OK;
+    } else if (!enable && mRemoteDisplay != NULL) {
+        mRemoteDisplay->stop();
+        mRemoteDisplay.clear();
+    }
+
+    return OK;
+}
+
 status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const
 {
     const size_t SIZE = 256;
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 6ede9a4..8fbc5d5 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -42,6 +42,7 @@
 class IMediaMetadataRetriever;
 class IOMX;
 class MediaRecorderClient;
+struct RemoteDisplay;
 
 #define CALLBACK_ANTAGONIZER 0
 #if CALLBACK_ANTAGONIZER
@@ -247,6 +248,7 @@
     virtual sp<IMemory>         decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
     virtual sp<IOMX>            getOMX();
     virtual sp<ICrypto>         makeCrypto();
+    virtual status_t            enableRemoteDisplay(bool enable);
 
     virtual status_t            dump(int fd, const Vector<String16>& args);
 
@@ -423,6 +425,7 @@
                 int32_t                     mNextConnId;
                 sp<IOMX>                    mOMX;
                 sp<ICrypto>                 mCrypto;
+                sp<RemoteDisplay>           mRemoteDisplay;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/media/libmediaplayerservice/RemoteDisplay.cpp b/media/libmediaplayerservice/RemoteDisplay.cpp
new file mode 100644
index 0000000..855824a
--- /dev/null
+++ b/media/libmediaplayerservice/RemoteDisplay.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 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.
+ */
+
+#include "RemoteDisplay.h"
+
+#include "ANetworkSession.h"
+#include "source/WifiDisplaySource.h"
+
+namespace android {
+
+RemoteDisplay::RemoteDisplay()
+    : mInitCheck(NO_INIT),
+      mLooper(new ALooper),
+      mNetSession(new ANetworkSession),
+      mSource(new WifiDisplaySource(mNetSession)) {
+    mLooper->registerHandler(mSource);
+}
+
+RemoteDisplay::~RemoteDisplay() {
+}
+
+status_t RemoteDisplay::start() {
+    mNetSession->start();
+    mLooper->start();
+
+    // XXX replace with 8554 for bcom dongle (it doesn't respect the
+    // default port or the one advertised in the wfd IE).
+    mSource->start(WifiDisplaySource::kWifiDisplayDefaultPort);
+
+    return OK;
+}
+
+status_t RemoteDisplay::stop() {
+    mSource->stop();
+
+    mLooper->stop();
+    mNetSession->stop();
+
+    return OK;
+}
+
+}  // namespace android
+
diff --git a/media/libmediaplayerservice/RemoteDisplay.h b/media/libmediaplayerservice/RemoteDisplay.h
new file mode 100644
index 0000000..6b37afb
--- /dev/null
+++ b/media/libmediaplayerservice/RemoteDisplay.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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 REMOTE_DISPLAY_H_
+
+#define REMOTE_DISPLAY_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ALooper;
+struct ANetworkSession;
+struct WifiDisplaySource;
+
+struct RemoteDisplay : public RefBase {
+    RemoteDisplay();
+
+    status_t start();
+    status_t stop();
+
+protected:
+    virtual ~RemoteDisplay();
+
+private:
+    status_t mInitCheck;
+
+    sp<ALooper> mNetLooper;
+    sp<ALooper> mLooper;
+    sp<ANetworkSession> mNetSession;
+    sp<WifiDisplaySource> mSource;
+
+    DISALLOW_EVIL_CONSTRUCTORS(RemoteDisplay);
+};
+
+}  // namespace android
+
+#endif  // REMOTE_DISPLAY_H_
+
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index c37d2ca..3dd5d60 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -861,6 +861,20 @@
         return INVALID_OPERATION;
     }
 
+    int32_t storeMeta;
+    if (encoder
+            && msg->findInt32("store-metadata-in-buffers", &storeMeta)
+            && storeMeta != 0) {
+        err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexInput, OMX_TRUE);
+
+        if (err != OK) {
+            ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d",
+                  mComponentName.c_str(), err);
+
+            return err;
+        }
+    }
+
     if (!strncasecmp(mime, "video/", 6)) {
         if (encoder) {
             err = setupVideoEncoder(mime, msg);
@@ -2424,6 +2438,21 @@
     CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_COMPONENT);
     info->mStatus = BufferInfo::OWNED_BY_US;
 
+    const sp<AMessage> &bufferMeta = info->mData->meta();
+    void *mediaBuffer;
+    if (bufferMeta->findPointer("mediaBuffer", &mediaBuffer)
+            && mediaBuffer != NULL) {
+        // We're in "store-metadata-in-buffers" mode, the underlying
+        // OMX component had access to data that's implicitly refcounted
+        // by this "mediaBuffer" object. Now that the OMX component has
+        // told us that it's done with the input buffer, we can decrement
+        // the mediaBuffer's reference count.
+        ((MediaBuffer *)mediaBuffer)->release();
+        mediaBuffer = NULL;
+
+        bufferMeta->setPointer("mediaBuffer", NULL);
+    }
+
     PortMode mode = getPortMode(kPortIndexInput);
 
     switch (mode) {
@@ -2531,10 +2560,10 @@
                 }
 
                 if (buffer != info->mData) {
-                    if (0 && !(flags & OMX_BUFFERFLAG_CODECCONFIG)) {
-                        ALOGV("[%s] Needs to copy input data.",
-                             mCodec->mComponentName.c_str());
-                    }
+                    ALOGV("[%s] Needs to copy input data for buffer %p. (%p != %p)",
+                         mCodec->mComponentName.c_str(),
+                         bufferID,
+                         buffer.get(), info->mData.get());
 
                     CHECK_LE(buffer->size(), info->mData->capacity());
                     memcpy(info->mData->data(), buffer->data(), buffer->size());
@@ -2547,10 +2576,22 @@
                     ALOGV("[%s] calling emptyBuffer %p w/ EOS",
                          mCodec->mComponentName.c_str(), bufferID);
                 } else {
+#if TRACK_BUFFER_TIMING
+                    ALOGI("[%s] calling emptyBuffer %p w/ time %lld us",
+                         mCodec->mComponentName.c_str(), bufferID, timeUs);
+#else
                     ALOGV("[%s] calling emptyBuffer %p w/ time %lld us",
                          mCodec->mComponentName.c_str(), bufferID, timeUs);
+#endif
                 }
 
+#if TRACK_BUFFER_TIMING
+                ACodec::BufferStats stats;
+                stats.mEmptyBufferTimeUs = ALooper::GetNowUs();
+                stats.mFillBufferDoneTimeUs = -1ll;
+                mCodec->mBufferStats.add(timeUs, stats);
+#endif
+
                 CHECK_EQ(mCodec->mOMX->emptyBuffer(
                             mCodec->mNode,
                             bufferID,
@@ -2647,6 +2688,22 @@
          mCodec->mComponentName.c_str(), bufferID, timeUs, flags);
 
     ssize_t index;
+
+#if TRACK_BUFFER_TIMING
+    index = mCodec->mBufferStats.indexOfKey(timeUs);
+    if (index >= 0) {
+        ACodec::BufferStats *stats = &mCodec->mBufferStats.editValueAt(index);
+        stats->mFillBufferDoneTimeUs = ALooper::GetNowUs();
+
+        ALOGI("frame PTS %lld: %lld",
+                timeUs,
+                stats->mFillBufferDoneTimeUs - stats->mEmptyBufferTimeUs);
+
+        mCodec->mBufferStats.removeItemsAt(index);
+        stats = NULL;
+    }
+#endif
+
     BufferInfo *info =
         mCodec->findBufferByID(kPortIndexOutput, bufferID, &index);
 
@@ -2891,7 +2948,7 @@
     AString mime;
 
     AString componentName;
-    uint32_t quirks;
+    uint32_t quirks = 0;
     if (msg->findString("componentName", &componentName)) {
         ssize_t index = matchingCodecs.add();
         OMXCodec::CodecNameAndQuirks *entry = &matchingCodecs.editItemAt(index);
diff --git a/media/libstagefright/wifi-display/ANetworkSession.h b/media/libstagefright/wifi-display/ANetworkSession.h
index 0402317..d4cd14f 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.h
+++ b/media/libstagefright/wifi-display/ANetworkSession.h
@@ -27,6 +27,8 @@
 
 struct AMessage;
 
+// Helper class to manage a number of live sockets (datagram and stream-based)
+// on a single thread. Clients are notified about activity through AMessages.
 struct ANetworkSession : public RefBase {
     ANetworkSession();
 
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index 114ff62..b035a51 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -3,9 +3,42 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-        udptest.cpp                 \
-        ANetworkSession.cpp         \
-        ParsedMessage.cpp           \
+        ANetworkSession.cpp             \
+        ParsedMessage.cpp               \
+        source/Converter.cpp            \
+        source/PlaybackSession.cpp      \
+        source/RepeaterSource.cpp       \
+        source/Serializer.cpp           \
+        source/TSPacketizer.cpp         \
+        source/WifiDisplaySource.cpp    \
+
+LOCAL_C_INCLUDES:= \
+        $(TOP)/frameworks/av/media/libstagefright \
+        $(TOP)/frameworks/native/include/media/openmax \
+        $(TOP)/frameworks/av/media/libstagefright/mpeg2ts \
+
+LOCAL_SHARED_LIBRARIES:= \
+        libbinder                       \
+        libcutils                       \
+        libgui                          \
+        libmedia                        \
+        libstagefright                  \
+        libstagefright_foundation       \
+        libui                           \
+        libutils                        \
+
+LOCAL_MODULE:= libstagefright_wfd
+
+LOCAL_MODULE_TAGS:= optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+        wfd.cpp                 \
 
 LOCAL_SHARED_LIBRARIES:= \
         libbinder                       \
@@ -13,6 +46,29 @@
         libmedia                        \
         libstagefright                  \
         libstagefright_foundation       \
+        libstagefright_wfd              \
+        libutils                        \
+
+LOCAL_MODULE:= wfd
+
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+        udptest.cpp                 \
+
+LOCAL_SHARED_LIBRARIES:= \
+        libbinder                       \
+        libgui                          \
+        libmedia                        \
+        libstagefright                  \
+        libstagefright_foundation       \
+        libstagefright_wfd              \
         libutils                        \
 
 LOCAL_MODULE:= udptest
diff --git a/media/libstagefright/wifi-display/ParsedMessage.h b/media/libstagefright/wifi-display/ParsedMessage.h
index 00f578f..e9a1859 100644
--- a/media/libstagefright/wifi-display/ParsedMessage.h
+++ b/media/libstagefright/wifi-display/ParsedMessage.h
@@ -21,6 +21,8 @@
 
 namespace android {
 
+// Encapsulates an "HTTP/RTSP style" response, i.e. a status line,
+// key/value pairs making up the headers and an optional body/content.
 struct ParsedMessage : public RefBase {
     static sp<ParsedMessage> Parse(
             const char *data, size_t size, bool noMoreData, size_t *length);
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
new file mode 100644
index 0000000..655fbae
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright 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 "Converter"
+#include <utils/Log.h>
+
+#include "Converter.h"
+
+#include <gui/SurfaceTextureClient.h>
+#include <media/ICrypto.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+Converter::Converter(
+        const sp<AMessage> &notify,
+        const sp<ALooper> &codecLooper,
+        const sp<AMessage> &format)
+    : mInitCheck(NO_INIT),
+      mNotify(notify),
+      mCodecLooper(codecLooper),
+      mInputFormat(format),
+      mDoMoreWorkPending(false) {
+    mInitCheck = initEncoder();
+}
+
+Converter::~Converter() {
+    if (mEncoder != NULL) {
+        mEncoder->release();
+        mEncoder.clear();
+    }
+}
+
+status_t Converter::initCheck() const {
+    return mInitCheck;
+}
+
+sp<AMessage> Converter::getOutputFormat() const {
+    return mOutputFormat;
+}
+
+status_t Converter::initEncoder() {
+    AString inputMIME;
+    CHECK(mInputFormat->findString("mime", &inputMIME));
+
+    AString outputMIME;
+    bool isAudio = false;
+    if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_AUDIO_RAW)) {
+        outputMIME = MEDIA_MIMETYPE_AUDIO_AAC;
+        isAudio = true;
+    } else if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_VIDEO_RAW)) {
+        outputMIME = MEDIA_MIMETYPE_VIDEO_AVC;
+    } else {
+        TRESPASS();
+    }
+
+    mEncoder = MediaCodec::CreateByType(
+            mCodecLooper, outputMIME.c_str(), true /* encoder */);
+
+    if (mEncoder == NULL) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    mOutputFormat = mInputFormat->dup();
+    mOutputFormat->setString("mime", outputMIME.c_str());
+
+    if (isAudio) {
+        mOutputFormat->setInt32("bitrate", 64000);      // 64 kBit/sec
+    } else {
+        mOutputFormat->setInt32("bitrate", 3000000);    // 3Mbit/sec
+        mOutputFormat->setInt32("frame-rate", 30);
+        mOutputFormat->setInt32("i-frame-interval", 3);  // Iframes every 3 secs
+    }
+
+    ALOGV("output format is '%s'", mOutputFormat->debugString(0).c_str());
+
+    status_t err = mEncoder->configure(
+            mOutputFormat,
+            NULL /* nativeWindow */,
+            NULL /* crypto */,
+            MediaCodec::CONFIGURE_FLAG_ENCODE);
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = mEncoder->start();
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = mEncoder->getInputBuffers(&mEncoderInputBuffers);
+
+    if (err != OK) {
+        return err;
+    }
+
+    return mEncoder->getOutputBuffers(&mEncoderOutputBuffers);
+}
+
+void Converter::feedAccessUnit(const sp<ABuffer> &accessUnit) {
+    sp<AMessage> msg = new AMessage(kWhatFeedAccessUnit, id());
+    msg->setBuffer("accessUnit", accessUnit);
+    msg->post();
+}
+
+void Converter::signalEOS() {
+    (new AMessage(kWhatInputEOS, id()))->post();
+}
+
+void Converter::notifyError(status_t err) {
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatError);
+    notify->setInt32("err", err);
+    notify->post();
+}
+
+void Converter::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatFeedAccessUnit:
+        {
+            sp<ABuffer> accessUnit;
+            CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+            mInputBufferQueue.push_back(accessUnit);
+
+            feedEncoderInputBuffers();
+
+            scheduleDoMoreWork();
+            break;
+        }
+
+        case kWhatInputEOS:
+        {
+            mInputBufferQueue.push_back(NULL);
+
+            feedEncoderInputBuffers();
+
+            scheduleDoMoreWork();
+            break;
+        }
+
+        case kWhatDoMoreWork:
+        {
+            mDoMoreWorkPending = false;
+            status_t err = doMoreWork();
+
+            if (err != OK) {
+                notifyError(err);
+            } else {
+                scheduleDoMoreWork();
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+void Converter::scheduleDoMoreWork() {
+    if (mDoMoreWorkPending) {
+        return;
+    }
+
+    mDoMoreWorkPending = true;
+    (new AMessage(kWhatDoMoreWork, id()))->post(1000ll);
+}
+
+status_t Converter::feedEncoderInputBuffers() {
+    while (!mInputBufferQueue.empty()
+            && !mAvailEncoderInputIndices.empty()) {
+        sp<ABuffer> buffer = *mInputBufferQueue.begin();
+        mInputBufferQueue.erase(mInputBufferQueue.begin());
+
+        size_t bufferIndex = *mAvailEncoderInputIndices.begin();
+        mAvailEncoderInputIndices.erase(mAvailEncoderInputIndices.begin());
+
+        int64_t timeUs = 0ll;
+        uint32_t flags = 0;
+
+        if (buffer != NULL) {
+            CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+
+            memcpy(mEncoderInputBuffers.itemAt(bufferIndex)->data(),
+                   buffer->data(),
+                   buffer->size());
+
+            void *mediaBuffer;
+            if (buffer->meta()->findPointer("mediaBuffer", &mediaBuffer)
+                    && mediaBuffer != NULL) {
+                mEncoderInputBuffers.itemAt(bufferIndex)->meta()
+                    ->setPointer("mediaBuffer", mediaBuffer);
+
+                buffer->meta()->setPointer("mediaBuffer", NULL);
+            }
+        } else {
+            flags = MediaCodec::BUFFER_FLAG_EOS;
+        }
+
+        status_t err = mEncoder->queueInputBuffer(
+                bufferIndex, 0, (buffer == NULL) ? 0 : buffer->size(),
+                timeUs, flags);
+
+        if (err != OK) {
+            return err;
+        }
+    }
+
+    return OK;
+}
+
+status_t Converter::doMoreWork() {
+    size_t bufferIndex;
+    status_t err = mEncoder->dequeueInputBuffer(&bufferIndex);
+
+    if (err == OK) {
+        mAvailEncoderInputIndices.push_back(bufferIndex);
+        feedEncoderInputBuffers();
+    }
+
+    size_t offset;
+    size_t size;
+    int64_t timeUs;
+    uint32_t flags;
+    err = mEncoder->dequeueOutputBuffer(
+            &bufferIndex, &offset, &size, &timeUs, &flags);
+
+    if (err == OK) {
+        if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatEOS);
+            notify->post();
+        } else {
+            sp<ABuffer> buffer = new ABuffer(size);
+            buffer->meta()->setInt64("timeUs", timeUs);
+
+            memcpy(buffer->data(),
+                   mEncoderOutputBuffers.itemAt(bufferIndex)->base() + offset,
+                   size);
+
+            if (flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) {
+                mOutputFormat->setBuffer("csd-0", buffer);
+            } else {
+                sp<AMessage> notify = mNotify->dup();
+                notify->setInt32("what", kWhatAccessUnit);
+                notify->setBuffer("accessUnit", buffer);
+                notify->post();
+            }
+        }
+
+        err = mEncoder->releaseOutputBuffer(bufferIndex);
+    } else if (err == -EAGAIN) {
+        err = OK;
+    }
+
+    return err;
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h
new file mode 100644
index 0000000..6700a32
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Converter.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 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 CONVERTER_H_
+
+#define CONVERTER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct MediaCodec;
+
+// Utility class that receives media access units and converts them into
+// media access unit of a different format.
+// Right now this'll convert raw video into H.264 and raw audio into AAC.
+struct Converter : public AHandler {
+    Converter(
+            const sp<AMessage> &notify,
+            const sp<ALooper> &codecLooper,
+            const sp<AMessage> &format);
+
+    status_t initCheck() const;
+
+    sp<AMessage> getOutputFormat() const;
+
+    void feedAccessUnit(const sp<ABuffer> &accessUnit);
+    void signalEOS();
+
+    enum {
+        kWhatAccessUnit,
+        kWhatEOS,
+        kWhatError,
+    };
+
+protected:
+    virtual ~Converter();
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    enum {
+        kWhatFeedAccessUnit,
+        kWhatInputEOS,
+        kWhatDoMoreWork
+    };
+
+    status_t mInitCheck;
+    sp<AMessage> mNotify;
+    sp<ALooper> mCodecLooper;
+    sp<AMessage> mInputFormat;
+    sp<AMessage> mOutputFormat;
+
+    sp<MediaCodec> mEncoder;
+
+    Vector<sp<ABuffer> > mEncoderInputBuffers;
+    Vector<sp<ABuffer> > mEncoderOutputBuffers;
+
+    List<size_t> mAvailEncoderInputIndices;
+
+    List<sp<ABuffer> > mInputBufferQueue;
+
+    bool mDoMoreWorkPending;
+
+    status_t initEncoder();
+
+    status_t feedEncoderInputBuffers();
+
+    void scheduleDoMoreWork();
+    status_t doMoreWork();
+
+    void notifyError(status_t err);
+
+    DISALLOW_EVIL_CONSTRUCTORS(Converter);
+};
+
+}  // namespace android
+
+#endif  // CONVERTER_H_
+
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
new file mode 100644
index 0000000..5095c15
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -0,0 +1,952 @@
+/*
+ * Copyright 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 "PlaybackSession"
+#include <utils/Log.h>
+
+#include "PlaybackSession.h"
+
+#include "Converter.h"
+#include "RepeaterSource.h"
+#include "Serializer.h"
+#include "TSPacketizer.h"
+
+#include <binder/IServiceManager.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#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>
+#include <ui/DisplayInfo.h>
+
+#include <OMX_IVCommon.h>
+
+#define FAKE_VIDEO      0
+
+namespace android {
+
+static size_t kMaxRTPPacketSize = 1500;
+static size_t kMaxNumTSPacketsPerRTPPacket = (kMaxRTPPacketSize - 12) / 188;
+
+struct WifiDisplaySource::PlaybackSession::Track : public RefBase {
+    Track(const sp<Converter> &converter);
+    Track(const sp<AMessage> &format);
+
+    sp<AMessage> getFormat();
+
+    const sp<Converter> &converter() const;
+    ssize_t packetizerTrackIndex() const;
+
+    void setPacketizerTrackIndex(size_t index);
+
+protected:
+    virtual ~Track();
+
+private:
+    sp<Converter> mConverter;
+    sp<AMessage> mFormat;
+    ssize_t mPacketizerTrackIndex;
+
+    DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+WifiDisplaySource::PlaybackSession::Track::Track(const sp<Converter> &converter)
+    : mConverter(converter),
+      mPacketizerTrackIndex(-1) {
+}
+
+WifiDisplaySource::PlaybackSession::Track::Track(const sp<AMessage> &format)
+    : mFormat(format),
+      mPacketizerTrackIndex(-1) {
+}
+
+WifiDisplaySource::PlaybackSession::Track::~Track() {
+}
+
+sp<AMessage> WifiDisplaySource::PlaybackSession::Track::getFormat() {
+    if (mFormat != NULL) {
+        return mFormat;
+    }
+
+    return mConverter->getOutputFormat();
+}
+
+const sp<Converter> &WifiDisplaySource::PlaybackSession::Track::converter() const {
+    return mConverter;
+}
+
+ssize_t WifiDisplaySource::PlaybackSession::Track::packetizerTrackIndex() const {
+    return mPacketizerTrackIndex;
+}
+
+void WifiDisplaySource::PlaybackSession::Track::setPacketizerTrackIndex(size_t index) {
+    CHECK_LT(mPacketizerTrackIndex, 0);
+    mPacketizerTrackIndex = index;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+WifiDisplaySource::PlaybackSession::PlaybackSession(
+        const sp<ANetworkSession> &netSession,
+        const sp<AMessage> &notify)
+    : mNetSession(netSession),
+      mNotify(notify),
+      mLastLifesignUs(),
+      mTSQueue(new ABuffer(12 + kMaxNumTSPacketsPerRTPPacket * 188)),
+      mPrevTimeUs(-1ll),
+      mUseInterleavedTCP(false),
+      mRTPChannel(0),
+      mRTCPChannel(0),
+      mRTPPort(0),
+      mRTPSessionID(0),
+      mRTCPSessionID(0),
+      mRTPSeqNo(0),
+      mLastNTPTime(0),
+      mLastRTPTime(0),
+      mNumRTPSent(0),
+      mNumRTPOctetsSent(0),
+      mNumSRsSent(0),
+      mSendSRPending(false),
+      mFirstPacketTimeUs(-1ll),
+      mHistoryLength(0) {
+    mTSQueue->setRange(0, 12);
+}
+
+status_t WifiDisplaySource::PlaybackSession::init(
+        const char *clientIP, int32_t clientRtp, int32_t clientRtcp,
+        bool useInterleavedTCP) {
+    status_t err = setupPacketizer();
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (useInterleavedTCP) {
+        mUseInterleavedTCP = true;
+        mRTPChannel = clientRtp;
+        mRTCPChannel = clientRtcp;
+        mRTPPort = 0;
+        mRTPSessionID = 0;
+        mRTCPSessionID = 0;
+
+        updateLiveness();
+        return OK;
+    }
+
+    mUseInterleavedTCP = false;
+    mRTPChannel = 0;
+    mRTCPChannel = 0;
+
+    int serverRtp;
+
+    sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id());
+    sp<AMessage> rtcpNotify = new AMessage(kWhatRTCPNotify, id());
+    for (serverRtp = 15550;; serverRtp += 2) {
+        int32_t rtpSession;
+        err = mNetSession->createUDPSession(
+                    serverRtp, clientIP, clientRtp,
+                    rtpNotify, &rtpSession);
+
+        if (err != OK) {
+            ALOGI("failed to create RTP socket on port %d", serverRtp);
+            continue;
+        }
+
+        if (clientRtcp < 0) {
+            // No RTCP.
+
+            mRTPPort = serverRtp;
+            mRTPSessionID = rtpSession;
+            mRTCPSessionID = 0;
+
+            ALOGI("rtpSessionId = %d", rtpSession);
+            break;
+        }
+
+        int32_t rtcpSession;
+        err = mNetSession->createUDPSession(
+                serverRtp + 1, clientIP, clientRtcp,
+                rtcpNotify, &rtcpSession);
+
+        if (err == OK) {
+            mRTPPort = serverRtp;
+            mRTPSessionID = rtpSession;
+            mRTCPSessionID = rtcpSession;
+
+            ALOGI("rtpSessionID = %d, rtcpSessionID = %d", rtpSession, rtcpSession);
+            break;
+        }
+
+        ALOGI("failed to create RTCP socket on port %d", serverRtp + 1);
+        mNetSession->destroySession(rtpSession);
+    }
+
+    if (mRTPPort == 0) {
+        return UNKNOWN_ERROR;
+    }
+
+    updateLiveness();
+
+    return OK;
+}
+
+WifiDisplaySource::PlaybackSession::~PlaybackSession() {
+    mTracks.clear();
+
+    if (mCodecLooper != NULL) {
+        mCodecLooper->stop();
+        mCodecLooper.clear();
+    }
+
+    mPacketizer.clear();
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binder = sm->getService(String16("SurfaceFlinger"));
+    sp<ISurfaceComposer> service = interface_cast<ISurfaceComposer>(binder);
+    CHECK(service != NULL);
+
+    service->connectDisplay(NULL);
+
+    if (mSerializer != NULL) {
+        mSerializer->stop();
+
+        looper()->unregisterHandler(mSerializer->id());
+        mSerializer.clear();
+    }
+
+    if (mSerializerLooper != NULL) {
+        mSerializerLooper->stop();
+        mSerializerLooper.clear();
+    }
+
+    if (mRTCPSessionID != 0) {
+        mNetSession->destroySession(mRTCPSessionID);
+    }
+
+    if (mRTPSessionID != 0) {
+        mNetSession->destroySession(mRTPSessionID);
+    }
+}
+
+int32_t WifiDisplaySource::PlaybackSession::getRTPPort() const {
+    return mRTPPort;
+}
+
+int64_t WifiDisplaySource::PlaybackSession::getLastLifesignUs() const {
+    return mLastLifesignUs;
+}
+
+void WifiDisplaySource::PlaybackSession::updateLiveness() {
+    mLastLifesignUs = ALooper::GetNowUs();
+}
+
+status_t WifiDisplaySource::PlaybackSession::play() {
+    updateLiveness();
+
+    if (mRTCPSessionID != 0) {
+        scheduleSendSR();
+    }
+
+    return mSerializer->start();
+}
+
+status_t WifiDisplaySource::PlaybackSession::pause() {
+    updateLiveness();
+
+    return OK;
+}
+
+void WifiDisplaySource::PlaybackSession::onMessageReceived(
+        const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatRTPNotify:
+        case kWhatRTCPNotify:
+        {
+            int32_t reason;
+            CHECK(msg->findInt32("reason", &reason));
+
+            switch (reason) {
+                case ANetworkSession::kWhatError:
+                {
+                    int32_t sessionID;
+                    CHECK(msg->findInt32("sessionID", &sessionID));
+
+                    int32_t err;
+                    CHECK(msg->findInt32("err", &err));
+
+                    int32_t errorOccuredDuringSend;
+                    CHECK(msg->findInt32("send", &errorOccuredDuringSend));
+
+                    AString detail;
+                    CHECK(msg->findString("detail", &detail));
+
+                    if (msg->what() == kWhatRTPNotify
+                            && !errorOccuredDuringSend) {
+                        // This is ok, we don't expect to receive anything on
+                        // the RTP socket.
+                        break;
+                    }
+
+                    ALOGE("An error occurred during %s in session %d "
+                          "(%d, '%s' (%s)).",
+                          errorOccuredDuringSend ? "send" : "receive",
+                          sessionID,
+                          err,
+                          detail.c_str(),
+                          strerror(-err));
+
+                    mNetSession->destroySession(sessionID);
+
+                    if (sessionID == mRTPSessionID) {
+                        mRTPSessionID = 0;
+                    } else if (sessionID == mRTCPSessionID) {
+                        mRTCPSessionID = 0;
+                    }
+
+                    // Inform WifiDisplaySource of our premature death (wish).
+                    sp<AMessage> notify = mNotify->dup();
+                    notify->setInt32("what", kWhatSessionDead);
+                    notify->post();
+                    break;
+                }
+
+                case ANetworkSession::kWhatDatagram:
+                {
+                    int32_t sessionID;
+                    CHECK(msg->findInt32("sessionID", &sessionID));
+
+                    sp<ABuffer> data;
+                    CHECK(msg->findBuffer("data", &data));
+
+                    status_t err;
+                    if (msg->what() == kWhatRTCPNotify) {
+                        err = parseRTCP(data);
+                    }
+                    break;
+                }
+
+                default:
+                    TRESPASS();
+            }
+            break;
+        }
+
+        case kWhatSendSR:
+        {
+            mSendSRPending = false;
+
+            if (mRTCPSessionID == 0) {
+                break;
+            }
+
+            onSendSR();
+
+            scheduleSendSR();
+            break;
+        }
+
+        case kWhatSerializerNotify:
+        {
+            int32_t what;
+            CHECK(msg->findInt32("what", &what));
+
+            if (what == Serializer::kWhatEOS) {
+                ALOGI("input eos");
+
+                for (size_t i = 0; i < mTracks.size(); ++i) {
+#if FAKE_VIDEO
+                    sp<AMessage> msg = new AMessage(kWhatConverterNotify, id());
+                    msg->setInt32("what", Converter::kWhatEOS);
+                    msg->setSize("trackIndex", i);
+                    msg->post();
+#else
+                    mTracks.valueAt(i)->converter()->signalEOS();
+#endif
+                }
+            } else {
+                CHECK_EQ(what, Serializer::kWhatAccessUnit);
+
+                size_t trackIndex;
+                CHECK(msg->findSize("trackIndex", &trackIndex));
+
+                sp<ABuffer> accessUnit;
+                CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+#if FAKE_VIDEO
+                int64_t timeUs;
+                CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+                void *mbuf;
+                CHECK(accessUnit->meta()->findPointer("mediaBuffer", &mbuf));
+
+                ((MediaBuffer *)mbuf)->release();
+                mbuf = NULL;
+
+                sp<AMessage> msg = new AMessage(kWhatConverterNotify, id());
+                msg->setInt32("what", Converter::kWhatAccessUnit);
+                msg->setSize("trackIndex", trackIndex);
+                msg->setBuffer("accessUnit", accessUnit);
+                msg->post();
+#else
+                mTracks.valueFor(trackIndex)->converter()
+                    ->feedAccessUnit(accessUnit);
+#endif
+            }
+            break;
+        }
+
+        case kWhatConverterNotify:
+        {
+            int32_t what;
+            CHECK(msg->findInt32("what", &what));
+
+            size_t trackIndex;
+            CHECK(msg->findSize("trackIndex", &trackIndex));
+
+            if (what == Converter::kWhatAccessUnit) {
+                const sp<Track> &track = mTracks.valueFor(trackIndex);
+
+                uint32_t flags = 0;
+
+                ssize_t packetizerTrackIndex = track->packetizerTrackIndex();
+                if (packetizerTrackIndex < 0) {
+                    flags = TSPacketizer::EMIT_PAT_AND_PMT;
+
+                    packetizerTrackIndex =
+                        mPacketizer->addTrack(track->getFormat());
+
+                    if (packetizerTrackIndex >= 0) {
+                        track->setPacketizerTrackIndex(packetizerTrackIndex);
+                    }
+                }
+
+                if (packetizerTrackIndex >= 0) {
+                    sp<ABuffer> accessUnit;
+                    CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+                    int64_t timeUs;
+                    CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+                    if (mPrevTimeUs < 0ll || mPrevTimeUs + 100000ll >= timeUs) {
+                        flags |= TSPacketizer::EMIT_PCR;
+                        mPrevTimeUs = timeUs;
+                    }
+
+                    sp<ABuffer> packets;
+                    mPacketizer->packetize(
+                            packetizerTrackIndex, accessUnit, &packets, flags);
+
+                    for (size_t offset = 0;
+                            offset < packets->size(); offset += 188) {
+                        bool lastTSPacket = (offset + 188 >= packets->size());
+
+                        appendTSData(
+                                packets->data() + offset,
+                                188,
+                                true /* timeDiscontinuity */,
+                                lastTSPacket /* flush */);
+                    }
+                }
+            } else if (what == Converter::kWhatEOS) {
+                CHECK_EQ(what, Converter::kWhatEOS);
+
+                ALOGI("output EOS on track %d", trackIndex);
+
+                ssize_t index = mTracks.indexOfKey(trackIndex);
+                CHECK_GE(index, 0);
+
+#if !FAKE_VIDEO
+                const sp<Converter> &converter =
+                    mTracks.valueAt(index)->converter();
+                looper()->unregisterHandler(converter->id());
+#endif
+
+                mTracks.removeItemsAt(index);
+
+                if (mTracks.isEmpty()) {
+                    ALOGI("Reached EOS");
+                }
+            } else {
+                CHECK_EQ(what, Converter::kWhatError);
+
+                status_t err;
+                CHECK(msg->findInt32("err", &err));
+
+                ALOGE("converter signaled error %d", err);
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+status_t WifiDisplaySource::PlaybackSession::setupPacketizer() {
+    sp<AMessage> msg = new AMessage(kWhatSerializerNotify, id());
+
+    mSerializerLooper = new ALooper;
+    mSerializerLooper->start();
+
+    mSerializer = new Serializer(
+#if FAKE_VIDEO
+            true /* throttled */
+#else
+            false /* throttled */
+#endif
+            , msg);
+    mSerializerLooper->registerHandler(mSerializer);
+
+    mPacketizer = new TSPacketizer;
+
+#if FAKE_VIDEO
+    DataSource::RegisterDefaultSniffers();
+
+    sp<DataSource> dataSource =
+        DataSource::CreateFromURI(
+                "/system/etc/inception_1500.mp4");
+
+    CHECK(dataSource != NULL);
+
+    sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+    CHECK(extractor != NULL);
+
+    bool haveAudio = false;
+    bool haveVideo = false;
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+        const char *mime;
+        CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+        bool useTrack = false;
+        if (!strncasecmp(mime, "audio/", 6) && !haveAudio) {
+            useTrack = true;
+            haveAudio = true;
+        } else if (!strncasecmp(mime, "video/", 6) && !haveVideo) {
+            useTrack = true;
+            haveVideo = true;
+        }
+
+        if (!useTrack) {
+            continue;
+        }
+
+        sp<MediaSource> source = extractor->getTrack(i);
+
+        ssize_t index = mSerializer->addSource(source);
+        CHECK_GE(index, 0);
+
+        sp<AMessage> format;
+        status_t err = convertMetaDataToMessage(source->getFormat(), &format);
+        CHECK_EQ(err, (status_t)OK);
+
+        mTracks.add(index, new Track(format));
+    }
+    CHECK(haveAudio || haveVideo);
+#else
+    mCodecLooper = new ALooper;
+    mCodecLooper->start();
+
+    DisplayInfo info;
+    SurfaceComposerClient::getDisplayInfo(0, &info);
+
+    // sp<SurfaceMediaSource> source = new SurfaceMediaSource(info.w, info.h);
+    sp<SurfaceMediaSource> source = new SurfaceMediaSource(720, 1280);
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binder = sm->getService(String16("SurfaceFlinger"));
+    sp<ISurfaceComposer> service = interface_cast<ISurfaceComposer>(binder);
+    CHECK(service != NULL);
+
+    service->connectDisplay(source->getBufferQueue());
+
+#if 0
+    {
+        ALOGI("reading buffer");
+
+        CHECK_EQ((status_t)OK, source->start());
+        MediaBuffer *mbuf;
+        CHECK_EQ((status_t)OK, source->read(&mbuf));
+        mbuf->release();
+        mbuf = NULL;
+
+        ALOGI("got buffer");
+    }
+#endif
+
+#if 0
+    ssize_t index = mSerializer->addSource(source);
+#else
+    ssize_t index = mSerializer->addSource(
+            new RepeaterSource(source, 30.0 /* rateHz */));
+#endif
+
+    CHECK_GE(index, 0);
+
+    sp<AMessage> format;
+    status_t err = convertMetaDataToMessage(source->getFormat(), &format);
+    CHECK_EQ(err, (status_t)OK);
+
+    format->setInt32("store-metadata-in-buffers", true);
+
+    format->setInt32(
+            "color-format", OMX_COLOR_FormatAndroidOpaque);
+
+    sp<AMessage> notify = new AMessage(kWhatConverterNotify, id());
+    notify->setSize("trackIndex", index);
+
+    sp<Converter> converter =
+        new Converter(notify, mCodecLooper, format);
+
+    looper()->registerHandler(converter);
+
+    mTracks.add(index, new Track(converter));
+#endif
+
+    return OK;
+}
+
+void WifiDisplaySource::PlaybackSession::scheduleSendSR() {
+    if (mSendSRPending) {
+        return;
+    }
+
+    mSendSRPending = true;
+    (new AMessage(kWhatSendSR, id()))->post(kSendSRIntervalUs);
+}
+
+void WifiDisplaySource::PlaybackSession::addSR(const sp<ABuffer> &buffer) {
+    uint8_t *data = buffer->data() + buffer->size();
+
+    // TODO: Use macros/utility functions to clean up all the bitshifts below.
+
+    data[0] = 0x80 | 0;
+    data[1] = 200;  // SR
+    data[2] = 0;
+    data[3] = 6;
+    data[4] = kSourceID >> 24;
+    data[5] = (kSourceID >> 16) & 0xff;
+    data[6] = (kSourceID >> 8) & 0xff;
+    data[7] = kSourceID & 0xff;
+
+    data[8] = mLastNTPTime >> (64 - 8);
+    data[9] = (mLastNTPTime >> (64 - 16)) & 0xff;
+    data[10] = (mLastNTPTime >> (64 - 24)) & 0xff;
+    data[11] = (mLastNTPTime >> 32) & 0xff;
+    data[12] = (mLastNTPTime >> 24) & 0xff;
+    data[13] = (mLastNTPTime >> 16) & 0xff;
+    data[14] = (mLastNTPTime >> 8) & 0xff;
+    data[15] = mLastNTPTime & 0xff;
+
+    data[16] = (mLastRTPTime >> 24) & 0xff;
+    data[17] = (mLastRTPTime >> 16) & 0xff;
+    data[18] = (mLastRTPTime >> 8) & 0xff;
+    data[19] = mLastRTPTime & 0xff;
+
+    data[20] = mNumRTPSent >> 24;
+    data[21] = (mNumRTPSent >> 16) & 0xff;
+    data[22] = (mNumRTPSent >> 8) & 0xff;
+    data[23] = mNumRTPSent & 0xff;
+
+    data[24] = mNumRTPOctetsSent >> 24;
+    data[25] = (mNumRTPOctetsSent >> 16) & 0xff;
+    data[26] = (mNumRTPOctetsSent >> 8) & 0xff;
+    data[27] = mNumRTPOctetsSent & 0xff;
+
+    buffer->setRange(buffer->offset(), buffer->size() + 28);
+}
+
+void WifiDisplaySource::PlaybackSession::addSDES(const sp<ABuffer> &buffer) {
+    uint8_t *data = buffer->data() + buffer->size();
+    data[0] = 0x80 | 1;
+    data[1] = 202;  // SDES
+    data[4] = kSourceID >> 24;
+    data[5] = (kSourceID >> 16) & 0xff;
+    data[6] = (kSourceID >> 8) & 0xff;
+    data[7] = kSourceID & 0xff;
+
+    size_t offset = 8;
+
+    data[offset++] = 1;  // CNAME
+
+    static const char *kCNAME = "someone@somewhere";
+    data[offset++] = strlen(kCNAME);
+
+    memcpy(&data[offset], kCNAME, strlen(kCNAME));
+    offset += strlen(kCNAME);
+
+    data[offset++] = 7;  // NOTE
+
+    static const char *kNOTE = "Hell's frozen over.";
+    data[offset++] = strlen(kNOTE);
+
+    memcpy(&data[offset], kNOTE, strlen(kNOTE));
+    offset += strlen(kNOTE);
+
+    data[offset++] = 0;
+
+    if ((offset % 4) > 0) {
+        size_t count = 4 - (offset % 4);
+        switch (count) {
+            case 3:
+                data[offset++] = 0;
+            case 2:
+                data[offset++] = 0;
+            case 1:
+                data[offset++] = 0;
+        }
+    }
+
+    size_t numWords = (offset / 4) - 1;
+    data[2] = numWords >> 8;
+    data[3] = numWords & 0xff;
+
+    buffer->setRange(buffer->offset(), buffer->size() + offset);
+}
+
+// static
+uint64_t WifiDisplaySource::PlaybackSession::GetNowNTP() {
+    uint64_t nowUs = ALooper::GetNowUs();
+
+    nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+    uint64_t hi = nowUs / 1000000ll;
+    uint64_t lo = ((1ll << 32) * (nowUs % 1000000ll)) / 1000000ll;
+
+    return (hi << 32) | lo;
+}
+
+void WifiDisplaySource::PlaybackSession::onSendSR() {
+    sp<ABuffer> buffer = new ABuffer(1500);
+    buffer->setRange(0, 0);
+
+    addSR(buffer);
+    addSDES(buffer);
+
+    if (mUseInterleavedTCP) {
+        sp<AMessage> notify = mNotify->dup();
+        notify->setInt32("what", kWhatBinaryData);
+        notify->setInt32("channel", mRTCPChannel);
+        notify->setBuffer("data", buffer);
+        notify->post();
+    } else {
+        mNetSession->sendRequest(
+                mRTCPSessionID, buffer->data(), buffer->size());
+    }
+
+    ++mNumSRsSent;
+}
+
+ssize_t WifiDisplaySource::PlaybackSession::appendTSData(
+        const void *data, size_t size, bool timeDiscontinuity, bool flush) {
+    CHECK_EQ(size, 188);
+
+    CHECK_LE(mTSQueue->size() + size, mTSQueue->capacity());
+
+    memcpy(mTSQueue->data() + mTSQueue->size(), data, size);
+    mTSQueue->setRange(0, mTSQueue->size() + size);
+
+    if (flush || mTSQueue->size() == mTSQueue->capacity()) {
+        // flush
+
+        int64_t nowUs = ALooper::GetNowUs();
+        if (mFirstPacketTimeUs < 0ll) {
+            mFirstPacketTimeUs = nowUs;
+        }
+
+        // 90kHz time scale
+        uint32_t rtpTime = ((nowUs - mFirstPacketTimeUs) * 9ll) / 100ll;
+
+        uint8_t *rtp = mTSQueue->data();
+        rtp[0] = 0x80;
+        rtp[1] = 33 | (timeDiscontinuity ? (1 << 7) : 0);  // M-bit
+        rtp[2] = (mRTPSeqNo >> 8) & 0xff;
+        rtp[3] = mRTPSeqNo & 0xff;
+        rtp[4] = rtpTime >> 24;
+        rtp[5] = (rtpTime >> 16) & 0xff;
+        rtp[6] = (rtpTime >> 8) & 0xff;
+        rtp[7] = rtpTime & 0xff;
+        rtp[8] = kSourceID >> 24;
+        rtp[9] = (kSourceID >> 16) & 0xff;
+        rtp[10] = (kSourceID >> 8) & 0xff;
+        rtp[11] = kSourceID & 0xff;
+
+        ++mRTPSeqNo;
+        ++mNumRTPSent;
+        mNumRTPOctetsSent += mTSQueue->size() - 12;
+
+        mLastRTPTime = rtpTime;
+        mLastNTPTime = GetNowNTP();
+
+        if (mUseInterleavedTCP) {
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatBinaryData);
+
+            sp<ABuffer> data = new ABuffer(mTSQueue->size());
+            memcpy(data->data(), rtp, mTSQueue->size());
+
+            notify->setInt32("channel", mRTPChannel);
+            notify->setBuffer("data", data);
+            notify->post();
+        } else {
+            mNetSession->sendRequest(
+                    mRTPSessionID, rtp, mTSQueue->size());
+        }
+
+        mTSQueue->setInt32Data(mRTPSeqNo - 1);
+        mHistory.push_back(mTSQueue);
+        ++mHistoryLength;
+
+        if (mHistoryLength > kMaxHistoryLength) {
+            mTSQueue = *mHistory.begin();
+            mHistory.erase(mHistory.begin());
+
+            --mHistoryLength;
+        } else {
+            mTSQueue = new ABuffer(12 + kMaxNumTSPacketsPerRTPPacket * 188);
+        }
+
+        mTSQueue->setRange(0, 12);
+    }
+
+    return size;
+}
+
+status_t WifiDisplaySource::PlaybackSession::parseRTCP(
+        const sp<ABuffer> &buffer) {
+    const uint8_t *data = buffer->data();
+    size_t size = buffer->size();
+
+    while (size > 0) {
+        if (size < 8) {
+            // Too short to be a valid RTCP header
+            return ERROR_MALFORMED;
+        }
+
+        if ((data[0] >> 6) != 2) {
+            // Unsupported version.
+            return ERROR_UNSUPPORTED;
+        }
+
+        if (data[0] & 0x20) {
+            // Padding present.
+
+            size_t paddingLength = data[size - 1];
+
+            if (paddingLength + 12 > size) {
+                // If we removed this much padding we'd end up with something
+                // that's too short to be a valid RTP header.
+                return ERROR_MALFORMED;
+            }
+
+            size -= paddingLength;
+        }
+
+        size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
+
+        if (size < headerLength) {
+            // Only received a partial packet?
+            return ERROR_MALFORMED;
+        }
+
+        switch (data[1]) {
+            case 200:
+            case 201:  // RR
+            case 202:  // SDES
+            case 203:
+            case 204:  // APP
+                break;
+
+            case 205:  // TSFB (transport layer specific feedback)
+                parseTSFB(data, headerLength);
+                break;
+
+            case 206:  // PSFB (payload specific feedback)
+                hexdump(data, headerLength);
+                break;
+
+            default:
+            {
+                ALOGW("Unknown RTCP packet type %u of size %d",
+                     (unsigned)data[1], headerLength);
+                break;
+            }
+        }
+
+        data += headerLength;
+        size -= headerLength;
+    }
+
+    return OK;
+}
+
+status_t WifiDisplaySource::PlaybackSession::parseTSFB(
+        const uint8_t *data, size_t size) {
+    if ((data[0] & 0x1f) != 1) {
+        return ERROR_UNSUPPORTED;  // We only support NACK for now.
+    }
+
+    uint32_t srcId = U32_AT(&data[8]);
+    if (srcId != kSourceID) {
+        return ERROR_MALFORMED;
+    }
+
+    for (size_t i = 12; i < size; i += 4) {
+        uint16_t seqNo = U16_AT(&data[i]);
+        uint16_t blp = U16_AT(&data[i + 2]);
+
+        List<sp<ABuffer> >::iterator it = mHistory.begin();
+        bool found = false;
+        while (it != mHistory.end()) {
+            const sp<ABuffer> &buffer = *it;
+
+            uint16_t bufferSeqNo = buffer->int32Data() & 0xffff;
+
+            if (bufferSeqNo == seqNo) {
+                mNetSession->sendRequest(
+                        mRTPSessionID, buffer->data(), buffer->size());
+
+                found = true;
+                break;
+            }
+
+            ++it;
+        }
+
+        if (found) {
+            ALOGI("retransmitting seqNo %d", seqNo);
+        } else {
+            ALOGI("seqNo %d no longer available", seqNo);
+        }
+    }
+
+    return OK;
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h
new file mode 100644
index 0000000..d256fc4
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 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 PLAYBACK_SESSION_H_
+
+#define PLAYBACK_SESSION_H_
+
+#include "WifiDisplaySource.h"
+
+namespace android {
+
+struct ABuffer;
+struct Serializer;
+struct TSPacketizer;
+
+// Encapsulates the state of an RTP/RTCP session in the context of wifi
+// display.
+struct WifiDisplaySource::PlaybackSession : public AHandler {
+    PlaybackSession(
+            const sp<ANetworkSession> &netSession, const sp<AMessage> &notify);
+
+    status_t init(
+            const char *clientIP, int32_t clientRtp, int32_t clientRtcp,
+            bool useInterleavedTCP);
+
+    int32_t getRTPPort() const;
+
+    int64_t getLastLifesignUs() const;
+    void updateLiveness();
+
+    status_t play();
+    status_t pause();
+
+    enum {
+        kWhatSessionDead,
+        kWhatBinaryData,
+    };
+
+protected:
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+    virtual ~PlaybackSession();
+
+private:
+    struct Track;
+
+    enum {
+        kWhatSendSR,
+        kWhatRTPNotify,
+        kWhatRTCPNotify,
+        kWhatSerializerNotify,
+        kWhatConverterNotify,
+        kWhatUpdateSurface,
+    };
+
+    static const int64_t kSendSRIntervalUs = 10000000ll;
+    static const uint32_t kSourceID = 0xdeadbeef;
+    static const size_t kMaxHistoryLength = 128;
+
+    sp<ANetworkSession> mNetSession;
+    sp<AMessage> mNotify;
+
+    int64_t mLastLifesignUs;
+
+    sp<ALooper> mSerializerLooper;
+    sp<Serializer> mSerializer;
+    sp<TSPacketizer> mPacketizer;
+    sp<ALooper> mCodecLooper;
+
+    KeyedVector<size_t, sp<Track> > mTracks;
+
+    sp<ABuffer> mTSQueue;
+    int64_t mPrevTimeUs;
+
+    bool mUseInterleavedTCP;
+
+    // in TCP mode
+    int32_t mRTPChannel;
+    int32_t mRTCPChannel;
+
+    // in UDP mode
+    int32_t mRTPPort;
+    int32_t mRTPSessionID;
+    int32_t mRTCPSessionID;
+
+
+    uint32_t mRTPSeqNo;
+
+    uint64_t mLastNTPTime;
+    uint32_t mLastRTPTime;
+    uint32_t mNumRTPSent;
+    uint32_t mNumRTPOctetsSent;
+    uint32_t mNumSRsSent;
+
+    bool mSendSRPending;
+
+    int64_t mFirstPacketTimeUs;
+
+    List<sp<ABuffer> > mHistory;
+    size_t mHistoryLength;
+
+    void onSendSR();
+    void addSR(const sp<ABuffer> &buffer);
+    void addSDES(const sp<ABuffer> &buffer);
+    static uint64_t GetNowNTP();
+
+    status_t setupPacketizer();
+
+    ssize_t appendTSData(
+            const void *data, size_t size, bool timeDiscontinuity, bool flush);
+
+    void scheduleSendSR();
+
+    status_t parseRTCP(const sp<ABuffer> &buffer);
+    status_t parseTSFB(const uint8_t *data, size_t size);
+
+    DISALLOW_EVIL_CONSTRUCTORS(PlaybackSession);
+};
+
+}  // namespace android
+
+#endif  // PLAYBACK_SESSION_H_
+
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.cpp b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
new file mode 100644
index 0000000..8af4fdf
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
@@ -0,0 +1,140 @@
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RepeaterSource"
+#include <utils/Log.h>
+
+#include "RepeaterSource.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+RepeaterSource::RepeaterSource(const sp<MediaSource> &source, double rateHz)
+    : mSource(source),
+      mRateHz(rateHz),
+      mBuffer(NULL),
+      mResult(OK),
+      mStartTimeUs(-1ll),
+      mFrameCount(0) {
+}
+
+RepeaterSource::~RepeaterSource() {
+    stop();
+}
+
+status_t RepeaterSource::start(MetaData *params) {
+    status_t err = mSource->start(params);
+
+    if (err != OK) {
+        return err;
+    }
+
+    mBuffer = NULL;
+    mResult = OK;
+    mStartTimeUs = -1ll;
+    mFrameCount = 0;
+
+    mLooper = new ALooper;
+    mLooper->start();
+
+    mReflector = new AHandlerReflector<RepeaterSource>(this);
+    mLooper->registerHandler(mReflector);
+
+    postRead();
+
+    return OK;
+}
+
+status_t RepeaterSource::stop() {
+    if (mLooper != NULL) {
+        mLooper->stop();
+        mLooper.clear();
+
+        mReflector.clear();
+    }
+
+    return mSource->stop();
+}
+
+sp<MetaData> RepeaterSource::getFormat() {
+    return mSource->getFormat();
+}
+
+status_t RepeaterSource::read(
+        MediaBuffer **buffer, const ReadOptions *options) {
+    int64_t seekTimeUs;
+    ReadOptions::SeekMode seekMode;
+    CHECK(options == NULL || !options->getSeekTo(&seekTimeUs, &seekMode));
+
+    int64_t bufferTimeUs = -1ll;
+
+    if (mStartTimeUs < 0ll) {
+        Mutex::Autolock autoLock(mLock);
+        while (mBuffer == NULL && mResult == OK) {
+            mCondition.wait(mLock);
+        }
+
+        mStartTimeUs = ALooper::GetNowUs();
+        bufferTimeUs = mStartTimeUs;
+    } else {
+        bufferTimeUs = mStartTimeUs + (mFrameCount * 1000000ll) / mRateHz;
+
+        int64_t nowUs = ALooper::GetNowUs();
+        int64_t delayUs = bufferTimeUs - nowUs;
+
+        if (delayUs > 0ll) {
+            usleep(delayUs);
+        }
+    }
+
+    Mutex::Autolock autoLock(mLock);
+    if (mResult != OK) {
+        CHECK(mBuffer == NULL);
+        return mResult;
+    }
+
+    mBuffer->add_ref();
+    *buffer = mBuffer;
+    (*buffer)->meta_data()->setInt64(kKeyTime, bufferTimeUs);
+
+    ++mFrameCount;
+
+    return OK;
+}
+
+void RepeaterSource::postRead() {
+    (new AMessage(kWhatRead, mReflector->id()))->post();
+}
+
+void RepeaterSource::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatRead:
+        {
+            MediaBuffer *buffer;
+            status_t err = mSource->read(&buffer);
+
+            Mutex::Autolock autoLock(mLock);
+            if (mBuffer != NULL) {
+                mBuffer->release();
+                mBuffer = NULL;
+            }
+            mBuffer = buffer;
+            mResult = err;
+
+            mCondition.broadcast();
+
+            if (err == OK) {
+                postRead();
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+}  // namespace android
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.h b/media/libstagefright/wifi-display/source/RepeaterSource.h
new file mode 100644
index 0000000..31eb5cd
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.h
@@ -0,0 +1,55 @@
+#ifndef REPEATER_SOURCE_H_
+
+#define REPEATER_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/MediaSource.h>
+
+namespace android {
+
+// This MediaSource delivers frames at a constant rate by repeating buffers
+// if necessary.
+struct RepeaterSource : public MediaSource {
+    RepeaterSource(const sp<MediaSource> &source, double rateHz);
+
+    virtual status_t start(MetaData *params);
+    virtual status_t stop();
+    virtual sp<MetaData> getFormat();
+
+    virtual status_t read(
+            MediaBuffer **buffer, const ReadOptions *options);
+
+    void onMessageReceived(const sp<AMessage> &msg);
+
+protected:
+    virtual ~RepeaterSource();
+
+private:
+    enum {
+        kWhatRead,
+    };
+
+    Mutex mLock;
+    Condition mCondition;
+
+    sp<MediaSource> mSource;
+    double mRateHz;
+
+    sp<ALooper> mLooper;
+    sp<AHandlerReflector<RepeaterSource> > mReflector;
+
+    MediaBuffer *mBuffer;
+    status_t mResult;
+
+    int64_t mStartTimeUs;
+    int32_t mFrameCount;
+
+    void postRead();
+
+    DISALLOW_EVIL_CONSTRUCTORS(RepeaterSource);
+};
+
+}  // namespace android
+
+#endif // REPEATER_SOURCE_H_
diff --git a/media/libstagefright/wifi-display/source/Serializer.cpp b/media/libstagefright/wifi-display/source/Serializer.cpp
new file mode 100644
index 0000000..bd53fc8
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Serializer.cpp
@@ -0,0 +1,366 @@
+/*
+ * Copyright 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 "Serializer"
+#include <utils/Log.h>
+
+#include "Serializer.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+struct Serializer::Track : public RefBase {
+    Track(const sp<MediaSource> &source);
+
+    status_t start();
+    status_t stop();
+
+    void readBufferIfNecessary();
+
+    bool reachedEOS() const;
+    int64_t bufferTimeUs() const;
+
+    sp<ABuffer> drainBuffer();
+
+protected:
+    virtual ~Track();
+
+private:
+    sp<MediaSource> mSource;
+
+    bool mStarted;
+    status_t mFinalResult;
+    MediaBuffer *mBuffer;
+    int64_t mBufferTimeUs;
+
+    DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+Serializer::Track::Track(const sp<MediaSource> &source)
+    : mSource(source),
+      mStarted(false),
+      mFinalResult(OK),
+      mBuffer(NULL),
+      mBufferTimeUs(-1ll) {
+}
+
+Serializer::Track::~Track() {
+    stop();
+}
+
+status_t Serializer::Track::start() {
+    if (mStarted) {
+        return OK;
+    }
+
+    status_t err = mSource->start();
+
+    if (err == OK) {
+        mStarted = true;
+    }
+
+    return err;
+}
+
+status_t Serializer::Track::stop() {
+    if (!mStarted) {
+        return OK;
+    }
+
+    if (mBuffer != NULL) {
+        mBuffer->release();
+        mBuffer = NULL;
+
+        mBufferTimeUs = -1ll;
+    }
+
+    status_t err = mSource->stop();
+
+    mStarted = false;
+
+    return err;
+}
+
+void Serializer::Track::readBufferIfNecessary() {
+    if (mBuffer != NULL) {
+        return;
+    }
+
+    mFinalResult = mSource->read(&mBuffer);
+
+    if (mFinalResult != OK) {
+        ALOGI("read failed w/ err %d", mFinalResult);
+        return;
+    }
+
+    CHECK(mBuffer->meta_data()->findInt64(kKeyTime, &mBufferTimeUs));
+}
+
+bool Serializer::Track::reachedEOS() const {
+    return mFinalResult != OK;
+}
+
+int64_t Serializer::Track::bufferTimeUs() const {
+    return mBufferTimeUs;
+}
+
+sp<ABuffer> Serializer::Track::drainBuffer() {
+    sp<ABuffer> accessUnit = new ABuffer(mBuffer->range_length());
+
+    memcpy(accessUnit->data(),
+           (const uint8_t *)mBuffer->data() + mBuffer->range_offset(),
+           mBuffer->range_length());
+
+    accessUnit->meta()->setInt64("timeUs", mBufferTimeUs);
+    accessUnit->meta()->setPointer("mediaBuffer", mBuffer);
+
+    mBuffer = NULL;
+    mBufferTimeUs = -1ll;
+
+    return accessUnit;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Serializer::Serializer(bool throttle, const sp<AMessage> &notify)
+    : mThrottle(throttle),
+      mNotify(notify),
+      mPollGeneration(0),
+      mStartTimeUs(-1ll) {
+}
+
+Serializer::~Serializer() {
+}
+
+status_t Serializer::postSynchronouslyAndReturnError(
+        const sp<AMessage> &msg) {
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (!response->findInt32("err", &err)) {
+        err = OK;
+    }
+
+    return err;
+}
+
+ssize_t Serializer::addSource(const sp<MediaSource> &source) {
+    sp<AMessage> msg = new AMessage(kWhatAddSource, id());
+    msg->setPointer("source", source.get());
+
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (!response->findInt32("err", &err)) {
+        size_t index;
+        CHECK(response->findSize("index", &index));
+
+        return index;
+    }
+
+    return err;
+}
+
+status_t Serializer::start() {
+    return postSynchronouslyAndReturnError(new AMessage(kWhatStart, id()));
+}
+
+status_t Serializer::stop() {
+    return postSynchronouslyAndReturnError(new AMessage(kWhatStop, id()));
+}
+
+void Serializer::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatAddSource:
+        {
+            ssize_t index = onAddSource(msg);
+
+            sp<AMessage> response = new AMessage;
+
+            if (index < 0) {
+                response->setInt32("err", index);
+            } else {
+                response->setSize("index", index);
+            }
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatStart:
+        case kWhatStop:
+        {
+            status_t err = (msg->what() == kWhatStart) ? onStart() : onStop();
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatPoll:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mPollGeneration) {
+                break;
+            }
+
+            int64_t delayUs = onPoll();
+            if (delayUs >= 0ll) {
+                schedulePoll(delayUs);
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+ssize_t Serializer::onAddSource(const sp<AMessage> &msg) {
+    void *obj;
+    CHECK(msg->findPointer("source", &obj));
+
+    sp<MediaSource> source = static_cast<MediaSource *>(obj);
+
+    sp<Track> track = new Track(source);
+    return mTracks.add(track);
+}
+
+status_t Serializer::onStart() {
+    status_t err = OK;
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        err = mTracks.itemAt(i)->start();
+
+        if (err != OK) {
+            break;
+        }
+    }
+
+    if (err == OK) {
+#if 0
+        schedulePoll();
+#else
+        // XXX the dongle doesn't appear to have setup the RTP connection
+        // fully at the time PLAY is called. We have to delay sending data
+        // for a little bit.
+        schedulePoll(500000ll);
+#endif
+    }
+
+    return err;
+}
+
+status_t Serializer::onStop() {
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        mTracks.itemAt(i)->stop();
+    }
+
+    cancelPoll();
+
+    return OK;
+}
+
+int64_t Serializer::onPoll() {
+    int64_t minTimeUs = -1ll;
+    ssize_t minTrackIndex = -1;
+
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        const sp<Track> &track = mTracks.itemAt(i);
+
+        track->readBufferIfNecessary();
+
+        if (!track->reachedEOS()) {
+            int64_t timeUs = track->bufferTimeUs();
+
+            if (minTrackIndex < 0 || timeUs < minTimeUs) {
+                minTimeUs = timeUs;
+                minTrackIndex = i;
+            }
+        }
+    }
+
+    if (minTrackIndex < 0) {
+        sp<AMessage> notify = mNotify->dup();
+        notify->setInt32("what", kWhatEOS);
+        notify->post();
+
+        return -1ll;
+    }
+
+    if (mThrottle) {
+        int64_t nowUs = ALooper::GetNowUs();
+
+        if (mStartTimeUs < 0ll) {
+            mStartTimeUs = nowUs;
+        }
+
+        int64_t lateByUs = nowUs - (minTimeUs + mStartTimeUs);
+
+        if (lateByUs < 0ll) {
+            // Too early
+            return -lateByUs;
+        }
+    }
+
+    sp<AMessage> notify = mNotify->dup();
+
+    notify->setInt32("what", kWhatAccessUnit);
+    notify->setSize("trackIndex", minTrackIndex);
+
+    notify->setBuffer(
+            "accessUnit", mTracks.itemAt(minTrackIndex)->drainBuffer());
+
+    notify->post();
+
+    return 0ll;
+}
+
+void Serializer::schedulePoll(int64_t delayUs) {
+    sp<AMessage> msg = new AMessage(kWhatPoll, id());
+    msg->setInt32("generation", mPollGeneration);
+    msg->post(delayUs);
+}
+
+void Serializer::cancelPoll() {
+    ++mPollGeneration;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/wifi-display/source/Serializer.h b/media/libstagefright/wifi-display/source/Serializer.h
new file mode 100644
index 0000000..07950fa
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Serializer.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 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 SERIALIZER_H_
+
+#define SERIALIZER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct AMessage;
+struct MediaSource;
+
+// After adding a number of MediaSource objects and starting the Serializer,
+// it'll emit their access units in order of increasing timestamps.
+struct Serializer : public AHandler {
+    enum {
+        kWhatEOS,
+        kWhatAccessUnit
+    };
+
+    // In throttled operation, data is emitted at a pace corresponding
+    // to the incoming media timestamps.
+    Serializer(bool throttle, const sp<AMessage> &notify);
+
+    ssize_t addSource(const sp<MediaSource> &source);
+
+    status_t start();
+    status_t stop();
+
+protected:
+    virtual void onMessageReceived(const sp<AMessage> &what);
+    virtual ~Serializer();
+
+private:
+    enum {
+        kWhatAddSource,
+        kWhatStart,
+        kWhatStop,
+        kWhatPoll
+    };
+
+    struct Track;
+
+    bool mThrottle;
+    sp<AMessage> mNotify;
+    Vector<sp<Track> > mTracks;
+
+    int32_t mPollGeneration;
+
+    int64_t mStartTimeUs;
+
+    status_t postSynchronouslyAndReturnError(const sp<AMessage> &msg);
+
+    ssize_t onAddSource(const sp<AMessage> &msg);
+    status_t onStart();
+    status_t onStop();
+    int64_t onPoll();
+
+    void schedulePoll(int64_t delayUs = 0ll);
+    void cancelPoll();
+
+    DISALLOW_EVIL_CONSTRUCTORS(Serializer);
+};
+
+}  // namespace android
+
+#endif  // SERIALIZER_H_
+
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
new file mode 100644
index 0000000..b9a3e9b
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
@@ -0,0 +1,694 @@
+/*
+ * Copyright 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 "TSPacketizer"
+#include <utils/Log.h>
+
+#include "TSPacketizer.h"
+#include "include/avc_utils.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+
+#include <arpa/inet.h>
+
+namespace android {
+
+struct TSPacketizer::Track : public RefBase {
+    Track(const sp<AMessage> &format,
+          unsigned PID, unsigned streamType, unsigned streamID);
+
+    unsigned PID() const;
+    unsigned streamType() const;
+    unsigned streamID() const;
+
+    // Returns the previous value.
+    unsigned incrementContinuityCounter();
+
+    bool isAudio() const;
+    bool isVideo() const;
+
+    bool isH264() const;
+    bool lacksADTSHeader() const;
+
+    sp<ABuffer> prependCSD(const sp<ABuffer> &accessUnit) const;
+    sp<ABuffer> prependADTSHeader(const sp<ABuffer> &accessUnit) const;
+
+protected:
+    virtual ~Track();
+
+private:
+    sp<AMessage> mFormat;
+
+    unsigned mPID;
+    unsigned mStreamType;
+    unsigned mStreamID;
+    unsigned mContinuityCounter;
+
+    AString mMIME;
+    Vector<sp<ABuffer> > mCSD;
+
+    DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+TSPacketizer::Track::Track(
+        const sp<AMessage> &format,
+        unsigned PID, unsigned streamType, unsigned streamID)
+    : mFormat(format),
+      mPID(PID),
+      mStreamType(streamType),
+      mStreamID(streamID),
+      mContinuityCounter(0) {
+    CHECK(format->findString("mime", &mMIME));
+
+    if (!strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)
+            || !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+        for (size_t i = 0;; ++i) {
+            sp<ABuffer> csd;
+            if (!format->findBuffer(StringPrintf("csd-%d", i).c_str(), &csd)) {
+                break;
+            }
+
+            mCSD.push(csd);
+        }
+    }
+}
+
+TSPacketizer::Track::~Track() {
+}
+
+unsigned TSPacketizer::Track::PID() const {
+    return mPID;
+}
+
+unsigned TSPacketizer::Track::streamType() const {
+    return mStreamType;
+}
+
+unsigned TSPacketizer::Track::streamID() const {
+    return mStreamID;
+}
+
+unsigned TSPacketizer::Track::incrementContinuityCounter() {
+    unsigned prevCounter = mContinuityCounter;
+
+    if (++mContinuityCounter == 16) {
+        mContinuityCounter = 0;
+    }
+
+    return prevCounter;
+}
+
+bool TSPacketizer::Track::isAudio() const {
+    return !strncasecmp("audio/", mMIME.c_str(), 6);
+}
+
+bool TSPacketizer::Track::isVideo() const {
+    return !strncasecmp("video/", mMIME.c_str(), 6);
+}
+
+bool TSPacketizer::Track::isH264() const {
+    return !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_VIDEO_AVC);
+}
+
+bool TSPacketizer::Track::lacksADTSHeader() const {
+    if (strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+        return false;
+    }
+
+    int32_t isADTS;
+    if (mFormat->findInt32("is-adts", &isADTS) && isADTS != 0) {
+        return false;
+    }
+
+    return true;
+}
+
+sp<ABuffer> TSPacketizer::Track::prependCSD(
+        const sp<ABuffer> &accessUnit) const {
+    size_t size = 0;
+    for (size_t i = 0; i < mCSD.size(); ++i) {
+        size += mCSD.itemAt(i)->size();
+    }
+
+    sp<ABuffer> dup = new ABuffer(accessUnit->size() + size);
+    size_t offset = 0;
+    for (size_t i = 0; i < mCSD.size(); ++i) {
+        const sp<ABuffer> &csd = mCSD.itemAt(i);
+
+        memcpy(dup->data() + offset, csd->data(), csd->size());
+        offset += csd->size();
+    }
+
+    memcpy(dup->data() + offset, accessUnit->data(), accessUnit->size());
+
+    return dup;
+}
+
+sp<ABuffer> TSPacketizer::Track::prependADTSHeader(
+        const sp<ABuffer> &accessUnit) const {
+    CHECK_EQ(mCSD.size(), 1u);
+
+    const uint8_t *codec_specific_data = mCSD.itemAt(0)->data();
+
+    const uint32_t aac_frame_length = accessUnit->size() + 7;
+
+    sp<ABuffer> dup = new ABuffer(aac_frame_length);
+
+    unsigned profile = (codec_specific_data[0] >> 3) - 1;
+
+    unsigned sampling_freq_index =
+        ((codec_specific_data[0] & 7) << 1)
+        | (codec_specific_data[1] >> 7);
+
+    unsigned channel_configuration =
+        (codec_specific_data[1] >> 3) & 0x0f;
+
+    uint8_t *ptr = dup->data();
+
+    *ptr++ = 0xff;
+    *ptr++ = 0xf1;  // b11110001, ID=0, layer=0, protection_absent=1
+
+    *ptr++ =
+        profile << 6
+        | sampling_freq_index << 2
+        | ((channel_configuration >> 2) & 1);  // private_bit=0
+
+    // original_copy=0, home=0, copyright_id_bit=0, copyright_id_start=0
+    *ptr++ =
+        (channel_configuration & 3) << 6
+        | aac_frame_length >> 11;
+    *ptr++ = (aac_frame_length >> 3) & 0xff;
+    *ptr++ = (aac_frame_length & 7) << 5;
+
+    // adts_buffer_fullness=0, number_of_raw_data_blocks_in_frame=0
+    *ptr++ = 0;
+
+    memcpy(ptr, accessUnit->data(), accessUnit->size());
+
+    return dup;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSPacketizer::TSPacketizer()
+    : mPATContinuityCounter(0),
+      mPMTContinuityCounter(0) {
+    initCrcTable();
+}
+
+TSPacketizer::~TSPacketizer() {
+}
+
+ssize_t TSPacketizer::addTrack(const sp<AMessage> &format) {
+    AString mime;
+    CHECK(format->findString("mime", &mime));
+
+    unsigned PIDStart;
+    bool isVideo = !strncasecmp("video/", mime.c_str(), 6);
+    bool isAudio = !strncasecmp("audio/", mime.c_str(), 6);
+
+    if (isVideo) {
+        PIDStart = 0x1011;
+    } else if (isAudio) {
+        PIDStart = 0x1100;
+    } else {
+        return ERROR_UNSUPPORTED;
+    }
+
+    unsigned streamType;
+    unsigned streamIDStart;
+    unsigned streamIDStop;
+
+    if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)) {
+        streamType = 0x1b;
+        streamIDStart = 0xe0;
+        streamIDStop = 0xef;
+    } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+        streamType = 0x0f;
+        streamIDStart = 0xc0;
+        streamIDStop = 0xdf;
+    } else {
+        return ERROR_UNSUPPORTED;
+    }
+
+    size_t numTracksOfThisType = 0;
+    unsigned PID = PIDStart;
+
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        const sp<Track> &track = mTracks.itemAt(i);
+
+        if (track->streamType() == streamType) {
+            ++numTracksOfThisType;
+        }
+
+        if ((isAudio && track->isAudio()) || (isVideo && track->isVideo())) {
+            ++PID;
+        }
+    }
+
+    unsigned streamID = streamIDStart + numTracksOfThisType;
+    if (streamID > streamIDStop) {
+        return -ERANGE;
+    }
+
+    sp<Track> track = new Track(format, PID, streamType, streamID);
+    return mTracks.add(track);
+}
+
+status_t TSPacketizer::packetize(
+        size_t trackIndex,
+        const sp<ABuffer> &_accessUnit,
+        sp<ABuffer> *packets,
+        uint32_t flags) {
+    sp<ABuffer> accessUnit = _accessUnit;
+
+    packets->clear();
+
+    if (trackIndex >= mTracks.size()) {
+        return -ERANGE;
+    }
+
+    int64_t timeUs;
+    CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+    const sp<Track> &track = mTracks.itemAt(trackIndex);
+
+    if (track->isH264()) {
+        if (IsIDR(accessUnit)) {
+            // prepend codec specific data, i.e. SPS and PPS.
+            accessUnit = track->prependCSD(accessUnit);
+        }
+    } else if (track->lacksADTSHeader()) {
+        accessUnit = track->prependADTSHeader(accessUnit);
+    }
+
+    // 0x47
+    // transport_error_indicator = b0
+    // payload_unit_start_indicator = b1
+    // transport_priority = b0
+    // PID
+    // transport_scrambling_control = b00
+    // adaptation_field_control = b??
+    // continuity_counter = b????
+    // -- payload follows
+    // packet_startcode_prefix = 0x000001
+    // stream_id
+    // PES_packet_length = 0x????
+    // reserved = b10
+    // PES_scrambling_control = b00
+    // PES_priority = b0
+    // data_alignment_indicator = b1
+    // copyright = b0
+    // original_or_copy = b0
+    // PTS_DTS_flags = b10  (PTS only)
+    // ESCR_flag = b0
+    // ES_rate_flag = b0
+    // DSM_trick_mode_flag = b0
+    // additional_copy_info_flag = b0
+    // PES_CRC_flag = b0
+    // PES_extension_flag = b0
+    // PES_header_data_length = 0x05
+    // reserved = b0010 (PTS)
+    // PTS[32..30] = b???
+    // reserved = b1
+    // PTS[29..15] = b??? ???? ???? ???? (15 bits)
+    // reserved = b1
+    // PTS[14..0] = b??? ???? ???? ???? (15 bits)
+    // reserved = b1
+    // the first fragment of "buffer" follows
+
+    size_t numTSPackets;
+    if (accessUnit->size() <= 170) {
+        numTSPackets = 1;
+    } else {
+        numTSPackets = 1 + ((accessUnit->size() - 170) + 183) / 184;
+    }
+
+    if (flags & EMIT_PAT_AND_PMT) {
+        numTSPackets += 2;
+    }
+
+    if (flags & EMIT_PCR) {
+        ++numTSPackets;
+    }
+
+    sp<ABuffer> buffer = new ABuffer(numTSPackets * 188);
+    uint8_t *packetDataStart = buffer->data();
+
+    if (flags & EMIT_PAT_AND_PMT) {
+        // Program Association Table (PAT):
+        // 0x47
+        // transport_error_indicator = b0
+        // payload_unit_start_indicator = b1
+        // transport_priority = b0
+        // PID = b0000000000000 (13 bits)
+        // transport_scrambling_control = b00
+        // adaptation_field_control = b01 (no adaptation field, payload only)
+        // continuity_counter = b????
+        // skip = 0x00
+        // --- payload follows
+        // table_id = 0x00
+        // section_syntax_indicator = b1
+        // must_be_zero = b0
+        // reserved = b11
+        // section_length = 0x00d
+        // transport_stream_id = 0x0000
+        // reserved = b11
+        // version_number = b00001
+        // current_next_indicator = b1
+        // section_number = 0x00
+        // last_section_number = 0x00
+        //   one program follows:
+        //   program_number = 0x0001
+        //   reserved = b111
+        //   program_map_PID = kPID_PMT (13 bits!)
+        // CRC = 0x????????
+
+        if (++mPATContinuityCounter == 16) {
+            mPATContinuityCounter = 0;
+        }
+
+        uint8_t *ptr = packetDataStart;
+        *ptr++ = 0x47;
+        *ptr++ = 0x40;
+        *ptr++ = 0x00;
+        *ptr++ = 0x10 | mPATContinuityCounter;
+        *ptr++ = 0x00;
+
+        const uint8_t *crcDataStart = ptr;
+        *ptr++ = 0x00;
+        *ptr++ = 0xb0;
+        *ptr++ = 0x0d;
+        *ptr++ = 0x00;
+        *ptr++ = 0x00;
+        *ptr++ = 0xc3;
+        *ptr++ = 0x00;
+        *ptr++ = 0x00;
+        *ptr++ = 0x00;
+        *ptr++ = 0x01;
+        *ptr++ = 0xe0 | (kPID_PMT >> 8);
+        *ptr++ = kPID_PMT & 0xff;
+
+        CHECK_EQ(ptr - crcDataStart, 12);
+        uint32_t crc = htonl(crc32(crcDataStart, ptr - crcDataStart));
+        memcpy(ptr, &crc, 4);
+        ptr += 4;
+
+        size_t sizeLeft = packetDataStart + 188 - ptr;
+        memset(ptr, 0xff, sizeLeft);
+
+        packetDataStart += 188;
+
+        // Program Map (PMT):
+        // 0x47
+        // transport_error_indicator = b0
+        // payload_unit_start_indicator = b1
+        // transport_priority = b0
+        // PID = kPID_PMT (13 bits)
+        // transport_scrambling_control = b00
+        // adaptation_field_control = b01 (no adaptation field, payload only)
+        // continuity_counter = b????
+        // skip = 0x00
+        // -- payload follows
+        // table_id = 0x02
+        // section_syntax_indicator = b1
+        // must_be_zero = b0
+        // reserved = b11
+        // section_length = 0x???
+        // program_number = 0x0001
+        // reserved = b11
+        // version_number = b00001
+        // current_next_indicator = b1
+        // section_number = 0x00
+        // last_section_number = 0x00
+        // reserved = b111
+        // PCR_PID = kPCR_PID (13 bits)
+        // reserved = b1111
+        // program_info_length = 0x000
+        //   one or more elementary stream descriptions follow:
+        //   stream_type = 0x??
+        //   reserved = b111
+        //   elementary_PID = b? ???? ???? ???? (13 bits)
+        //   reserved = b1111
+        //   ES_info_length = 0x000
+        // CRC = 0x????????
+
+        if (++mPMTContinuityCounter == 16) {
+            mPMTContinuityCounter = 0;
+        }
+
+        size_t section_length = 5 * mTracks.size() + 4 + 9;
+
+        ptr = packetDataStart;
+        *ptr++ = 0x47;
+        *ptr++ = 0x40 | (kPID_PMT >> 8);
+        *ptr++ = kPID_PMT & 0xff;
+        *ptr++ = 0x10 | mPMTContinuityCounter;
+        *ptr++ = 0x00;
+
+        crcDataStart = ptr;
+        *ptr++ = 0x02;
+        *ptr++ = 0xb0 | (section_length >> 8);
+        *ptr++ = section_length & 0xff;
+        *ptr++ = 0x00;
+        *ptr++ = 0x01;
+        *ptr++ = 0xc3;
+        *ptr++ = 0x00;
+        *ptr++ = 0x00;
+        *ptr++ = 0xe0 | (kPID_PCR >> 8);
+        *ptr++ = kPID_PCR & 0xff;
+        *ptr++ = 0xf0;
+        *ptr++ = 0x00;
+
+        for (size_t i = 0; i < mTracks.size(); ++i) {
+            const sp<Track> &track = mTracks.itemAt(i);
+
+            *ptr++ = track->streamType();
+            *ptr++ = 0xe0 | (track->PID() >> 8);
+            *ptr++ = track->PID() & 0xff;
+            *ptr++ = 0xf0;
+            *ptr++ = 0x00;
+        }
+
+        CHECK_EQ(ptr - crcDataStart, 12 + mTracks.size() * 5);
+        crc = htonl(crc32(crcDataStart, ptr - crcDataStart));
+        memcpy(ptr, &crc, 4);
+        ptr += 4;
+
+        sizeLeft = packetDataStart + 188 - ptr;
+        memset(ptr, 0xff, sizeLeft);
+
+        packetDataStart += 188;
+    }
+
+    if (flags & EMIT_PCR) {
+        // PCR stream
+        // 0x47
+        // transport_error_indicator = b0
+        // payload_unit_start_indicator = b1
+        // transport_priority = b0
+        // PID = kPCR_PID (13 bits)
+        // transport_scrambling_control = b00
+        // adaptation_field_control = b10 (adaptation field only, no payload)
+        // continuity_counter = b0000 (does not increment)
+        // adaptation_field_length = 183
+        // discontinuity_indicator = b0
+        // random_access_indicator = b0
+        // elementary_stream_priority_indicator = b0
+        // PCR_flag = b1
+        // OPCR_flag = b0
+        // splicing_point_flag = b0
+        // transport_private_data_flag = b0
+        // adaptation_field_extension_flag = b0
+        // program_clock_reference_base = b?????????????????????????????????
+        // reserved = b111111
+        // program_clock_reference_extension = b?????????
+
+#if 0
+        int64_t nowUs = ALooper::GetNowUs();
+#else
+        int64_t nowUs = timeUs;
+#endif
+
+        uint64_t PCR = nowUs * 27;  // PCR based on a 27MHz clock
+        uint64_t PCR_base = PCR / 300;
+        uint32_t PCR_ext = PCR % 300;
+
+        uint8_t *ptr = packetDataStart;
+        *ptr++ = 0x47;
+        *ptr++ = 0x40 | (kPID_PCR >> 8);
+        *ptr++ = kPID_PCR & 0xff;
+        *ptr++ = 0x20;
+        *ptr++ = 0xb7;  // adaptation_field_length
+        *ptr++ = 0x10;
+        *ptr++ = (PCR_base >> 25) & 0xff;
+        *ptr++ = (PCR_base >> 17) & 0xff;
+        *ptr++ = (PCR_base >> 9) & 0xff;
+        *ptr++ = ((PCR_base & 1) << 7) | 0x7e | ((PCR_ext >> 8) & 1);
+        *ptr++ = (PCR_ext & 0xff);
+
+        size_t sizeLeft = packetDataStart + 188 - ptr;
+        memset(ptr, 0xff, sizeLeft);
+
+        packetDataStart += 188;
+    }
+
+    uint32_t PTS = (timeUs * 9ll) / 100ll;
+
+    size_t PES_packet_length = accessUnit->size() + 8;
+    bool padding = (accessUnit->size() < (188 - 18));
+
+    if (PES_packet_length >= 65536) {
+        // This really should only happen for video.
+        CHECK(track->isVideo());
+
+        // It's valid to set this to 0 for video according to the specs.
+        PES_packet_length = 0;
+    }
+
+    uint8_t *ptr = packetDataStart;
+    *ptr++ = 0x47;
+    *ptr++ = 0x40 | (track->PID() >> 8);
+    *ptr++ = track->PID() & 0xff;
+    *ptr++ = (padding ? 0x30 : 0x10) | track->incrementContinuityCounter();
+
+    if (padding) {
+        size_t paddingSize = 188 - 18 - accessUnit->size();
+        *ptr++ = paddingSize - 1;
+        if (paddingSize >= 2) {
+            *ptr++ = 0x00;
+            memset(ptr, 0xff, paddingSize - 2);
+            ptr += paddingSize - 2;
+        }
+    }
+
+    *ptr++ = 0x00;
+    *ptr++ = 0x00;
+    *ptr++ = 0x01;
+    *ptr++ = track->streamID();
+    *ptr++ = PES_packet_length >> 8;
+    *ptr++ = PES_packet_length & 0xff;
+    *ptr++ = 0x84;
+    *ptr++ = 0x80;
+    *ptr++ = 0x05;
+    *ptr++ = 0x20 | (((PTS >> 30) & 7) << 1) | 1;
+    *ptr++ = (PTS >> 22) & 0xff;
+    *ptr++ = (((PTS >> 15) & 0x7f) << 1) | 1;
+    *ptr++ = (PTS >> 7) & 0xff;
+    *ptr++ = ((PTS & 0x7f) << 1) | 1;
+
+    // 18 bytes of TS/PES header leave 188 - 18 = 170 bytes for the payload
+
+    size_t sizeLeft = packetDataStart + 188 - ptr;
+    size_t copy = accessUnit->size();
+    if (copy > sizeLeft) {
+        copy = sizeLeft;
+    }
+
+    memcpy(ptr, accessUnit->data(), copy);
+    ptr += copy;
+    CHECK_EQ(sizeLeft, copy);
+    memset(ptr, 0xff, sizeLeft - copy);
+
+    packetDataStart += 188;
+
+    size_t offset = copy;
+    while (offset < accessUnit->size()) {
+        bool padding = (accessUnit->size() - offset) < (188 - 4);
+
+        // for subsequent fragments of "buffer":
+        // 0x47
+        // transport_error_indicator = b0
+        // payload_unit_start_indicator = b0
+        // transport_priority = b0
+        // PID = b0 0001 1110 ???? (13 bits) [0x1e0 + 1 + sourceIndex]
+        // transport_scrambling_control = b00
+        // adaptation_field_control = b??
+        // continuity_counter = b????
+        // the fragment of "buffer" follows.
+
+        uint8_t *ptr = packetDataStart;
+        *ptr++ = 0x47;
+        *ptr++ = 0x00 | (track->PID() >> 8);
+        *ptr++ = track->PID() & 0xff;
+
+        *ptr++ = (padding ? 0x30 : 0x10) | track->incrementContinuityCounter();
+
+        if (padding) {
+            size_t paddingSize = 188 - 4 - (accessUnit->size() - offset);
+            *ptr++ = paddingSize - 1;
+            if (paddingSize >= 2) {
+                *ptr++ = 0x00;
+                memset(ptr, 0xff, paddingSize - 2);
+                ptr += paddingSize - 2;
+            }
+        }
+
+        // 4 bytes of TS header leave 188 - 4 = 184 bytes for the payload
+
+        size_t sizeLeft = packetDataStart + 188 - ptr;
+        size_t copy = accessUnit->size() - offset;
+        if (copy > sizeLeft) {
+            copy = sizeLeft;
+        }
+
+        memcpy(ptr, accessUnit->data() + offset, copy);
+        ptr += copy;
+        CHECK_EQ(sizeLeft, copy);
+        memset(ptr, 0xff, sizeLeft - copy);
+
+        offset += copy;
+        packetDataStart += 188;
+    }
+
+    CHECK(packetDataStart == buffer->data() + buffer->capacity());
+
+    *packets = buffer;
+
+    return OK;
+}
+
+void TSPacketizer::initCrcTable() {
+    uint32_t poly = 0x04C11DB7;
+
+    for (int i = 0; i < 256; i++) {
+        uint32_t crc = i << 24;
+        for (int j = 0; j < 8; j++) {
+            crc = (crc << 1) ^ ((crc & 0x80000000) ? (poly) : 0);
+        }
+        mCrcTable[i] = crc;
+    }
+}
+
+uint32_t TSPacketizer::crc32(const uint8_t *start, size_t size) const {
+    uint32_t crc = 0xFFFFFFFF;
+    const uint8_t *p;
+
+    for (p = start; p < start + size; ++p) {
+        crc = (crc << 8) ^ mCrcTable[((crc >> 24) ^ *p) & 0xFF];
+    }
+
+    return crc;
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.h b/media/libstagefright/wifi-display/source/TSPacketizer.h
new file mode 100644
index 0000000..9dbeb27
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 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 TS_PACKETIZER_H_
+
+#define TS_PACKETIZER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+// Forms the packets of a transport stream given access units.
+// Emits metadata tables (PAT and PMT) and timestamp stream (PCR) based
+// on flags.
+struct TSPacketizer : public RefBase {
+    TSPacketizer();
+
+    // Returns trackIndex or error.
+    ssize_t addTrack(const sp<AMessage> &format);
+
+    enum {
+        EMIT_PAT_AND_PMT = 1,
+        EMIT_PCR         = 2,
+    };
+    status_t packetize(
+            size_t trackIndex, const sp<ABuffer> &accessUnit,
+            sp<ABuffer> *packets,
+            uint32_t flags);
+
+protected:
+    virtual ~TSPacketizer();
+
+private:
+    enum {
+        kPID_PMT = 0x100,
+        kPID_PCR = 0x1000,
+    };
+
+    struct Track;
+
+    Vector<sp<Track> > mTracks;
+
+    unsigned mPATContinuityCounter;
+    unsigned mPMTContinuityCounter;
+
+    uint32_t mCrcTable[256];
+
+    void initCrcTable();
+    uint32_t crc32(const uint8_t *start, size_t size) const;
+
+    DISALLOW_EVIL_CONSTRUCTORS(TSPacketizer);
+};
+
+}  // namespace android
+
+#endif  // TS_PACKETIZER_H_
+
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
new file mode 100644
index 0000000..3f75bc3
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -0,0 +1,944 @@
+/*
+ * Copyright 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 "WifiDisplaySource"
+#include <utils/Log.h>
+
+#include "WifiDisplaySource.h"
+#include "PlaybackSession.h"
+#include "ParsedMessage.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+WifiDisplaySource::WifiDisplaySource(const sp<ANetworkSession> &netSession)
+    : mNetSession(netSession),
+      mSessionID(0),
+      mReaperPending(false),
+      mNextCSeq(1) {
+}
+
+WifiDisplaySource::~WifiDisplaySource() {
+}
+
+status_t WifiDisplaySource::start(int32_t port) {
+    sp<AMessage> msg = new AMessage(kWhatStart, id());
+    msg->setInt32("port", port);
+
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (!response->findInt32("err", &err)) {
+        err = OK;
+    }
+
+    return err;
+}
+
+status_t WifiDisplaySource::stop() {
+    sp<AMessage> msg = new AMessage(kWhatStop, id());
+
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (!response->findInt32("err", &err)) {
+        err = OK;
+    }
+
+    return err;
+}
+
+void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatStart:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            int32_t port;
+            CHECK(msg->findInt32("port", &port));
+
+            sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
+
+            status_t err = mNetSession->createRTSPServer(
+                    port, notify, &mSessionID);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatRTSPNotify:
+        {
+            int32_t reason;
+            CHECK(msg->findInt32("reason", &reason));
+
+            switch (reason) {
+                case ANetworkSession::kWhatError:
+                {
+                    int32_t sessionID;
+                    CHECK(msg->findInt32("sessionID", &sessionID));
+
+                    int32_t err;
+                    CHECK(msg->findInt32("err", &err));
+
+                    AString detail;
+                    CHECK(msg->findString("detail", &detail));
+
+                    ALOGE("An error occurred in session %d (%d, '%s/%s').",
+                          sessionID,
+                          err,
+                          detail.c_str(),
+                          strerror(-err));
+
+                    mNetSession->destroySession(sessionID);
+
+                    mClientIPs.removeItem(sessionID);
+                    break;
+                }
+
+                case ANetworkSession::kWhatClientConnected:
+                {
+                    int32_t sessionID;
+                    CHECK(msg->findInt32("sessionID", &sessionID));
+
+                    ClientInfo info;
+                    CHECK(msg->findString("client-ip", &info.mRemoteIP));
+                    CHECK(msg->findString("server-ip", &info.mLocalIP));
+                    CHECK(msg->findInt32("server-port", &info.mLocalPort));
+
+                    ALOGI("We now have a client (%d) connected.", sessionID);
+
+                    mClientIPs.add(sessionID, info);
+
+                    status_t err = sendM1(sessionID);
+                    CHECK_EQ(err, (status_t)OK);
+                    break;
+                }
+
+                case ANetworkSession::kWhatData:
+                {
+                    onReceiveClientData(msg);
+                    break;
+                }
+
+                default:
+                    TRESPASS();
+            }
+            break;
+        }
+
+        case kWhatStop:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            for (size_t i = mPlaybackSessions.size(); i-- > 0;) {
+                const sp<PlaybackSession> &playbackSession =
+                    mPlaybackSessions.valueAt(i);
+
+                looper()->unregisterHandler(playbackSession->id());
+                mPlaybackSessions.removeItemsAt(i);
+            }
+
+            status_t err = OK;
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatReapDeadClients:
+        {
+            mReaperPending = false;
+
+            for (size_t i = mPlaybackSessions.size(); i-- > 0;) {
+                const sp<PlaybackSession> &playbackSession =
+                    mPlaybackSessions.valueAt(i);
+
+                if (playbackSession->getLastLifesignUs()
+                        + kPlaybackSessionTimeoutUs < ALooper::GetNowUs()) {
+                    ALOGI("playback session %d timed out, reaping.",
+                            mPlaybackSessions.keyAt(i));
+
+                    looper()->unregisterHandler(playbackSession->id());
+                    mPlaybackSessions.removeItemsAt(i);
+                }
+            }
+
+            if (!mPlaybackSessions.isEmpty()) {
+                scheduleReaper();
+            }
+            break;
+        }
+
+        case kWhatPlaybackSessionNotify:
+        {
+            int32_t playbackSessionID;
+            CHECK(msg->findInt32("playbackSessionID", &playbackSessionID));
+
+            int32_t what;
+            CHECK(msg->findInt32("what", &what));
+
+            ssize_t index = mPlaybackSessions.indexOfKey(playbackSessionID);
+            if (index >= 0) {
+                const sp<PlaybackSession> &playbackSession =
+                    mPlaybackSessions.valueAt(index);
+
+                if (what == PlaybackSession::kWhatSessionDead) {
+                    ALOGI("playback sessions %d wants to quit.",
+                          playbackSessionID);
+
+                    looper()->unregisterHandler(playbackSession->id());
+                    mPlaybackSessions.removeItemsAt(index);
+                } else {
+                    CHECK_EQ(what, PlaybackSession::kWhatBinaryData);
+
+                    int32_t channel;
+                    CHECK(msg->findInt32("channel", &channel));
+
+                    sp<ABuffer> data;
+                    CHECK(msg->findBuffer("data", &data));
+
+                    CHECK_LE(channel, 0xffu);
+                    CHECK_LE(data->size(), 0xffffu);
+
+                    int32_t sessionID;
+                    CHECK(msg->findInt32("sessionID", &sessionID));
+
+                    char header[4];
+                    header[0] = '$';
+                    header[1] = channel;
+                    header[2] = data->size() >> 8;
+                    header[3] = data->size() & 0xff;
+
+                    mNetSession->sendRequest(
+                            sessionID, header, sizeof(header));
+
+                    mNetSession->sendRequest(
+                            sessionID, data->data(), data->size());
+                }
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+void WifiDisplaySource::registerResponseHandler(
+        int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
+    ResponseID id;
+    id.mSessionID = sessionID;
+    id.mCSeq = cseq;
+    mResponseHandlers.add(id, func);
+}
+
+status_t WifiDisplaySource::sendM1(int32_t sessionID) {
+    AString request = "OPTIONS * RTSP/1.0\r\n";
+    AppendCommonResponse(&request, mNextCSeq);
+
+    request.append(
+            "Require: org.wfa.wfd1.0\r\n"
+            "\r\n");
+
+    status_t err =
+        mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+    if (err != OK) {
+        return err;
+    }
+
+    registerResponseHandler(
+            sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM1Response);
+
+    ++mNextCSeq;
+
+    return OK;
+}
+
+status_t WifiDisplaySource::sendM3(int32_t sessionID) {
+    AString body =
+        "wfd_video_formats\r\n"
+        "wfd_audio_codecs\r\n"
+        "wfd_client_rtp_ports\r\n";
+
+    AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+    AppendCommonResponse(&request, mNextCSeq);
+
+    request.append("Content-Type: text/parameters\r\n");
+    request.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+    request.append("\r\n");
+    request.append(body);
+
+    status_t err =
+        mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+    if (err != OK) {
+        return err;
+    }
+
+    registerResponseHandler(
+            sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM3Response);
+
+    ++mNextCSeq;
+
+    return OK;
+}
+
+status_t WifiDisplaySource::sendM4(int32_t sessionID) {
+    // wfd_video_formats:
+    // 1 byte "native"
+    // 1 byte "preferred-display-mode-supported" 0 or 1
+    // one or more avc codec structures
+    //   1 byte profile
+    //   1 byte level
+    //   4 byte CEA mask
+    //   4 byte VESA mask
+    //   4 byte HH mask
+    //   1 byte latency
+    //   2 byte min-slice-slice
+    //   2 byte slice-enc-params
+    //   1 byte framerate-control-support
+    //   max-hres (none or 2 byte)
+    //   max-vres (none or 2 byte)
+
+    const ClientInfo &info = mClientIPs.valueFor(sessionID);
+
+    AString body = StringPrintf(
+        "wfd_video_formats: "
+        "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n"
+        "wfd_audio_codecs: AAC 00000001 00\r\n"  // 2 ch AAC 48kHz
+        "wfd_presentation_URL: rtsp://%s:%d/wfd1.0/streamid=0 none\r\n"
+        "wfd_client_rtp_ports: RTP/AVP/UDP;unicast 19000 0 mode=play\r\n",
+        info.mLocalIP.c_str(), info.mLocalPort);
+
+    AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+    AppendCommonResponse(&request, mNextCSeq);
+
+    request.append("Content-Type: text/parameters\r\n");
+    request.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+    request.append("\r\n");
+    request.append(body);
+
+    status_t err =
+        mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+    if (err != OK) {
+        return err;
+    }
+
+    registerResponseHandler(
+            sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM4Response);
+
+    ++mNextCSeq;
+
+    return OK;
+}
+
+status_t WifiDisplaySource::sendM5(int32_t sessionID) {
+    AString body = "wfd_trigger_method: SETUP\r\n";
+
+    AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+    AppendCommonResponse(&request, mNextCSeq);
+
+    request.append("Content-Type: text/parameters\r\n");
+    request.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+    request.append("\r\n");
+    request.append(body);
+
+    status_t err =
+        mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+    if (err != OK) {
+        return err;
+    }
+
+    registerResponseHandler(
+            sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM5Response);
+
+    ++mNextCSeq;
+
+    return OK;
+}
+
+status_t WifiDisplaySource::onReceiveM1Response(
+        int32_t sessionID, const sp<ParsedMessage> &msg) {
+    int32_t statusCode;
+    if (!msg->getStatusCode(&statusCode)) {
+        return ERROR_MALFORMED;
+    }
+
+    if (statusCode != 200) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    return OK;
+}
+
+status_t WifiDisplaySource::onReceiveM3Response(
+        int32_t sessionID, const sp<ParsedMessage> &msg) {
+    int32_t statusCode;
+    if (!msg->getStatusCode(&statusCode)) {
+        return ERROR_MALFORMED;
+    }
+
+    if (statusCode != 200) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    return sendM4(sessionID);
+}
+
+status_t WifiDisplaySource::onReceiveM4Response(
+        int32_t sessionID, const sp<ParsedMessage> &msg) {
+    int32_t statusCode;
+    if (!msg->getStatusCode(&statusCode)) {
+        return ERROR_MALFORMED;
+    }
+
+    if (statusCode != 200) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    return sendM5(sessionID);
+}
+
+status_t WifiDisplaySource::onReceiveM5Response(
+        int32_t sessionID, const sp<ParsedMessage> &msg) {
+    int32_t statusCode;
+    if (!msg->getStatusCode(&statusCode)) {
+        return ERROR_MALFORMED;
+    }
+
+    if (statusCode != 200) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    return OK;
+}
+
+void WifiDisplaySource::scheduleReaper() {
+    if (mReaperPending) {
+        return;
+    }
+
+    mReaperPending = true;
+    (new AMessage(kWhatReapDeadClients, id()))->post(kReaperIntervalUs);
+}
+
+void WifiDisplaySource::onReceiveClientData(const sp<AMessage> &msg) {
+    int32_t sessionID;
+    CHECK(msg->findInt32("sessionID", &sessionID));
+
+    sp<RefBase> obj;
+    CHECK(msg->findObject("data", &obj));
+
+    sp<ParsedMessage> data =
+        static_cast<ParsedMessage *>(obj.get());
+
+    ALOGV("session %d received '%s'",
+          sessionID, data->debugString().c_str());
+
+    AString method;
+    AString uri;
+    data->getRequestField(0, &method);
+
+    int32_t cseq;
+    if (!data->findInt32("cseq", &cseq)) {
+        sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
+        return;
+    }
+
+    if (method.startsWith("RTSP/")) {
+        // This is a response.
+
+        ResponseID id;
+        id.mSessionID = sessionID;
+        id.mCSeq = cseq;
+
+        ssize_t index = mResponseHandlers.indexOfKey(id);
+
+        if (index < 0) {
+            ALOGW("Received unsolicited server response, cseq %d", cseq);
+            return;
+        }
+
+        HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
+        mResponseHandlers.removeItemsAt(index);
+
+        status_t err = (this->*func)(sessionID, data);
+
+        if (err != OK) {
+            ALOGW("Response handler for session %d, cseq %d returned "
+                  "err %d (%s)",
+                  sessionID, cseq, err, strerror(-err));
+        }
+    } else {
+        AString version;
+        data->getRequestField(2, &version);
+        if (!(version == AString("RTSP/1.0"))) {
+            sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
+            return;
+        }
+
+        if (method == "DESCRIBE") {
+            onDescribeRequest(sessionID, cseq, data);
+        } else if (method == "OPTIONS") {
+            onOptionsRequest(sessionID, cseq, data);
+        } else if (method == "SETUP") {
+            onSetupRequest(sessionID, cseq, data);
+        } else if (method == "PLAY") {
+            onPlayRequest(sessionID, cseq, data);
+        } else if (method == "PAUSE") {
+            onPauseRequest(sessionID, cseq, data);
+        } else if (method == "TEARDOWN") {
+            onTeardownRequest(sessionID, cseq, data);
+        } else if (method == "GET_PARAMETER") {
+            onGetParameterRequest(sessionID, cseq, data);
+        } else if (method == "SET_PARAMETER") {
+            onSetParameterRequest(sessionID, cseq, data);
+        } else {
+            sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
+        }
+    }
+}
+
+void WifiDisplaySource::onDescribeRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    int64_t nowUs = ALooper::GetNowUs();
+
+    AString sdp;
+    sdp.append("v=0\r\n");
+
+    sdp.append(StringPrintf(
+                "o=- %lld %lld IN IP4 0.0.0.0\r\n", nowUs, nowUs));
+
+    sdp.append(
+            "o=- 0 0 IN IP4 127.0.0.0\r\n"
+            "s=Sample\r\n"
+            "c=IN IP4 0.0.0.0\r\n"
+            "b=AS:502\r\n"
+            "t=0 0\r\n"
+            "a=control:*\r\n"
+            "a=range:npt=now-\r\n"
+            "m=video 0 RTP/AVP 33\r\n"
+            "a=rtpmap:33 MP2T/90000\r\n"
+            "a=control:\r\n");
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq);
+
+    response.append("Content-Type: application/sdp\r\n");
+
+    // response.append("Content-Base: rtsp://0.0.0.0:7236\r\n");
+    response.append(StringPrintf("Content-Length: %d\r\n", sdp.size()));
+    response.append("\r\n");
+    response.append(sdp);
+
+    status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onOptionsRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    int32_t playbackSessionID;
+    sp<PlaybackSession> playbackSession =
+        findPlaybackSession(data, &playbackSessionID);
+
+    if (playbackSession != NULL) {
+        playbackSession->updateLiveness();
+    }
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq);
+
+    response.append(
+            "Public: org.wfa.wfd1.0, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, "
+            "GET_PARAMETER, SET_PARAMETER\r\n");
+
+    response.append("\r\n");
+
+    status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+
+    err = sendM3(sessionID);
+    CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onSetupRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    AString transport;
+    if (!data->findString("transport", &transport)) {
+        sendErrorResponse(sessionID, "400 Bad Request", cseq);
+        return;
+    }
+
+    bool useInterleavedTCP = false;
+
+    int clientRtp, clientRtcp;
+    if (transport.startsWith("RTP/AVP/TCP;")) {
+        AString interleaved;
+        if (!ParsedMessage::GetAttribute(
+                    transport.c_str(), "interleaved", &interleaved)
+                || sscanf(interleaved.c_str(), "%d-%d",
+                          &clientRtp, &clientRtcp) != 2) {
+            sendErrorResponse(sessionID, "400 Bad Request", cseq);
+            return;
+        }
+
+        useInterleavedTCP = true;
+    } else if (transport.startsWith("RTP/AVP;unicast;")
+            || transport.startsWith("RTP/AVP/UDP;unicast;")) {
+        bool badRequest = false;
+
+        AString clientPort;
+        if (!ParsedMessage::GetAttribute(
+                    transport.c_str(), "client_port", &clientPort)) {
+            badRequest = true;
+        } else if (sscanf(clientPort.c_str(), "%d-%d",
+                          &clientRtp, &clientRtcp) == 2) {
+        } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) {
+            // No RTCP.
+            clientRtcp = -1;
+        } else {
+            badRequest = true;
+        }
+
+        if (badRequest) {
+            sendErrorResponse(sessionID, "400 Bad Request", cseq);
+            return;
+        }
+#if 1
+    // The LG dongle doesn't specify client_port=xxx apparently.
+    } else if (transport == "RTP/AVP/UDP;unicast") {
+        clientRtp = 19000;
+        clientRtcp = clientRtp + 1;
+#endif
+    } else {
+        sendErrorResponse(sessionID, "461 Unsupported Transport", cseq);
+        return;
+    }
+
+    int32_t playbackSessionID = makeUniquePlaybackSessionID();
+
+    sp<AMessage> notify = new AMessage(kWhatPlaybackSessionNotify, id());
+    notify->setInt32("playbackSessionID", playbackSessionID);
+    notify->setInt32("sessionID", sessionID);
+
+    sp<PlaybackSession> playbackSession =
+        new PlaybackSession(mNetSession, notify);
+
+    looper()->registerHandler(playbackSession);
+
+    AString uri;
+    data->getRequestField(1, &uri);
+
+    if (strncasecmp("rtsp://", uri.c_str(), 7)) {
+        sendErrorResponse(sessionID, "400 Bad Request", cseq);
+        return;
+    }
+
+    if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) {
+        sendErrorResponse(sessionID, "404 Not found", cseq);
+        return;
+    }
+
+    const ClientInfo &info = mClientIPs.valueFor(sessionID);
+
+    status_t err = playbackSession->init(
+            info.mRemoteIP.c_str(),
+            clientRtp,
+            clientRtcp,
+            useInterleavedTCP);
+
+    if (err != OK) {
+        looper()->unregisterHandler(playbackSession->id());
+        playbackSession.clear();
+    }
+
+    switch (err) {
+        case OK:
+            break;
+        case -ENOENT:
+            sendErrorResponse(sessionID, "404 Not Found", cseq);
+            return;
+        default:
+            sendErrorResponse(sessionID, "403 Forbidden", cseq);
+            return;
+    }
+
+    mPlaybackSessions.add(playbackSessionID, playbackSession);
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq, playbackSessionID);
+
+    if (useInterleavedTCP) {
+        response.append(
+                StringPrintf(
+                    "Transport: RTP/AVP/TCP;interleaved=%d-%d;",
+                    clientRtp, clientRtcp));
+    } else {
+        int32_t serverRtp = playbackSession->getRTPPort();
+
+        if (clientRtcp >= 0) {
+            response.append(
+                    StringPrintf(
+                        "Transport: RTP/AVP;unicast;client_port=%d-%d;"
+                        "server_port=%d-%d\r\n",
+                        clientRtp, clientRtcp, serverRtp, serverRtp + 1));
+        } else {
+            response.append(
+                    StringPrintf(
+                        "Transport: RTP/AVP;unicast;client_port=%d;"
+                        "server_port=%d\r\n",
+                        clientRtp, serverRtp));
+        }
+    }
+
+    response.append("\r\n");
+
+    err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+
+#if 0
+    // XXX the dongle does not currently send keep-alives.
+    scheduleReaper();
+#endif
+}
+
+void WifiDisplaySource::onPlayRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    int32_t playbackSessionID;
+    sp<PlaybackSession> playbackSession =
+        findPlaybackSession(data, &playbackSessionID);
+
+    if (playbackSession == NULL) {
+        sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+        return;
+    }
+
+    status_t err = playbackSession->play();
+    CHECK_EQ(err, (status_t)OK);
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq, playbackSessionID);
+    response.append("Range: npt=now-\r\n");
+    response.append("\r\n");
+
+    err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onPauseRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    int32_t playbackSessionID;
+    sp<PlaybackSession> playbackSession =
+        findPlaybackSession(data, &playbackSessionID);
+
+    if (playbackSession == NULL) {
+        sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+        return;
+    }
+
+    status_t err = playbackSession->pause();
+    CHECK_EQ(err, (status_t)OK);
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq, playbackSessionID);
+    response.append("\r\n");
+
+    err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onTeardownRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    int32_t playbackSessionID;
+    sp<PlaybackSession> playbackSession =
+        findPlaybackSession(data, &playbackSessionID);
+
+    if (playbackSession == NULL) {
+        sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+        return;
+    }
+
+    looper()->unregisterHandler(playbackSession->id());
+    mPlaybackSessions.removeItem(playbackSessionID);
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq, playbackSessionID);
+    response.append("\r\n");
+
+    status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onGetParameterRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    int32_t playbackSessionID;
+    sp<PlaybackSession> playbackSession =
+        findPlaybackSession(data, &playbackSessionID);
+
+    if (playbackSession == NULL) {
+        sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+        return;
+    }
+
+    playbackSession->updateLiveness();
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq, playbackSessionID);
+    response.append("\r\n");
+
+    status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySource::onSetParameterRequest(
+        int32_t sessionID,
+        int32_t cseq,
+        const sp<ParsedMessage> &data) {
+    int32_t playbackSessionID;
+#if 0
+    // XXX the dongle does not include a "Session:" header in this request.
+    sp<PlaybackSession> playbackSession =
+        findPlaybackSession(data, &playbackSessionID);
+
+    if (playbackSession == NULL) {
+        sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+        return;
+    }
+#else
+    CHECK_EQ(mPlaybackSessions.size(), 1u);
+    playbackSessionID = mPlaybackSessions.keyAt(0);
+    sp<PlaybackSession> playbackSession = mPlaybackSessions.valueAt(0);
+#endif
+
+    playbackSession->updateLiveness();
+
+    AString response = "RTSP/1.0 200 OK\r\n";
+    AppendCommonResponse(&response, cseq, playbackSessionID);
+    response.append("\r\n");
+
+    status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+}
+
+// static
+void WifiDisplaySource::AppendCommonResponse(
+        AString *response, int32_t cseq, int32_t playbackSessionID) {
+    time_t now = time(NULL);
+    struct tm *now2 = gmtime(&now);
+    char buf[128];
+    strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", now2);
+
+    response->append("Date: ");
+    response->append(buf);
+    response->append("\r\n");
+
+    response->append("Server: Mine/1.0\r\n");
+
+    if (cseq >= 0) {
+        response->append(StringPrintf("CSeq: %d\r\n", cseq));
+    }
+
+    if (playbackSessionID >= 0ll) {
+        response->append(
+                StringPrintf(
+                    "Session: %d;timeout=%lld\r\n",
+                    playbackSessionID, kPlaybackSessionTimeoutSecs));
+    }
+}
+
+void WifiDisplaySource::sendErrorResponse(
+        int32_t sessionID,
+        const char *errorDetail,
+        int32_t cseq) {
+    AString response;
+    response.append("RTSP/1.0 ");
+    response.append(errorDetail);
+    response.append("\r\n");
+
+    AppendCommonResponse(&response, cseq);
+
+    response.append("\r\n");
+
+    status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+    CHECK_EQ(err, (status_t)OK);
+}
+
+int32_t WifiDisplaySource::makeUniquePlaybackSessionID() const {
+    for (;;) {
+        int32_t playbackSessionID = rand();
+
+        for (size_t i = 0; i < mPlaybackSessions.size(); ++i) {
+            if (mPlaybackSessions.keyAt(i) == playbackSessionID) {
+                continue;
+            }
+        }
+
+        return playbackSessionID;
+    }
+}
+
+sp<WifiDisplaySource::PlaybackSession> WifiDisplaySource::findPlaybackSession(
+        const sp<ParsedMessage> &data, int32_t *playbackSessionID) const {
+    if (!data->findInt32("session", playbackSessionID)) {
+        *playbackSessionID = 0;
+        return NULL;
+    }
+
+    ssize_t index = mPlaybackSessions.indexOfKey(*playbackSessionID);
+    if (index < 0) {
+        return NULL;
+    }
+
+    return mPlaybackSessions.valueAt(index);
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
new file mode 100644
index 0000000..95c3560
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright 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 WIFI_DISPLAY_SOURCE_H_
+
+#define WIFI_DISPLAY_SOURCE_H_
+
+#include "ANetworkSession.h"
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ParsedMessage;
+
+// Represents the RTSP server acting as a wifi display source.
+// Manages incoming connections, sets up Playback sessions as necessary.
+struct WifiDisplaySource : public AHandler {
+    static const unsigned kWifiDisplayDefaultPort = 7236;
+
+    WifiDisplaySource(const sp<ANetworkSession> &netSession);
+
+    status_t start(int32_t port);
+    status_t stop();
+
+protected:
+    virtual ~WifiDisplaySource();
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    struct PlaybackSession;
+
+    enum {
+        kWhatStart,
+        kWhatRTSPNotify,
+        kWhatStop,
+        kWhatReapDeadClients,
+        kWhatPlaybackSessionNotify,
+    };
+
+    struct ResponseID {
+        int32_t mSessionID;
+        int32_t mCSeq;
+
+        bool operator<(const ResponseID &other) const {
+            return mSessionID < other.mSessionID
+                || (mSessionID == other.mSessionID
+                        && mCSeq < other.mCSeq);
+        }
+    };
+
+    typedef status_t (WifiDisplaySource::*HandleRTSPResponseFunc)(
+            int32_t sessionID, const sp<ParsedMessage> &msg);
+
+    static const int64_t kReaperIntervalUs = 1000000ll;
+
+    static const int64_t kPlaybackSessionTimeoutSecs = 30;
+
+    static const int64_t kPlaybackSessionTimeoutUs =
+        kPlaybackSessionTimeoutSecs * 1000000ll;
+
+    sp<ANetworkSession> mNetSession;
+    int32_t mSessionID;
+
+    struct ClientInfo {
+        AString mRemoteIP;
+        AString mLocalIP;
+        int32_t mLocalPort;
+    };
+    KeyedVector<int32_t, ClientInfo> mClientIPs;
+
+    bool mReaperPending;
+
+    int32_t mNextCSeq;
+
+    KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers;
+
+    KeyedVector<int32_t, sp<PlaybackSession> > mPlaybackSessions;
+
+    status_t sendM1(int32_t sessionID);
+    status_t sendM3(int32_t sessionID);
+    status_t sendM4(int32_t sessionID);
+    status_t sendM5(int32_t sessionID);
+
+    status_t onReceiveM1Response(
+            int32_t sessionID, const sp<ParsedMessage> &msg);
+
+    status_t onReceiveM3Response(
+            int32_t sessionID, const sp<ParsedMessage> &msg);
+
+    status_t onReceiveM4Response(
+            int32_t sessionID, const sp<ParsedMessage> &msg);
+
+    status_t onReceiveM5Response(
+            int32_t sessionID, const sp<ParsedMessage> &msg);
+
+    void registerResponseHandler(
+            int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
+
+    void onReceiveClientData(const sp<AMessage> &msg);
+
+    void onDescribeRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void onOptionsRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void onSetupRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void onPlayRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void onPauseRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void onTeardownRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void onGetParameterRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void onSetParameterRequest(
+            int32_t sessionID,
+            int32_t cseq,
+            const sp<ParsedMessage> &data);
+
+    void sendErrorResponse(
+            int32_t sessionID,
+            const char *errorDetail,
+            int32_t cseq);
+
+    static void AppendCommonResponse(
+            AString *response, int32_t cseq, int32_t playbackSessionID = -1ll);
+
+    void scheduleReaper();
+
+    int32_t makeUniquePlaybackSessionID() const;
+
+    sp<PlaybackSession> findPlaybackSession(
+            const sp<ParsedMessage> &data, int32_t *playbackSessionID) const;
+
+    DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySource);
+};
+
+}  // namespace android
+
+#endif  // WIFI_DISPLAY_SOURCE_H_
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
new file mode 100644
index 0000000..32cdf3f
--- /dev/null
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright 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 "wfd"
+#include <utils/Log.h>
+
+#define SUPPORT_SINK    0
+
+#if SUPPORT_SINK
+#include "sink/WifiDisplaySink.h"
+#endif
+
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <media/IMediaPlayerService.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+static void enableDisableRemoteDisplay(bool enable) {
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binder = sm->getService(String16("media.player"));
+
+    sp<IMediaPlayerService> service =
+        interface_cast<IMediaPlayerService>(binder);
+
+    CHECK(service.get() != NULL);
+
+    service->enableRemoteDisplay(enable);
+}
+
+}  // namespace android
+
+static void usage(const char *me) {
+    fprintf(stderr,
+            "usage:\n"
+#if SUPPORT_SINK
+            "           %s -c host[:port]\tconnect to wifi source\n"
+            "           -u uri        \tconnect to an rtsp uri\n"
+#endif
+            "           -e            \tenable remote display\n"
+            "           -d            \tdisable remote display\n",
+            me);
+}
+
+int main(int argc, char **argv) {
+    using namespace android;
+
+    ProcessState::self()->startThreadPool();
+
+    DataSource::RegisterDefaultSniffers();
+
+    AString connectToHost;
+    int32_t connectToPort = -1;
+    AString uri;
+
+    int res;
+    while ((res = getopt(argc, argv, "hc:l:u:ed")) >= 0) {
+        switch (res) {
+#if SUPPORT_SINK
+            case 'c':
+            {
+                const char *colonPos = strrchr(optarg, ':');
+
+                if (colonPos == NULL) {
+                    connectToHost = optarg;
+                    connectToPort = WifiDisplaySource::kWifiDisplayDefaultPort;
+                } else {
+                    connectToHost.setTo(optarg, colonPos - optarg);
+
+                    char *end;
+                    connectToPort = strtol(colonPos + 1, &end, 10);
+
+                    if (*end != '\0' || end == colonPos + 1
+                            || connectToPort < 1 || connectToPort > 65535) {
+                        fprintf(stderr, "Illegal port specified.\n");
+                        exit(1);
+                    }
+                }
+                break;
+            }
+
+            case 'u':
+            {
+                uri = optarg;
+                break;
+            }
+#endif
+
+            case 'e':
+            case 'd':
+            {
+                enableDisableRemoteDisplay(res == 'e');
+                exit(0);
+                break;
+            }
+
+            case '?':
+            case 'h':
+            default:
+                usage(argv[0]);
+                exit(1);
+        }
+    }
+
+#if SUPPORT_SINK
+    if (connectToPort < 0 && uri.empty()) {
+        fprintf(stderr,
+                "You need to select either source host or uri.\n");
+
+        exit(1);
+    }
+
+    if (connectToPort >= 0 && !uri.empty()) {
+        fprintf(stderr,
+                "You need to either connect to a wfd host or an rtsp url, "
+                "not both.\n");
+        exit(1);
+    }
+
+    sp<ANetworkSession> session = new ANetworkSession;
+    session->start();
+
+    sp<ALooper> looper = new ALooper;
+
+    sp<WifiDisplaySink> sink = new WifiDisplaySink(session);
+    looper->registerHandler(sink);
+
+    if (connectToPort >= 0) {
+        sink->start(connectToHost.c_str(), connectToPort);
+    } else {
+        sink->start(uri.c_str());
+    }
+
+    looper->start(true /* runOnCallingThread */);
+#endif
+
+    return 0;
+}