Various improvements of wifi display code

- manually prepend SPS/PPS if encoder doesn't support it
- latency improvements
- support for "our" method of optional RTP retransmission
- improvements to the wfd commandline tool for testing
- make it easier to turn on/off suspension of the video pipeline on idle
- fixes an issue where an error during encryption would cause a SEGV
- add HDCP descriptor if necessary

Squashed commit of the following:

commit 1115be0ebb3b885b4f1b7dba56761ca013d0ec4a
Author: Andreas Huber <andih@google.com>
Date:   Fri Nov 9 11:32:23 2012 -0800

    Better shutdown of wfd -l sessions.

    Change-Id: Id898a14ae21efd3b065b00a729830063d39195a7

commit 0e7d106dfe4eb6e2640b0b66c65deaba265f7ff0
Author: Andreas Huber <andih@google.com>
Date:   Thu Nov 8 16:38:55 2012 -0800

    No more sending delay, create rtp packets upfront.

    Change-Id: I809a225f664fdb485c7d9a49a27886601a6a26b2

commit d399e8571b77353d59afb57508dfd2a82c1ef93a
Author: Andreas Huber <andih@google.com>
Date:   Thu Nov 8 14:19:43 2012 -0800

    Restore AudioSource buffer size, factor out TimeSeries, make

    suspending video optional.

    Change-Id: Ifdfe4d447b901e714abf52856b4641d1d55a5d41

commit f8b649f0b8f917d59f4b8a2e8e6d7db61a684a78
Author: Andreas Huber <andih@google.com>
Date:   Thu Nov 8 09:34:06 2012 -0800

    Pull 480 frames at a time from AudioSource/AudioRecord

    Change-Id: I1e215abd329faec3da026631122c0f4c800c0ac4

commit 1bc13452eb35eebbba00f5da93fa86535be5db59
Author: Andreas Huber <andih@google.com>
Date:   Thu Nov 8 08:50:30 2012 -0800

    fixed bitrate traffic simulation

    Change-Id: Ic5efb7cbb0b5d3b4917bc77b8ba73d447249e695

commit 016cdff18e74bdc631a5679e97192645ed095aa2
Author: Andreas Huber <andih@google.com>
Date:   Wed Nov 7 14:00:03 2012 -0800

    resurrected "our" style of retransmission.

    Change-Id: I34d757aba67428437cb39b8293a9651750ad20d9

commit 384cf1a3c8fb4ec410bdf8fa5722c298e6028f3e
Author: Andreas Huber <andih@google.com>
Date:   Tue Nov 6 09:38:55 2012 -0800

    Changes to make wfd work on manta.

    Change-Id: I7a4e00cf16581fe2146edd1b359af195774090e4

commit 9628f24b22b35f28630d99dda3614babf51bc07e
Author: Andreas Huber <andih@google.com>
Date:   Wed Nov 7 09:15:44 2012 -0800

    Patch up rtp timestamps to more accurately measure network jitter.

    Change-Id: I9502a4615575f97f98a215a13131a89a6ae93c6d

commit 7c891a1a24f08bbd50f55be13f7d05f43e807eb8
Author: Andreas Huber <andih@google.com>
Date:   Tue Nov 6 09:37:24 2012 -0800

    Additions to the "wfd" tool to create a local wfd source.

    Change-Id: I99558653a70fdc703f9d13990b3ce1c4d3ae227a

Change-Id: Ia94c63fc390f597014531073485f0cfc53b3418a
diff --git a/media/libstagefright/wifi-display/ANetworkSession.cpp b/media/libstagefright/wifi-display/ANetworkSession.cpp
index 819cd62..62a6e7f 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.cpp
+++ b/media/libstagefright/wifi-display/ANetworkSession.cpp
@@ -407,6 +407,24 @@
         do {
             const sp<ABuffer> &datagram = *mOutDatagrams.begin();
 
+            uint8_t *data = datagram->data();
+            if (data[0] == 0x80 && (data[1] & 0x7f) == 33) {
+                int64_t nowUs = ALooper::GetNowUs();
+
+                uint32_t prevRtpTime = U32_AT(&data[4]);
+
+                // 90kHz time scale
+                uint32_t rtpTime = (nowUs * 9ll) / 100ll;
+                int32_t diffTime = (int32_t)rtpTime - (int32_t)prevRtpTime;
+
+                ALOGV("correcting rtpTime by %.0f ms", diffTime / 90.0);
+
+                data[4] = rtpTime >> 24;
+                data[5] = (rtpTime >> 16) & 0xff;
+                data[6] = (rtpTime >> 8) & 0xff;
+                data[7] = rtpTime & 0xff;
+            }
+
             int n;
             do {
                 n = send(mSocket, datagram->data(), datagram->size(), 0);
@@ -424,6 +442,9 @@
         } while (err == OK && !mOutDatagrams.empty());
 
         if (err == -EAGAIN) {
+            if (!mOutDatagrams.empty()) {
+                ALOGI("%d datagrams remain queued.", mOutDatagrams.size());
+            }
             err = OK;
         }
 
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index 611bfff..75098f1 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -17,6 +17,7 @@
         source/Sender.cpp               \
         source/TSPacketizer.cpp         \
         source/WifiDisplaySource.cpp    \
+        TimeSeries.cpp                  \
 
 LOCAL_C_INCLUDES:= \
         $(TOP)/frameworks/av/media/libstagefright \
diff --git a/media/libstagefright/wifi-display/TimeSeries.cpp b/media/libstagefright/wifi-display/TimeSeries.cpp
new file mode 100644
index 0000000..d882d98
--- /dev/null
+++ b/media/libstagefright/wifi-display/TimeSeries.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 "TimeSeries.h"
+
+#include <math.h>
+#include <string.h>
+
+namespace android {
+
+TimeSeries::TimeSeries()
+    : mCount(0),
+      mSum(0.0) {
+}
+
+void TimeSeries::add(double val) {
+    if (mCount < kHistorySize) {
+        mValues[mCount++] = val;
+        mSum += val;
+    } else {
+        mSum -= mValues[0];
+        memmove(&mValues[0], &mValues[1], (kHistorySize - 1) * sizeof(double));
+        mValues[kHistorySize - 1] = val;
+        mSum += val;
+    }
+}
+
+double TimeSeries::mean() const {
+    if (mCount < 1) {
+        return 0.0;
+    }
+
+    return mSum / mCount;
+}
+
+double TimeSeries::sdev() const {
+    if (mCount < 1) {
+        return 0.0;
+    }
+
+    double m = mean();
+
+    double sum = 0.0;
+    for (size_t i = 0; i < mCount; ++i) {
+        double tmp = mValues[i] - m;
+        tmp *= tmp;
+
+        sum += tmp;
+    }
+
+    return sqrt(sum / mCount);
+}
+
+}  // namespace android
diff --git a/media/libstagefright/wifi-display/TimeSeries.h b/media/libstagefright/wifi-display/TimeSeries.h
new file mode 100644
index 0000000..c818d51
--- /dev/null
+++ b/media/libstagefright/wifi-display/TimeSeries.h
@@ -0,0 +1,46 @@
+/*
+ * 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 TIME_SERIES_H_
+
+#define TIME_SERIES_H_
+
+#include <sys/types.h>
+
+namespace android {
+
+struct TimeSeries {
+    TimeSeries();
+
+    void add(double val);
+
+    double mean() const;
+    double sdev() const;
+
+private:
+    enum {
+        kHistorySize = 20
+    };
+    double mValues[kHistorySize];
+
+    size_t mCount;
+    double mSum;
+};
+
+}  // namespace android
+
+#endif  // TIME_SERIES_H_
+
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
index 01a394f..82c98b9 100644
--- a/media/libstagefright/wifi-display/source/Converter.cpp
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -48,6 +48,7 @@
       mInputFormat(format),
       mIsVideo(false),
       mIsPCMAudio(usePCMAudio),
+      mNeedToManuallyPrependSPSPPS(false),
       mDoMoreWorkPending(false)
 #if ENABLE_SILENCE_DETECTION
       ,mFirstSilentFrameUs(-1ll)
@@ -94,6 +95,10 @@
     return mOutputFormat;
 }
 
+bool Converter::needToManuallyPrependSPSPPS() const {
+    return mNeedToManuallyPrependSPSPPS;
+}
+
 static int32_t getBitrate(const char *propName, int32_t defaultValue) {
     char val[PROPERTY_VALUE_MAX];
     if (property_get(propName, val, NULL)) {
@@ -157,16 +162,45 @@
         mOutputFormat->setInt32("bitrate-mode", OMX_Video_ControlRateConstant);
         mOutputFormat->setInt32("frame-rate", 30);
         mOutputFormat->setInt32("i-frame-interval", 1);  // Iframes every 1 secs
-        mOutputFormat->setInt32("prepend-sps-pps-to-idr-frames", 1);
     }
 
     ALOGV("output format is '%s'", mOutputFormat->debugString(0).c_str());
 
-    status_t err = mEncoder->configure(
-            mOutputFormat,
-            NULL /* nativeWindow */,
-            NULL /* crypto */,
-            MediaCodec::CONFIGURE_FLAG_ENCODE);
+    mNeedToManuallyPrependSPSPPS = false;
+
+    status_t err = NO_INIT;
+
+    if (!isAudio) {
+        sp<AMessage> tmp = mOutputFormat->dup();
+        tmp->setInt32("prepend-sps-pps-to-idr-frames", 1);
+
+        err = mEncoder->configure(
+                tmp,
+                NULL /* nativeWindow */,
+                NULL /* crypto */,
+                MediaCodec::CONFIGURE_FLAG_ENCODE);
+
+        if (err == OK) {
+            // Encoder supported prepending SPS/PPS, we don't need to emulate
+            // it.
+            mOutputFormat = tmp;
+        } else {
+            mNeedToManuallyPrependSPSPPS = true;
+
+            ALOGI("We going to manually prepend SPS and PPS to IDR frames.");
+        }
+    }
+
+    if (err != OK) {
+        // We'll get here for audio or if we failed to configure the encoder
+        // to automatically prepend SPS/PPS in the case of video.
+
+        err = mEncoder->configure(
+                    mOutputFormat,
+                    NULL /* nativeWindow */,
+                    NULL /* crypto */,
+                    MediaCodec::CONFIGURE_FLAG_ENCODE);
+    }
 
     if (err != OK) {
         return err;
diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h
index 2cdeda3..0665eea 100644
--- a/media/libstagefright/wifi-display/source/Converter.h
+++ b/media/libstagefright/wifi-display/source/Converter.h
@@ -44,6 +44,7 @@
     size_t getInputBufferCount() const;
 
     sp<AMessage> getOutputFormat() const;
+    bool needToManuallyPrependSPSPPS() const;
 
     void feedAccessUnit(const sp<ABuffer> &accessUnit);
     void signalEOS();
@@ -78,6 +79,7 @@
     bool mIsVideo;
     bool mIsPCMAudio;
     sp<AMessage> mOutputFormat;
+    bool mNeedToManuallyPrependSPSPPS;
 
     sp<MediaCodec> mEncoder;
     sp<AMessage> mEncoderActivityNotify;
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index f1e7140..4e5eb52 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -26,6 +26,7 @@
 #include "Sender.h"
 #include "TSPacketizer.h"
 #include "include/avc_utils.h"
+#include "WifiDisplaySource.h"
 
 #include <binder/IServiceManager.h>
 #include <gui/ISurfaceComposer.h>
@@ -81,7 +82,10 @@
     bool hasOutputBuffer(int64_t *timeUs) const;
     void queueOutputBuffer(const sp<ABuffer> &accessUnit);
     sp<ABuffer> dequeueOutputBuffer();
+
+#if SUSPEND_VIDEO_IF_IDLE
     bool isSuspended() const;
+#endif
 
     size_t countQueuedOutputBuffers() const {
         return mQueuedOutputBuffers.size();
@@ -279,7 +283,6 @@
 void WifiDisplaySource::PlaybackSession::Track::queueOutputBuffer(
         const sp<ABuffer> &accessUnit) {
     mQueuedOutputBuffers.push_back(accessUnit);
-
     mLastOutputBufferQueuedTimeUs = ALooper::GetNowUs();
 }
 
@@ -292,6 +295,7 @@
     return outputBuffer;
 }
 
+#if SUSPEND_VIDEO_IF_IDLE
 bool WifiDisplaySource::PlaybackSession::Track::isSuspended() const {
     if (!mQueuedOutputBuffers.empty()) {
         return false;
@@ -307,6 +311,7 @@
     // this track suspended for the time being.
     return (ALooper::GetNowUs() - mLastOutputBufferQueuedTimeUs) > 60000ll;
 }
+#endif
 
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -443,8 +448,13 @@
                 ssize_t packetizerTrackIndex = track->packetizerTrackIndex();
 
                 if (packetizerTrackIndex < 0) {
-                    packetizerTrackIndex =
-                        mPacketizer->addTrack(track->getFormat());
+                    sp<AMessage> trackFormat = track->getFormat()->dup();
+                    if (mHDCP != NULL && !track->isAudio()) {
+                        // HDCP2.0 _and_ HDCP 2.1 specs say to set the version
+                        // inside the HDCP descriptor to 0x20!!!
+                        trackFormat->setInt32("hdcp-version", 0x20);
+                    }
+                    packetizerTrackIndex = mPacketizer->addTrack(trackFormat);
 
                     CHECK_GE(packetizerTrackIndex, 0);
 
@@ -642,8 +652,10 @@
     sp<Converter> converter =
         new Converter(notify, codecLooper, format, usePCMAudio);
 
-    if (converter->initCheck() != OK) {
-        return converter->initCheck();
+    err = converter->initCheck();
+    if (err != OK) {
+        ALOGE("%s converter returned err %d", isVideo ? "video" : "audio", err);
+        return err;
     }
 
     looper()->registerHandler(converter);
@@ -735,11 +747,19 @@
 }
 
 int32_t WifiDisplaySource::PlaybackSession::width() const {
+#if USE_1080P
+    return 1920;
+#else
     return 1280;
+#endif
 }
 
 int32_t WifiDisplaySource::PlaybackSession::height() const {
+#if USE_1080P
+    return 1080;
+#else
     return 720;
+#endif
 }
 
 void WifiDisplaySource::PlaybackSession::requestIDRFrame() {
@@ -767,7 +787,7 @@
 }
 
 status_t WifiDisplaySource::PlaybackSession::packetizeAccessUnit(
-        size_t trackIndex, const sp<ABuffer> &accessUnit,
+        size_t trackIndex, sp<ABuffer> accessUnit,
         sp<ABuffer> *packets) {
     const sp<Track> &track = mTracks.valueFor(trackIndex);
 
@@ -776,9 +796,20 @@
     bool isHDCPEncrypted = false;
     uint64_t inputCTR;
     uint8_t HDCP_private_data[16];
+
+    bool manuallyPrependSPSPPS =
+        !track->isAudio()
+        && track->converter()->needToManuallyPrependSPSPPS()
+        && IsIDR(accessUnit);
+
     if (mHDCP != NULL && !track->isAudio()) {
         isHDCPEncrypted = true;
 
+        if (manuallyPrependSPSPPS) {
+            accessUnit = mPacketizer->prependCSD(
+                    track->packetizerTrackIndex(), accessUnit);
+        }
+
         status_t err = mHDCP->encrypt(
                 accessUnit->data(), accessUnit->size(),
                 trackIndex  /* streamCTR */,
@@ -858,6 +889,8 @@
 #endif
 
         flags |= TSPacketizer::IS_ENCRYPTED;
+    } else if (manuallyPrependSPSPPS) {
+        flags |= TSPacketizer::PREPEND_SPS_PPS_TO_IDR_FRAMES;
     }
 
     int64_t timeUs = ALooper::GetNowUs();
@@ -930,12 +963,21 @@
                 minTrackIndex = mTracks.keyAt(i);
                 minTimeUs = timeUs;
             }
-        } else if (!track->isSuspended()) {
+        }
+#if SUSPEND_VIDEO_IF_IDLE
+        else if (!track->isSuspended()) {
             // We still consider this track "live", so it should keep
             // delivering output data whose time stamps we'll have to
             // consider for proper interleaving.
             return false;
         }
+#else
+        else {
+            // We need access units available on all tracks to be able to
+            // dequeue the earliest one.
+            return false;
+        }
+#endif
     }
 
     if (minTrackIndex < 0) {
@@ -950,6 +992,7 @@
 
     if (err != OK) {
         notifySessionDead();
+        return false;
     }
 
     if ((ssize_t)minTrackIndex == mVideoTrackIndex) {
@@ -957,6 +1000,17 @@
     }
     mSender->queuePackets(minTimeUs, packets);
 
+#if 0
+    if (minTrackIndex == mVideoTrackIndex) {
+        int64_t nowUs = ALooper::GetNowUs();
+
+        // Latency from "data acquired" to "ready to send if we wanted to".
+        ALOGI("[%s] latencyUs = %lld ms",
+              minTrackIndex == mVideoTrackIndex ? "video" : "audio",
+              (nowUs - minTimeUs) / 1000ll);
+    }
+#endif
+
     return true;
 }
 
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h
index cc8b244..dabc1c4 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.h
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.h
@@ -127,7 +127,7 @@
     bool allTracksHavePacketizerIndex();
 
     status_t packetizeAccessUnit(
-            size_t trackIndex, const sp<ABuffer> &accessUnit,
+            size_t trackIndex, sp<ABuffer> accessUnit,
             sp<ABuffer> *packets);
 
     status_t packetizeQueuedAccessUnits();
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.cpp b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
index 641e63f..72be927 100644
--- a/media/libstagefright/wifi-display/source/RepeaterSource.cpp
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
@@ -125,11 +125,14 @@
                 return mResult;
             }
 
+#if SUSPEND_VIDEO_IF_IDLE
             int64_t nowUs = ALooper::GetNowUs();
             if (nowUs - mLastBufferUpdateUs > 1000000ll) {
                 mLastBufferUpdateUs = -1ll;
                 stale = true;
-            } else {
+            } else
+#endif
+            {
                 mBuffer->add_ref();
                 *buffer = mBuffer;
                 (*buffer)->meta_data()->setInt64(kKeyTime, bufferTimeUs);
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.h b/media/libstagefright/wifi-display/source/RepeaterSource.h
index e4aa2b6..a13973c 100644
--- a/media/libstagefright/wifi-display/source/RepeaterSource.h
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.h
@@ -6,6 +6,8 @@
 #include <media/stagefright/foundation/AHandlerReflector.h>
 #include <media/stagefright/MediaSource.h>
 
+#define SUSPEND_VIDEO_IF_IDLE   1
+
 namespace android {
 
 // This MediaSource delivers frames at a constant rate by repeating buffers
diff --git a/media/libstagefright/wifi-display/source/Sender.cpp b/media/libstagefright/wifi-display/source/Sender.cpp
index ea12424..9048691 100644
--- a/media/libstagefright/wifi-display/source/Sender.cpp
+++ b/media/libstagefright/wifi-display/source/Sender.cpp
@@ -21,6 +21,7 @@
 #include "Sender.h"
 
 #include "ANetworkSession.h"
+#include "TimeSeries.h"
 
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -29,79 +30,8 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/Utils.h>
 
-#include <math.h>
-
-#define DEBUG_JITTER    0
-
 namespace android {
 
-////////////////////////////////////////////////////////////////////////////////
-
-#if DEBUG_JITTER
-struct TimeSeries {
-    TimeSeries();
-
-    void add(double val);
-
-    double mean() const;
-    double sdev() const;
-
-private:
-    enum {
-        kHistorySize = 20
-    };
-    double mValues[kHistorySize];
-
-    size_t mCount;
-    double mSum;
-};
-
-TimeSeries::TimeSeries()
-    : mCount(0),
-      mSum(0.0) {
-}
-
-void TimeSeries::add(double val) {
-    if (mCount < kHistorySize) {
-        mValues[mCount++] = val;
-        mSum += val;
-    } else {
-        mSum -= mValues[0];
-        memmove(&mValues[0], &mValues[1], (kHistorySize - 1) * sizeof(double));
-        mValues[kHistorySize - 1] = val;
-        mSum += val;
-    }
-}
-
-double TimeSeries::mean() const {
-    if (mCount < 1) {
-        return 0.0;
-    }
-
-    return mSum / mCount;
-}
-
-double TimeSeries::sdev() const {
-    if (mCount < 1) {
-        return 0.0;
-    }
-
-    double m = mean();
-
-    double sum = 0.0;
-    for (size_t i = 0; i < mCount; ++i) {
-        double tmp = mValues[i] - m;
-        tmp *= tmp;
-
-        sum += tmp;
-    }
-
-    return sqrt(sum / mCount);
-}
-#endif  // DEBUG_JITTER
-
-////////////////////////////////////////////////////////////////////////////////
-
 static size_t kMaxRTPPacketSize = 1500;
 static size_t kMaxNumTSPacketsPerRTPPacket = (kMaxRTPPacketSize - 12) / 188;
 
@@ -110,14 +40,13 @@
         const sp<AMessage> &notify)
     : mNetSession(netSession),
       mNotify(notify),
-      mTSQueue(new ABuffer(12 + kMaxNumTSPacketsPerRTPPacket * 188)),
       mTransportMode(TRANSPORT_UDP),
       mRTPChannel(0),
       mRTCPChannel(0),
       mRTPPort(0),
       mRTPSessionID(0),
       mRTCPSessionID(0),
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
       mRTPRetransmissionSessionID(0),
       mRTCPRetransmissionSessionID(0),
 #endif
@@ -128,7 +57,7 @@
       mFirstOutputBufferReadyTimeUs(-1ll),
       mFirstOutputBufferSentTimeUs(-1ll),
       mRTPSeqNo(0),
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
       mRTPRetransmissionSeqNo(0),
 #endif
       mLastNTPTime(0),
@@ -148,15 +77,13 @@
     ,mLogFile(NULL)
 #endif
 {
-    mTSQueue->setRange(0, 12);
-
 #if LOG_TRANSPORT_STREAM
     mLogFile = fopen("/system/etc/log.ts", "wb");
 #endif
 }
 
 Sender::~Sender() {
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
     if (mRTCPRetransmissionSessionID != 0) {
         mNetSession->destroySession(mRTCPRetransmissionSessionID);
     }
@@ -217,7 +144,7 @@
     sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id());
     sp<AMessage> rtcpNotify = new AMessage(kWhatRTCPNotify, id());
 
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
     sp<AMessage> rtpRetransmissionNotify =
         new AMessage(kWhatRTPRetransmissionNotify, id());
 
@@ -264,7 +191,7 @@
             }
         }
 
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
         if (mTransportMode == TRANSPORT_UDP) {
             int32_t rtpRetransmissionSession;
 
@@ -358,44 +285,67 @@
 }
 
 void Sender::queuePackets(
-        int64_t timeUs, const sp<ABuffer> &packets) {
-    bool isVideo = false;
+        int64_t timeUs, const sp<ABuffer> &tsPackets) {
+    const size_t numTSPackets = tsPackets->size() / 188;
 
-    int32_t dummy;
-    if (packets->meta()->findInt32("isVideo", &dummy)) {
-        isVideo = true;
+    const size_t numRTPPackets =
+        (numTSPackets + kMaxNumTSPacketsPerRTPPacket - 1)
+            / kMaxNumTSPacketsPerRTPPacket;
+
+    sp<ABuffer> udpPackets = new ABuffer(
+            numRTPPackets * (12 + kMaxNumTSPacketsPerRTPPacket * 188));
+
+    udpPackets->meta()->setInt64("timeUs", timeUs);
+
+    size_t dstOffset = 0;
+    for (size_t i = 0; i < numTSPackets; ++i) {
+        if ((i % kMaxNumTSPacketsPerRTPPacket) == 0) {
+            static const bool kMarkerBit = false;
+
+            uint8_t *rtp = udpPackets->data() + dstOffset;
+            rtp[0] = 0x80;
+            rtp[1] = 33 | (kMarkerBit ? (1 << 7) : 0);  // M-bit
+            rtp[2] = (mRTPSeqNo >> 8) & 0xff;
+            rtp[3] = mRTPSeqNo & 0xff;
+            rtp[4] = 0x00;  // rtp time to be filled in later.
+            rtp[5] = 0x00;
+            rtp[6] = 0x00;
+            rtp[7] = 0x00;
+            rtp[8] = kSourceID >> 24;
+            rtp[9] = (kSourceID >> 16) & 0xff;
+            rtp[10] = (kSourceID >> 8) & 0xff;
+            rtp[11] = kSourceID & 0xff;
+
+            ++mRTPSeqNo;
+
+            dstOffset += 12;
+        }
+
+        memcpy(udpPackets->data() + dstOffset,
+               tsPackets->data() + 188 * i,
+               188);
+
+        dstOffset += 188;
     }
 
-    int64_t delayUs;
-    int64_t whenUs;
+    udpPackets->setRange(0, dstOffset);
 
-    if (mFirstOutputBufferReadyTimeUs < 0ll) {
-        mFirstOutputBufferReadyTimeUs = timeUs;
-        mFirstOutputBufferSentTimeUs = whenUs = ALooper::GetNowUs();
-        delayUs = 0ll;
-    } else {
-        int64_t nowUs = ALooper::GetNowUs();
+    sp<AMessage> msg = new AMessage(kWhatDrainQueue, id());
+    msg->setBuffer("udpPackets", udpPackets);
+    msg->post();
 
-        whenUs = (timeUs - mFirstOutputBufferReadyTimeUs)
-                + mFirstOutputBufferSentTimeUs;
-
-        delayUs = whenUs - nowUs;
+#if LOG_TRANSPORT_STREAM
+    if (mLogFile != NULL) {
+        fwrite(tsPackets->data(), 1, tsPackets->size(), mLogFile);
     }
-
-    sp<AMessage> msg = new AMessage(kWhatQueuePackets, id());
-    msg->setBuffer("packets", packets);
-
-    packets->meta()->setInt64("timeUs", timeUs);
-    packets->meta()->setInt64("whenUs", whenUs);
-    packets->meta()->setInt64("delayUs", delayUs);
-    msg->post(delayUs > 0 ? delayUs : 0);
+#endif
 }
 
 void Sender::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatRTPNotify:
         case kWhatRTCPNotify:
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
         case kWhatRTPRetransmissionNotify:
         case kWhatRTCPRetransmissionNotify:
 #endif
@@ -419,7 +369,7 @@
                     CHECK(msg->findString("detail", &detail));
 
                     if ((msg->what() == kWhatRTPNotify
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
                             || msg->what() == kWhatRTPRetransmissionNotify
 #endif
                         ) && !errorOccuredDuringSend) {
@@ -443,7 +393,7 @@
                     } else if (sessionID == mRTCPSessionID) {
                         mRTCPSessionID = 0;
                     }
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
                     else if (sessionID == mRTPRetransmissionSessionID) {
                         mRTPRetransmissionSessionID = 0;
                     } else if (sessionID == mRTCPRetransmissionSessionID) {
@@ -465,7 +415,7 @@
 
                     status_t err;
                     if (msg->what() == kWhatRTCPNotify
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
                             || msg->what() == kWhatRTCPRetransmissionNotify
 #endif
                        )
@@ -507,12 +457,12 @@
             break;
         }
 
-        case kWhatQueuePackets:
+        case kWhatDrainQueue:
         {
-            sp<ABuffer> packets;
-            CHECK(msg->findBuffer("packets", &packets));
+            sp<ABuffer> udpPackets;
+            CHECK(msg->findBuffer("udpPackets", &udpPackets));
 
-            onQueuePackets(packets);
+            onDrainQueue(udpPackets);
             break;
         }
 
@@ -532,156 +482,6 @@
     }
 }
 
-void Sender::onQueuePackets(const sp<ABuffer> &packets) {
-#if DEBUG_JITTER
-    int32_t dummy;
-    if (packets->meta()->findInt32("isVideo", &dummy)) {
-        static int64_t lastTimeUs = 0ll;
-        int64_t nowUs = ALooper::GetNowUs();
-
-        static TimeSeries series;
-        series.add((double)(nowUs - lastTimeUs));
-
-        ALOGI("deltaTimeUs = %lld us, mean %.2f, sdev %.2f",
-              nowUs - lastTimeUs, series.mean(), series.sdev());
-
-        lastTimeUs = nowUs;
-    }
-#endif
-
-    int64_t startTimeUs = ALooper::GetNowUs();
-
-    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 */);
-    }
-
-#if 0
-    int64_t netTimeUs = ALooper::GetNowUs() - startTimeUs;
-
-    int64_t whenUs;
-    CHECK(packets->meta()->findInt64("whenUs", &whenUs));
-
-    int64_t delayUs;
-    CHECK(packets->meta()->findInt64("delayUs", &delayUs));
-
-    bool isVideo = false;
-    int32_t dummy;
-    if (packets->meta()->findInt32("isVideo", &dummy)) {
-        isVideo = true;
-    }
-
-    int64_t nowUs = ALooper::GetNowUs();
-
-    if (nowUs - whenUs > 2000) {
-        ALOGI("[%s] delayUs = %lld us, delta = %lld us",
-              isVideo ? "video" : "audio", delayUs, nowUs - netTimeUs - whenUs);
-    }
-#endif
-
-#if LOG_TRANSPORT_STREAM
-    if (mLogFile != NULL) {
-        fwrite(packets->data(), 1, packets->size(), mLogFile);
-    }
-#endif
-}
-
-ssize_t Sender::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 TRACK_BANDWIDTH
-        if (mFirstPacketTimeUs < 0ll) {
-            mFirstPacketTimeUs = nowUs;
-        }
-#endif
-
-        // 90kHz time scale
-        uint32_t rtpTime = (nowUs * 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 (mTransportMode == TRANSPORT_TCP_INTERLEAVED) {
-            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 {
-            sendPacket(mRTPSessionID, rtp, mTSQueue->size());
-
-#if TRACK_BANDWIDTH
-            mTotalBytesSent += mTSQueue->size();
-            int64_t delayUs = ALooper::GetNowUs() - mFirstPacketTimeUs;
-
-            if (delayUs > 0ll) {
-                ALOGI("approx. net bandwidth used: %.2f Mbit/sec",
-                        mTotalBytesSent * 8.0 / delayUs);
-            }
-#endif
-        }
-
-#if ENABLE_RETRANSMISSION
-        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);
-        }
-#endif
-
-        mTSQueue->setRange(0, 12);
-    }
-
-    return size;
-}
-
 void Sender::scheduleSendSR() {
     if (mSendSRPending || mRTCPSessionID == 0) {
         return;
@@ -851,6 +651,7 @@
             if (retransmit) {
                 ALOGI("retransmitting seqNo %d", bufferSeqNo);
 
+#if RETRANSMISSION_ACCORDING_TO_RFC_XXXX
                 sp<ABuffer> retransRTP = new ABuffer(2 + buffer->size());
                 uint8_t *rtp = retransRTP->data();
                 memcpy(rtp, buffer->data(), 12);
@@ -865,6 +666,10 @@
                 sendPacket(
                         mRTPRetransmissionSessionID,
                         retransRTP->data(), retransRTP->size());
+#else
+                sendPacket(
+                        mRTPSessionID, buffer->data(), buffer->size());
+#endif
 
                 if (bufferSeqNo == seqNo) {
                     foundSeqNo = true;
@@ -975,5 +780,91 @@
     notify->post();
 }
 
+void Sender::onDrainQueue(const sp<ABuffer> &udpPackets) {
+    static const size_t kFullRTPPacketSize =
+        12 + 188 * kMaxNumTSPacketsPerRTPPacket;
+
+    size_t srcOffset = 0;
+    while (srcOffset < udpPackets->size()) {
+        uint8_t *rtp = udpPackets->data() + srcOffset;
+
+        size_t rtpPacketSize = udpPackets->size() - srcOffset;
+        if (rtpPacketSize > kFullRTPPacketSize) {
+            rtpPacketSize = kFullRTPPacketSize;
+        }
+
+        int64_t nowUs = ALooper::GetNowUs();
+        mLastNTPTime = GetNowNTP();
+
+        // 90kHz time scale
+        uint32_t rtpTime = (nowUs * 9ll) / 100ll;
+
+        rtp[4] = rtpTime >> 24;
+        rtp[5] = (rtpTime >> 16) & 0xff;
+        rtp[6] = (rtpTime >> 8) & 0xff;
+        rtp[7] = rtpTime & 0xff;
+
+        ++mNumRTPSent;
+        mNumRTPOctetsSent += rtpPacketSize - 12;
+
+        mLastRTPTime = rtpTime;
+
+        if (mTransportMode == TRANSPORT_TCP_INTERLEAVED) {
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatBinaryData);
+
+            sp<ABuffer> data = new ABuffer(rtpPacketSize);
+            memcpy(data->data(), rtp, rtpPacketSize);
+
+            notify->setInt32("channel", mRTPChannel);
+            notify->setBuffer("data", data);
+            notify->post();
+        } else {
+            sendPacket(mRTPSessionID, rtp, rtpPacketSize);
+
+#if TRACK_BANDWIDTH
+            mTotalBytesSent += rtpPacketSize->size();
+            int64_t delayUs = ALooper::GetNowUs() - mFirstPacketTimeUs;
+
+            if (delayUs > 0ll) {
+                ALOGI("approx. net bandwidth used: %.2f Mbit/sec",
+                        mTotalBytesSent * 8.0 / delayUs);
+            }
+#endif
+        }
+
+#if ENABLE_RETRANSMISSION
+        addToHistory(rtp, rtpPacketSize);
+#endif
+
+        srcOffset += rtpPacketSize;
+    }
+
+#if 0
+    int64_t timeUs;
+    CHECK(udpPackets->meta()->findInt64("timeUs", &timeUs));
+
+    ALOGI("dTimeUs = %lld us", ALooper::GetNowUs() - timeUs);
+#endif
+}
+
+#if ENABLE_RETRANSMISSION
+void Sender::addToHistory(const uint8_t *rtp, size_t rtpPacketSize) {
+    sp<ABuffer> packet = new ABuffer(rtpPacketSize);
+    memcpy(packet->data(), rtp, rtpPacketSize);
+
+    unsigned rtpSeqNo = U16_AT(&rtp[2]);
+    packet->setInt32Data(rtpSeqNo);
+
+    mHistory.push_back(packet);
+    ++mHistoryLength;
+
+    if (mHistoryLength > kMaxHistoryLength) {
+        mHistory.erase(mHistory.begin());
+        --mHistoryLength;
+    }
+}
+#endif
+
 }  // namespace android
 
diff --git a/media/libstagefright/wifi-display/source/Sender.h b/media/libstagefright/wifi-display/source/Sender.h
index e476e84..73e3d19 100644
--- a/media/libstagefright/wifi-display/source/Sender.h
+++ b/media/libstagefright/wifi-display/source/Sender.h
@@ -23,9 +23,17 @@
 namespace android {
 
 #define LOG_TRANSPORT_STREAM            0
-#define ENABLE_RETRANSMISSION           0
 #define TRACK_BANDWIDTH                 0
 
+#define ENABLE_RETRANSMISSION                   0
+
+// If retransmission is enabled the following define determines what
+// kind we support, if RETRANSMISSION_ACCORDING_TO_RFC_XXXX is 0
+// we'll send NACKs on the original RTCP channel and retransmit packets
+// on the original RTP channel, otherwise a separate channel pair is used
+// for this purpose.
+#define RETRANSMISSION_ACCORDING_TO_RFC_XXXX    0
+
 struct ABuffer;
 struct ANetworkSession;
 
@@ -51,7 +59,7 @@
 
     int32_t getRTPPort() const;
 
-    void queuePackets(int64_t timeUs, const sp<ABuffer> &packets);
+    void queuePackets(int64_t timeUs, const sp<ABuffer> &tsPackets);
     void scheduleSendSR();
 
 protected:
@@ -60,13 +68,13 @@
 
 private:
     enum {
-        kWhatQueuePackets,
+        kWhatDrainQueue,
         kWhatSendSR,
         kWhatRTPNotify,
         kWhatRTCPNotify,
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
         kWhatRTPRetransmissionNotify,
-        kWhatRTCPRetransmissionNotify
+        kWhatRTCPRetransmissionNotify,
 #endif
     };
 
@@ -75,15 +83,13 @@
     static const uint32_t kSourceID = 0xdeadbeef;
     static const size_t kMaxHistoryLength = 128;
 
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
     static const size_t kRetransmissionPortOffset = 120;
 #endif
 
     sp<ANetworkSession> mNetSession;
     sp<AMessage> mNotify;
 
-    sp<ABuffer> mTSQueue;
-
     TransportMode mTransportMode;
     AString mClientIP;
 
@@ -96,7 +102,7 @@
     int32_t mRTPSessionID;
     int32_t mRTCPSessionID;
 
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
     int32_t mRTPRetransmissionSessionID;
     int32_t mRTCPRetransmissionSessionID;
 #endif
@@ -106,12 +112,11 @@
     bool mRTPConnected;
     bool mRTCPConnected;
 
-
     int64_t mFirstOutputBufferReadyTimeUs;
     int64_t mFirstOutputBufferSentTimeUs;
 
     uint32_t mRTPSeqNo;
-#if ENABLE_RETRANSMISSION
+#if ENABLE_RETRANSMISSION && RETRANSMISSION_ACCORDING_TO_RFC_XXXX
     uint32_t mRTPRetransmissionSeqNo;
 #endif
 
@@ -133,22 +138,18 @@
     uint64_t mTotalBytesSent;
 #endif
 
+#if LOG_TRANSPORT_STREAM
+    FILE *mLogFile;
+#endif
+
     void onSendSR();
     void addSR(const sp<ABuffer> &buffer);
     void addSDES(const sp<ABuffer> &buffer);
     static uint64_t GetNowNTP();
 
-#if LOG_TRANSPORT_STREAM
-    FILE *mLogFile;
-#endif
-
-    ssize_t appendTSData(
-            const void *data, size_t size, bool timeDiscontinuity, bool flush);
-
-    void onQueuePackets(const sp<ABuffer> &packets);
-
 #if ENABLE_RETRANSMISSION
     status_t parseTSFB(const uint8_t *data, size_t size);
+    void addToHistory(const uint8_t *rtp, size_t rtpPacketSize);
 #endif
 
     status_t parseRTCP(const sp<ABuffer> &buffer);
@@ -158,6 +159,8 @@
     void notifyInitDone();
     void notifySessionDead();
 
+    void onDrainQueue(const sp<ABuffer> &udpPackets);
+
     DISALLOW_EVIL_CONSTRUCTORS(Sender);
 };
 
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
index a5679ad..ef57a4d 100644
--- a/media/libstagefright/wifi-display/source/TSPacketizer.cpp
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
@@ -314,6 +314,25 @@
         mDescriptors.push_back(descriptor);
     }
 
+    int32_t hdcpVersion;
+    if (mFormat->findInt32("hdcp-version", &hdcpVersion)) {
+        // HDCP descriptor
+
+        CHECK(hdcpVersion == 0x20 || hdcpVersion == 0x21);
+
+        sp<ABuffer> descriptor = new ABuffer(7);
+        uint8_t *data = descriptor->data();
+        data[0] = 0x05;  // descriptor_tag
+        data[1] = 5;  // descriptor_length
+        data[2] = 'H';
+        data[3] = 'D';
+        data[4] = 'C';
+        data[5] = 'P';
+        data[6] = hdcpVersion;
+
+        mDescriptors.push_back(descriptor);
+    }
+
     mFinalized = true;
 }
 
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
index b16c5d0..78d6e62 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -534,9 +534,15 @@
     //   use "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n"
     // For 720p24:
     //   use "78 00 02 02 00008000 00000000 00000000 00 0000 0000 00 none none\r\n"
+    // For 1080p30:
+    //   use "38 00 02 02 00000080 00000000 00000000 00 0000 0000 00 none none\r\n"
     AString body = StringPrintf(
         "wfd_video_formats: "
+#if USE_1080P
+        "38 00 02 02 00000080 00000000 00000000 00 0000 0000 00 none none\r\n"
+#else
         "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n"
+#endif
         "wfd_audio_codecs: %s\r\n"
         "wfd_presentation_URL: rtsp://%s/wfd1.0/streamid=0 none\r\n"
         "wfd_client_rtp_ports: RTP/AVP/%s;unicast %d 0 mode=play\r\n",
@@ -773,8 +779,10 @@
 
         status_t err = makeHDCP();
         if (err != OK) {
-            ALOGE("Unable to instantiate HDCP component.");
-            return err;
+            ALOGE("Unable to instantiate HDCP component. "
+                  "Not using HDCP after all.");
+
+            mUsingHDCP = false;
         }
     }
 
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
index 02fa0a6..1e855e7 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -26,6 +26,8 @@
 
 namespace android {
 
+#define USE_1080P       0
+
 struct IHDCP;
 struct IRemoteDisplayClient;
 struct ParsedMessage;
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
index 011edab..03a1123 100644
--- a/media/libstagefright/wifi-display/wfd.cpp
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -23,22 +23,163 @@
 
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
+#include <gui/SurfaceComposerClient.h>
+#include <media/AudioSystem.h>
 #include <media/IMediaPlayerService.h>
+#include <media/IRemoteDisplay.h>
+#include <media/IRemoteDisplayClient.h>
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/foundation/ADebug.h>
 
 namespace android {
 
-}  // namespace android
-
 static void usage(const char *me) {
     fprintf(stderr,
             "usage:\n"
             "           %s -c host[:port]\tconnect to wifi source\n"
-            "           -u uri        \tconnect to an rtsp uri\n",
+            "               -u uri        \tconnect to an rtsp uri\n"
+            "               -l ip[:port] \tlisten on the specified port "
+            "(create a sink)\n",
             me);
 }
 
+struct RemoteDisplayClient : public BnRemoteDisplayClient {
+    RemoteDisplayClient();
+
+    virtual void onDisplayConnected(
+            const sp<ISurfaceTexture> &surfaceTexture,
+            uint32_t width,
+            uint32_t height,
+            uint32_t flags);
+
+    virtual void onDisplayDisconnected();
+    virtual void onDisplayError(int32_t error);
+
+    void waitUntilDone();
+
+protected:
+    virtual ~RemoteDisplayClient();
+
+private:
+    Mutex mLock;
+    Condition mCondition;
+
+    bool mDone;
+
+    sp<SurfaceComposerClient> mComposerClient;
+    sp<ISurfaceTexture> mSurfaceTexture;
+    sp<IBinder> mDisplayBinder;
+
+    DISALLOW_EVIL_CONSTRUCTORS(RemoteDisplayClient);
+};
+
+RemoteDisplayClient::RemoteDisplayClient()
+    : mDone(false) {
+    mComposerClient = new SurfaceComposerClient;
+    CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
+}
+
+RemoteDisplayClient::~RemoteDisplayClient() {
+}
+
+void RemoteDisplayClient::onDisplayConnected(
+        const sp<ISurfaceTexture> &surfaceTexture,
+        uint32_t width,
+        uint32_t height,
+        uint32_t flags) {
+    ALOGI("onDisplayConnected width=%u, height=%u, flags = 0x%08x",
+          width, height, flags);
+
+    mSurfaceTexture = surfaceTexture;
+    mDisplayBinder = mComposerClient->createDisplay(
+            String8("foo"), false /* secure */);
+
+    SurfaceComposerClient::openGlobalTransaction();
+    mComposerClient->setDisplaySurface(mDisplayBinder, mSurfaceTexture);
+
+    Rect layerStackRect(1280, 720);  // XXX fix this.
+    Rect displayRect(1280, 720);
+
+    mComposerClient->setDisplayProjection(
+            mDisplayBinder, 0 /* 0 degree rotation */,
+            layerStackRect,
+            displayRect);
+
+    SurfaceComposerClient::closeGlobalTransaction();
+}
+
+void RemoteDisplayClient::onDisplayDisconnected() {
+    ALOGI("onDisplayDisconnected");
+
+    Mutex::Autolock autoLock(mLock);
+    mDone = true;
+    mCondition.broadcast();
+}
+
+void RemoteDisplayClient::onDisplayError(int32_t error) {
+    ALOGI("onDisplayError error=%d", error);
+
+    Mutex::Autolock autoLock(mLock);
+    mDone = true;
+    mCondition.broadcast();
+}
+
+void RemoteDisplayClient::waitUntilDone() {
+    Mutex::Autolock autoLock(mLock);
+    while (!mDone) {
+        mCondition.wait(mLock);
+    }
+}
+
+static status_t enableAudioSubmix(bool enable) {
+    status_t err = AudioSystem::setDeviceConnectionState(
+            AUDIO_DEVICE_IN_REMOTE_SUBMIX,
+            enable
+                ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE
+                : AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+            NULL /* device_address */);
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = AudioSystem::setDeviceConnectionState(
+            AUDIO_DEVICE_OUT_REMOTE_SUBMIX,
+            enable
+                ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE
+                : AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+            NULL /* device_address */);
+
+    return err;
+}
+
+static void createSource(const AString &addr, int32_t port) {
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binder = sm->getService(String16("media.player"));
+    sp<IMediaPlayerService> service =
+        interface_cast<IMediaPlayerService>(binder);
+
+    CHECK(service.get() != NULL);
+
+    enableAudioSubmix(true /* enable */);
+
+    String8 iface;
+    iface.append(addr.c_str());
+    iface.append(StringPrintf(":%d", port).c_str());
+
+    sp<RemoteDisplayClient> client = new RemoteDisplayClient;
+    sp<IRemoteDisplay> display = service->listenForRemoteDisplay(client, iface);
+
+    client->waitUntilDone();
+
+    display->dispose();
+    display.clear();
+
+    enableAudioSubmix(false /* enable */);
+}
+
+}  // namespace android
+
 int main(int argc, char **argv) {
     using namespace android;
 
@@ -50,6 +191,9 @@
     int32_t connectToPort = -1;
     AString uri;
 
+    AString listenOnAddr;
+    int32_t listenOnPort = -1;
+
     int res;
     while ((res = getopt(argc, argv, "hc:l:u:")) >= 0) {
         switch (res) {
@@ -81,6 +225,28 @@
                 break;
             }
 
+            case 'l':
+            {
+                const char *colonPos = strrchr(optarg, ':');
+
+                if (colonPos == NULL) {
+                    listenOnAddr = optarg;
+                    listenOnPort = WifiDisplaySource::kWifiDisplayDefaultPort;
+                } else {
+                    listenOnAddr.setTo(optarg, colonPos - optarg);
+
+                    char *end;
+                    listenOnPort = strtol(colonPos + 1, &end, 10);
+
+                    if (*end != '\0' || end == colonPos + 1
+                            || listenOnPort < 1 || listenOnPort > 65535) {
+                        fprintf(stderr, "Illegal port specified.\n");
+                        exit(1);
+                    }
+                }
+                break;
+            }
+
             case '?':
             case 'h':
             default:
@@ -89,6 +255,18 @@
         }
     }
 
+    if (connectToPort >= 0 && listenOnPort >= 0) {
+        fprintf(stderr,
+                "You can connect to a source or create one, "
+                "but not both at the same time.\n");
+        exit(1);
+    }
+
+    if (listenOnPort >= 0) {
+        createSource(listenOnAddr, listenOnPort);
+        exit(0);
+    }
+
     if (connectToPort < 0 && uri.empty()) {
         fprintf(stderr,
                 "You need to select either source host or uri.\n");