Support for acting as a wifi display sink.

Change-Id: I0beac87025b93c60164daa865c89f16b72197a47
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index a02732b..91aaafe 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -293,8 +293,8 @@
                 break;
             }
 
-            if (mAudioDecoder == NULL && mAudioSink != NULL ||
-                mVideoDecoder == NULL && mNativeWindow != NULL) {
+            if ((mAudioDecoder == NULL && mAudioSink != NULL)
+                    || (mVideoDecoder == NULL && mNativeWindow != NULL)) {
                 msg->post(100000ll);
                 mScanSourcesPending = true;
             }
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index b035a51..0e59b9e 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -5,6 +5,10 @@
 LOCAL_SRC_FILES:= \
         ANetworkSession.cpp             \
         ParsedMessage.cpp               \
+        sink/LinearRegression.cpp       \
+        sink/RTPSink.cpp                \
+        sink/TunnelRenderer.cpp         \
+        sink/WifiDisplaySink.cpp        \
         source/Converter.cpp            \
         source/PlaybackSession.cpp      \
         source/RepeaterSource.cpp       \
diff --git a/media/libstagefright/wifi-display/sink/LinearRegression.cpp b/media/libstagefright/wifi-display/sink/LinearRegression.cpp
new file mode 100644
index 0000000..8cfce37
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/LinearRegression.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "LinearRegression"
+#include <utils/Log.h>
+
+#include "LinearRegression.h"
+
+#include <math.h>
+#include <string.h>
+
+namespace android {
+
+LinearRegression::LinearRegression(size_t historySize)
+    : mHistorySize(historySize),
+      mCount(0),
+      mHistory(new Point[mHistorySize]),
+      mSumX(0.0),
+      mSumY(0.0) {
+}
+
+LinearRegression::~LinearRegression() {
+    delete[] mHistory;
+    mHistory = NULL;
+}
+
+void LinearRegression::addPoint(float x, float y) {
+    if (mCount == mHistorySize) {
+        const Point &oldest = mHistory[0];
+
+        mSumX -= oldest.mX;
+        mSumY -= oldest.mY;
+
+        memmove(&mHistory[0], &mHistory[1], (mHistorySize - 1) * sizeof(Point));
+        --mCount;
+    }
+
+    Point *newest = &mHistory[mCount++];
+    newest->mX = x;
+    newest->mY = y;
+
+    mSumX += x;
+    mSumY += y;
+}
+
+bool LinearRegression::approxLine(float *n1, float *n2, float *b) const {
+    static const float kEpsilon = 1.0E-4;
+
+    if (mCount < 2) {
+        return false;
+    }
+
+    float sumX2 = 0.0f;
+    float sumY2 = 0.0f;
+    float sumXY = 0.0f;
+
+    float meanX = mSumX / (float)mCount;
+    float meanY = mSumY / (float)mCount;
+
+    for (size_t i = 0; i < mCount; ++i) {
+        const Point &p = mHistory[i];
+
+        float x = p.mX - meanX;
+        float y = p.mY - meanY;
+
+        sumX2 += x * x;
+        sumY2 += y * y;
+        sumXY += x * y;
+    }
+
+    float T = sumX2 + sumY2;
+    float D = sumX2 * sumY2 - sumXY * sumXY;
+    float root = sqrt(T * T * 0.25 - D);
+
+    float L1 = T * 0.5 - root;
+
+    if (fabs(sumXY) > kEpsilon) {
+        *n1 = 1.0;
+        *n2 = (2.0 * L1 - sumX2) / sumXY;
+
+        float mag = sqrt((*n1) * (*n1) + (*n2) * (*n2));
+
+        *n1 /= mag;
+        *n2 /= mag;
+    } else {
+        *n1 = 0.0;
+        *n2 = 1.0;
+    }
+
+    *b = (*n1) * meanX + (*n2) * meanY;
+
+    return true;
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/wifi-display/sink/LinearRegression.h b/media/libstagefright/wifi-display/sink/LinearRegression.h
new file mode 100644
index 0000000..ca6f5a1
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/LinearRegression.h
@@ -0,0 +1,52 @@
+/*
+ * 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 LINEAR_REGRESSION_H_
+
+#define LINEAR_REGRESSION_H_
+
+#include <sys/types.h>
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+// Helper class to fit a line to a set of points minimizing the sum of
+// squared (orthogonal) distances from line to individual points.
+struct LinearRegression {
+    LinearRegression(size_t historySize);
+    ~LinearRegression();
+
+    void addPoint(float x, float y);
+
+    bool approxLine(float *n1, float *n2, float *b) const;
+
+private:
+    struct Point {
+        float mX, mY;
+    };
+
+    size_t mHistorySize;
+    size_t mCount;
+    Point *mHistory;
+
+    float mSumX, mSumY;
+
+    DISALLOW_EVIL_CONSTRUCTORS(LinearRegression);
+};
+
+}  // namespace android
+
+#endif  // LINEAR_REGRESSION_H_
diff --git a/media/libstagefright/wifi-display/sink/RTPSink.cpp b/media/libstagefright/wifi-display/sink/RTPSink.cpp
new file mode 100644
index 0000000..0918034
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/RTPSink.cpp
@@ -0,0 +1,806 @@
+/*
+ * 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 "RTPSink"
+#include <utils/Log.h>
+
+#include "RTPSink.h"
+
+#include "ANetworkSession.h"
+#include "TunnelRenderer.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/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+struct RTPSink::Source : public RefBase {
+    Source(uint16_t seq, const sp<ABuffer> &buffer,
+           const sp<AMessage> queueBufferMsg);
+
+    bool updateSeq(uint16_t seq, const sp<ABuffer> &buffer);
+
+    void addReportBlock(uint32_t ssrc, const sp<ABuffer> &buf);
+
+protected:
+    virtual ~Source();
+
+private:
+    static const uint32_t kMinSequential = 2;
+    static const uint32_t kMaxDropout = 3000;
+    static const uint32_t kMaxMisorder = 100;
+    static const uint32_t kRTPSeqMod = 1u << 16;
+
+    sp<AMessage> mQueueBufferMsg;
+
+    uint16_t mMaxSeq;
+    uint32_t mCycles;
+    uint32_t mBaseSeq;
+    uint32_t mBadSeq;
+    uint32_t mProbation;
+    uint32_t mReceived;
+    uint32_t mExpectedPrior;
+    uint32_t mReceivedPrior;
+
+    void initSeq(uint16_t seq);
+    void queuePacket(const sp<ABuffer> &buffer);
+
+    DISALLOW_EVIL_CONSTRUCTORS(Source);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPSink::Source::Source(
+        uint16_t seq, const sp<ABuffer> &buffer,
+        const sp<AMessage> queueBufferMsg)
+    : mQueueBufferMsg(queueBufferMsg),
+      mProbation(kMinSequential) {
+    initSeq(seq);
+    mMaxSeq = seq - 1;
+
+    buffer->setInt32Data(mCycles | seq);
+    queuePacket(buffer);
+}
+
+RTPSink::Source::~Source() {
+}
+
+void RTPSink::Source::initSeq(uint16_t seq) {
+    mMaxSeq = seq;
+    mCycles = 0;
+    mBaseSeq = seq;
+    mBadSeq = kRTPSeqMod + 1;
+    mReceived = 0;
+    mExpectedPrior = 0;
+    mReceivedPrior = 0;
+}
+
+bool RTPSink::Source::updateSeq(uint16_t seq, const sp<ABuffer> &buffer) {
+    uint16_t udelta = seq - mMaxSeq;
+
+    if (mProbation) {
+        // Startup phase
+
+        if (seq == mMaxSeq + 1) {
+            buffer->setInt32Data(mCycles | seq);
+            queuePacket(buffer);
+
+            --mProbation;
+            mMaxSeq = seq;
+            if (mProbation == 0) {
+                initSeq(seq);
+                ++mReceived;
+
+                return true;
+            }
+        } else {
+            // Packet out of sequence, restart startup phase
+
+            mProbation = kMinSequential - 1;
+            mMaxSeq = seq;
+
+#if 0
+            mPackets.clear();
+            mTotalBytesQueued = 0;
+            ALOGI("XXX cleared packets");
+#endif
+
+            buffer->setInt32Data(mCycles | seq);
+            queuePacket(buffer);
+        }
+
+        return false;
+    }
+
+    if (udelta < kMaxDropout) {
+        // In order, with permissible gap.
+
+        if (seq < mMaxSeq) {
+            // Sequence number wrapped - count another 64K cycle
+            mCycles += kRTPSeqMod;
+        }
+
+        mMaxSeq = seq;
+    } else if (udelta <= kRTPSeqMod - kMaxMisorder) {
+        // The sequence number made a very large jump
+
+        if (seq == mBadSeq) {
+            // Two sequential packets -- assume that the other side
+            // restarted without telling us so just re-sync
+            // (i.e. pretend this was the first packet)
+
+            initSeq(seq);
+        } else {
+            mBadSeq = (seq + 1) & (kRTPSeqMod - 1);
+
+            return false;
+        }
+    } else {
+        // Duplicate or reordered packet.
+    }
+
+    ++mReceived;
+
+    buffer->setInt32Data(mCycles | seq);
+    queuePacket(buffer);
+
+    return true;
+}
+
+void RTPSink::Source::queuePacket(const sp<ABuffer> &buffer) {
+    sp<AMessage> msg = mQueueBufferMsg->dup();
+    msg->setBuffer("buffer", buffer);
+    msg->post();
+}
+
+void RTPSink::Source::addReportBlock(
+        uint32_t ssrc, const sp<ABuffer> &buf) {
+    uint32_t extMaxSeq = mMaxSeq | mCycles;
+    uint32_t expected = extMaxSeq - mBaseSeq + 1;
+
+    int64_t lost = (int64_t)expected - (int64_t)mReceived;
+    if (lost > 0x7fffff) {
+        lost = 0x7fffff;
+    } else if (lost < -0x800000) {
+        lost = -0x800000;
+    }
+
+    uint32_t expectedInterval = expected - mExpectedPrior;
+    mExpectedPrior = expected;
+
+    uint32_t receivedInterval = mReceived - mReceivedPrior;
+    mReceivedPrior = mReceived;
+
+    int64_t lostInterval = expectedInterval - receivedInterval;
+
+    uint8_t fractionLost;
+    if (expectedInterval == 0 || lostInterval <=0) {
+        fractionLost = 0;
+    } else {
+        fractionLost = (lostInterval << 8) / expectedInterval;
+    }
+
+    uint8_t *ptr = buf->data() + buf->size();
+
+    ptr[0] = ssrc >> 24;
+    ptr[1] = (ssrc >> 16) & 0xff;
+    ptr[2] = (ssrc >> 8) & 0xff;
+    ptr[3] = ssrc & 0xff;
+
+    ptr[4] = fractionLost;
+
+    ptr[5] = (lost >> 16) & 0xff;
+    ptr[6] = (lost >> 8) & 0xff;
+    ptr[7] = lost & 0xff;
+
+    ptr[8] = extMaxSeq >> 24;
+    ptr[9] = (extMaxSeq >> 16) & 0xff;
+    ptr[10] = (extMaxSeq >> 8) & 0xff;
+    ptr[11] = extMaxSeq & 0xff;
+
+    // XXX TODO:
+
+    ptr[12] = 0x00;  // interarrival jitter
+    ptr[13] = 0x00;
+    ptr[14] = 0x00;
+    ptr[15] = 0x00;
+
+    ptr[16] = 0x00;  // last SR
+    ptr[17] = 0x00;
+    ptr[18] = 0x00;
+    ptr[19] = 0x00;
+
+    ptr[20] = 0x00;  // delay since last SR
+    ptr[21] = 0x00;
+    ptr[22] = 0x00;
+    ptr[23] = 0x00;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPSink::RTPSink(
+        const sp<ANetworkSession> &netSession,
+        const sp<ISurfaceTexture> &surfaceTex)
+    : mNetSession(netSession),
+      mSurfaceTex(surfaceTex),
+      mRTPPort(0),
+      mRTPSessionID(0),
+      mRTCPSessionID(0),
+      mFirstArrivalTimeUs(-1ll),
+      mNumPacketsReceived(0ll),
+      mRegression(1000),
+      mMaxDelayMs(-1ll) {
+}
+
+RTPSink::~RTPSink() {
+    if (mRTCPSessionID != 0) {
+        mNetSession->destroySession(mRTCPSessionID);
+    }
+
+    if (mRTPSessionID != 0) {
+        mNetSession->destroySession(mRTPSessionID);
+    }
+}
+
+status_t RTPSink::init(bool useTCPInterleaving) {
+    if (useTCPInterleaving) {
+        return OK;
+    }
+
+    int clientRtp;
+
+    sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id());
+    sp<AMessage> rtcpNotify = new AMessage(kWhatRTCPNotify, id());
+    for (clientRtp = 15550;; clientRtp += 2) {
+        int32_t rtpSession;
+        status_t err = mNetSession->createUDPSession(
+                    clientRtp, rtpNotify, &rtpSession);
+
+        if (err != OK) {
+            ALOGI("failed to create RTP socket on port %d", clientRtp);
+            continue;
+        }
+
+        int32_t rtcpSession;
+        err = mNetSession->createUDPSession(
+                clientRtp + 1, rtcpNotify, &rtcpSession);
+
+        if (err == OK) {
+            mRTPPort = clientRtp;
+            mRTPSessionID = rtpSession;
+            mRTCPSessionID = rtcpSession;
+            break;
+        }
+
+        ALOGI("failed to create RTCP socket on port %d", clientRtp + 1);
+        mNetSession->destroySession(rtpSession);
+    }
+
+    if (mRTPPort == 0) {
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+int32_t RTPSink::getRTPPort() const {
+    return mRTPPort;
+}
+
+void RTPSink::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));
+
+                    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);
+
+                    if (sessionID == mRTPSessionID) {
+                        mRTPSessionID = 0;
+                    } else if (sessionID == mRTCPSessionID) {
+                        mRTCPSessionID = 0;
+                    }
+                    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() == kWhatRTPNotify) {
+                        err = parseRTP(data);
+                    } else {
+                        err = parseRTCP(data);
+                    }
+                    break;
+                }
+
+                default:
+                    TRESPASS();
+            }
+            break;
+        }
+
+        case kWhatSendRR:
+        {
+            onSendRR();
+            break;
+        }
+
+        case kWhatPacketLost:
+        {
+            onPacketLost(msg);
+            break;
+        }
+
+        case kWhatInject:
+        {
+            int32_t isRTP;
+            CHECK(msg->findInt32("isRTP", &isRTP));
+
+            sp<ABuffer> buffer;
+            CHECK(msg->findBuffer("buffer", &buffer));
+
+            status_t err;
+            if (isRTP) {
+                err = parseRTP(buffer);
+            } else {
+                err = parseRTCP(buffer);
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+status_t RTPSink::injectPacket(bool isRTP, const sp<ABuffer> &buffer) {
+    sp<AMessage> msg = new AMessage(kWhatInject, id());
+    msg->setInt32("isRTP", isRTP);
+    msg->setBuffer("buffer", buffer);
+    msg->post();
+
+    return OK;
+}
+
+status_t RTPSink::parseRTP(const sp<ABuffer> &buffer) {
+    size_t size = buffer->size();
+    if (size < 12) {
+        // Too short to be a valid RTP header.
+        return ERROR_MALFORMED;
+    }
+
+    const uint8_t *data = buffer->data();
+
+    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;
+    }
+
+    int numCSRCs = data[0] & 0x0f;
+
+    size_t payloadOffset = 12 + 4 * numCSRCs;
+
+    if (size < payloadOffset) {
+        // Not enough data to fit the basic header and all the CSRC entries.
+        return ERROR_MALFORMED;
+    }
+
+    if (data[0] & 0x10) {
+        // Header eXtension present.
+
+        if (size < payloadOffset + 4) {
+            // Not enough data to fit the basic header, all CSRC entries
+            // and the first 4 bytes of the extension header.
+
+            return ERROR_MALFORMED;
+        }
+
+        const uint8_t *extensionData = &data[payloadOffset];
+
+        size_t extensionLength =
+            4 * (extensionData[2] << 8 | extensionData[3]);
+
+        if (size < payloadOffset + 4 + extensionLength) {
+            return ERROR_MALFORMED;
+        }
+
+        payloadOffset += 4 + extensionLength;
+    }
+
+    uint32_t srcId = U32_AT(&data[8]);
+    uint32_t rtpTime = U32_AT(&data[4]);
+    uint16_t seqNo = U16_AT(&data[2]);
+
+    int64_t arrivalTimeUs;
+    CHECK(buffer->meta()->findInt64("arrivalTimeUs", &arrivalTimeUs));
+
+    if (mFirstArrivalTimeUs < 0ll) {
+        mFirstArrivalTimeUs = arrivalTimeUs;
+    }
+    arrivalTimeUs -= mFirstArrivalTimeUs;
+
+    int64_t arrivalTimeMedia = (arrivalTimeUs * 9ll) / 100ll;
+
+    ALOGV("seqNo: %d, SSRC 0x%08x, diff %lld",
+            seqNo, srcId, rtpTime - arrivalTimeMedia);
+
+    mRegression.addPoint((float)rtpTime, (float)arrivalTimeMedia);
+
+    ++mNumPacketsReceived;
+
+    float n1, n2, b;
+    if (mRegression.approxLine(&n1, &n2, &b)) {
+        ALOGV("Line %lld: %.2f %.2f %.2f, slope %.2f",
+              mNumPacketsReceived, n1, n2, b, -n1 / n2);
+
+        float expectedArrivalTimeMedia = (b - n1 * (float)rtpTime) / n2;
+        float latenessMs = (arrivalTimeMedia - expectedArrivalTimeMedia) / 90.0;
+
+        if (mMaxDelayMs < 0ll || latenessMs > mMaxDelayMs) {
+            mMaxDelayMs = latenessMs;
+            ALOGI("packet was %.2f ms late", latenessMs);
+        }
+    }
+
+    sp<AMessage> meta = buffer->meta();
+    meta->setInt32("ssrc", srcId);
+    meta->setInt32("rtp-time", rtpTime);
+    meta->setInt32("PT", data[1] & 0x7f);
+    meta->setInt32("M", data[1] >> 7);
+
+    buffer->setRange(payloadOffset, size - payloadOffset);
+
+    ssize_t index = mSources.indexOfKey(srcId);
+    if (index < 0) {
+        if (mRenderer == NULL) {
+            sp<AMessage> notifyLost = new AMessage(kWhatPacketLost, id());
+            notifyLost->setInt32("ssrc", srcId);
+
+            mRenderer = new TunnelRenderer(notifyLost, mSurfaceTex);
+            looper()->registerHandler(mRenderer);
+        }
+
+        sp<AMessage> queueBufferMsg =
+            new AMessage(TunnelRenderer::kWhatQueueBuffer, mRenderer->id());
+
+        sp<Source> source = new Source(seqNo, buffer, queueBufferMsg);
+        mSources.add(srcId, source);
+    } else {
+        mSources.valueAt(index)->updateSeq(seqNo, buffer);
+    }
+
+    return OK;
+}
+
+status_t RTPSink::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:
+            {
+                parseSR(data, headerLength);
+                break;
+            }
+
+            case 201:  // RR
+            case 202:  // SDES
+            case 204:  // APP
+                break;
+
+            case 205:  // TSFB (transport layer specific feedback)
+            case 206:  // PSFB (payload specific feedback)
+                // hexdump(data, headerLength);
+                break;
+
+            case 203:
+            {
+                parseBYE(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 RTPSink::parseBYE(const uint8_t *data, size_t size) {
+    size_t SC = data[0] & 0x3f;
+
+    if (SC == 0 || size < (4 + SC * 4)) {
+        // Packet too short for the minimal BYE header.
+        return ERROR_MALFORMED;
+    }
+
+    uint32_t id = U32_AT(&data[4]);
+
+    return OK;
+}
+
+status_t RTPSink::parseSR(const uint8_t *data, size_t size) {
+    size_t RC = data[0] & 0x1f;
+
+    if (size < (7 + RC * 6) * 4) {
+        // Packet too short for the minimal SR header.
+        return ERROR_MALFORMED;
+    }
+
+    uint32_t id = U32_AT(&data[4]);
+    uint64_t ntpTime = U64_AT(&data[8]);
+    uint32_t rtpTime = U32_AT(&data[16]);
+
+    ALOGV("SR: ssrc 0x%08x, ntpTime 0x%016llx, rtpTime 0x%08x",
+          id, ntpTime, rtpTime);
+
+    return OK;
+}
+
+status_t RTPSink::connect(
+        const char *host, int32_t remoteRtpPort, int32_t remoteRtcpPort) {
+    ALOGI("connecting RTP/RTCP sockets to %s:{%d,%d}",
+          host, remoteRtpPort, remoteRtcpPort);
+
+    status_t err =
+        mNetSession->connectUDPSession(mRTPSessionID, host, remoteRtpPort);
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = mNetSession->connectUDPSession(mRTCPSessionID, host, remoteRtcpPort);
+
+    if (err != OK) {
+        return err;
+    }
+
+#if 0
+    sp<ABuffer> buf = new ABuffer(1500);
+    memset(buf->data(), 0, buf->size());
+
+    mNetSession->sendRequest(
+            mRTPSessionID, buf->data(), buf->size());
+
+    mNetSession->sendRequest(
+            mRTCPSessionID, buf->data(), buf->size());
+#endif
+
+    scheduleSendRR();
+
+    return OK;
+}
+
+void RTPSink::scheduleSendRR() {
+    (new AMessage(kWhatSendRR, id()))->post(2000000ll);
+}
+
+void RTPSink::addSDES(const sp<ABuffer> &buffer) {
+    uint8_t *data = buffer->data() + buffer->size();
+    data[0] = 0x80 | 1;
+    data[1] = 202;  // SDES
+    data[4] = 0xde;  // SSRC
+    data[5] = 0xad;
+    data[6] = 0xbe;
+    data[7] = 0xef;
+
+    size_t offset = 8;
+
+    data[offset++] = 1;  // CNAME
+
+    AString cname = "stagefright@somewhere";
+    data[offset++] = cname.size();
+
+    memcpy(&data[offset], cname.c_str(), cname.size());
+    offset += cname.size();
+
+    data[offset++] = 6;  // TOOL
+
+    AString tool = "stagefright/1.0";
+    data[offset++] = tool.size();
+
+    memcpy(&data[offset], tool.c_str(), tool.size());
+    offset += tool.size();
+
+    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);
+}
+
+void RTPSink::onSendRR() {
+    sp<ABuffer> buf = new ABuffer(1500);
+    buf->setRange(0, 0);
+
+    uint8_t *ptr = buf->data();
+    ptr[0] = 0x80 | 0;
+    ptr[1] = 201;  // RR
+    ptr[2] = 0;
+    ptr[3] = 1;
+    ptr[4] = 0xde;  // SSRC
+    ptr[5] = 0xad;
+    ptr[6] = 0xbe;
+    ptr[7] = 0xef;
+
+    buf->setRange(0, 8);
+
+    size_t numReportBlocks = 0;
+    for (size_t i = 0; i < mSources.size(); ++i) {
+        uint32_t ssrc = mSources.keyAt(i);
+        sp<Source> source = mSources.valueAt(i);
+
+        if (numReportBlocks > 31 || buf->size() + 24 > buf->capacity()) {
+            // Cannot fit another report block.
+            break;
+        }
+
+        source->addReportBlock(ssrc, buf);
+        ++numReportBlocks;
+    }
+
+    ptr[0] |= numReportBlocks;  // 5 bit
+
+    size_t sizeInWordsMinus1 = 1 + 6 * numReportBlocks;
+    ptr[2] = sizeInWordsMinus1 >> 8;
+    ptr[3] = sizeInWordsMinus1 & 0xff;
+
+    buf->setRange(0, (sizeInWordsMinus1 + 1) * 4);
+
+    addSDES(buf);
+
+    mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+
+    scheduleSendRR();
+}
+
+void RTPSink::onPacketLost(const sp<AMessage> &msg) {
+    uint32_t srcId;
+    CHECK(msg->findInt32("ssrc", (int32_t *)&srcId));
+
+    int32_t seqNo;
+    CHECK(msg->findInt32("seqNo", &seqNo));
+
+    int32_t blp = 0;
+
+    sp<ABuffer> buf = new ABuffer(1500);
+    buf->setRange(0, 0);
+
+    uint8_t *ptr = buf->data();
+    ptr[0] = 0x80 | 1;  // generic NACK
+    ptr[1] = 205;  // RTPFB
+    ptr[2] = 0;
+    ptr[3] = 3;
+    ptr[4] = 0xde;  // sender SSRC
+    ptr[5] = 0xad;
+    ptr[6] = 0xbe;
+    ptr[7] = 0xef;
+    ptr[8] = (srcId >> 24) & 0xff;
+    ptr[9] = (srcId >> 16) & 0xff;
+    ptr[10] = (srcId >> 8) & 0xff;
+    ptr[11] = (srcId & 0xff);
+    ptr[12] = (seqNo >> 8) & 0xff;
+    ptr[13] = (seqNo & 0xff);
+    ptr[14] = (blp >> 8) & 0xff;
+    ptr[15] = (blp & 0xff);
+
+    buf->setRange(0, 16);
+
+    mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/wifi-display/sink/RTPSink.h b/media/libstagefright/wifi-display/sink/RTPSink.h
new file mode 100644
index 0000000..a1d127d
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/RTPSink.h
@@ -0,0 +1,98 @@
+/*
+ * 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 RTP_SINK_H_
+
+#define RTP_SINK_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+#include "LinearRegression.h"
+
+#include <gui/Surface.h>
+
+namespace android {
+
+struct ABuffer;
+struct ANetworkSession;
+struct TunnelRenderer;
+
+// Creates a pair of sockets for RTP/RTCP traffic, instantiates a renderer
+// for incoming transport stream data and occasionally sends statistics over
+// the RTCP channel.
+struct RTPSink : public AHandler {
+    RTPSink(const sp<ANetworkSession> &netSession,
+            const sp<ISurfaceTexture> &surfaceTex);
+
+    // If TCP interleaving is used, no UDP sockets are created, instead
+    // incoming RTP/RTCP packets (arriving on the RTSP control connection)
+    // are manually injected by WifiDisplaySink.
+    status_t init(bool useTCPInterleaving);
+
+    status_t connect(
+            const char *host, int32_t remoteRtpPort, int32_t remoteRtcpPort);
+
+    int32_t getRTPPort() const;
+
+    status_t injectPacket(bool isRTP, const sp<ABuffer> &buffer);
+
+protected:
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+    virtual ~RTPSink();
+
+private:
+    enum {
+        kWhatRTPNotify,
+        kWhatRTCPNotify,
+        kWhatSendRR,
+        kWhatPacketLost,
+        kWhatInject,
+    };
+
+    struct Source;
+    struct StreamSource;
+
+    sp<ANetworkSession> mNetSession;
+    sp<ISurfaceTexture> mSurfaceTex;
+    KeyedVector<uint32_t, sp<Source> > mSources;
+
+    int32_t mRTPPort;
+    int32_t mRTPSessionID;
+    int32_t mRTCPSessionID;
+
+    int64_t mFirstArrivalTimeUs;
+    int64_t mNumPacketsReceived;
+    LinearRegression mRegression;
+    int64_t mMaxDelayMs;
+
+    sp<TunnelRenderer> mRenderer;
+
+    status_t parseRTP(const sp<ABuffer> &buffer);
+    status_t parseRTCP(const sp<ABuffer> &buffer);
+    status_t parseBYE(const uint8_t *data, size_t size);
+    status_t parseSR(const uint8_t *data, size_t size);
+
+    void addSDES(const sp<ABuffer> &buffer);
+    void onSendRR();
+    void onPacketLost(const sp<AMessage> &msg);
+    void scheduleSendRR();
+
+    DISALLOW_EVIL_CONSTRUCTORS(RTPSink);
+};
+
+}  // namespace android
+
+#endif  // RTP_SINK_H_
diff --git a/media/libstagefright/wifi-display/sink/TunnelRenderer.cpp b/media/libstagefright/wifi-display/sink/TunnelRenderer.cpp
new file mode 100644
index 0000000..bc35aef
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/TunnelRenderer.cpp
@@ -0,0 +1,396 @@
+/*
+ * 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 "TunnelRenderer"
+#include <utils/Log.h>
+
+#include "TunnelRenderer.h"
+
+#include "ATSParser.h"
+
+#include <binder/IMemory.h>
+#include <binder/IServiceManager.h>
+#include <gui/SurfaceComposerClient.h>
+#include <media/IMediaPlayerService.h>
+#include <media/IStreamSource.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <ui/DisplayInfo.h>
+
+namespace android {
+
+struct TunnelRenderer::PlayerClient : public BnMediaPlayerClient {
+    PlayerClient() {}
+
+    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) {
+        ALOGI("notify %d, %d, %d", msg, ext1, ext2);
+    }
+
+protected:
+    virtual ~PlayerClient() {}
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(PlayerClient);
+};
+
+struct TunnelRenderer::StreamSource : public BnStreamSource {
+    StreamSource(TunnelRenderer *owner);
+
+    virtual void setListener(const sp<IStreamListener> &listener);
+    virtual void setBuffers(const Vector<sp<IMemory> > &buffers);
+
+    virtual void onBufferAvailable(size_t index);
+
+    virtual uint32_t flags() const;
+
+    void doSomeWork();
+
+protected:
+    virtual ~StreamSource();
+
+private:
+    mutable Mutex mLock;
+
+    TunnelRenderer *mOwner;
+
+    sp<IStreamListener> mListener;
+
+    Vector<sp<IMemory> > mBuffers;
+    List<size_t> mIndicesAvailable;
+
+    size_t mNumDeqeued;
+
+    DISALLOW_EVIL_CONSTRUCTORS(StreamSource);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TunnelRenderer::StreamSource::StreamSource(TunnelRenderer *owner)
+    : mOwner(owner),
+      mNumDeqeued(0) {
+}
+
+TunnelRenderer::StreamSource::~StreamSource() {
+}
+
+void TunnelRenderer::StreamSource::setListener(
+        const sp<IStreamListener> &listener) {
+    mListener = listener;
+}
+
+void TunnelRenderer::StreamSource::setBuffers(
+        const Vector<sp<IMemory> > &buffers) {
+    mBuffers = buffers;
+}
+
+void TunnelRenderer::StreamSource::onBufferAvailable(size_t index) {
+    CHECK_LT(index, mBuffers.size());
+
+    {
+        Mutex::Autolock autoLock(mLock);
+        mIndicesAvailable.push_back(index);
+    }
+
+    doSomeWork();
+}
+
+uint32_t TunnelRenderer::StreamSource::flags() const {
+    return kFlagAlignedVideoData;
+}
+
+void TunnelRenderer::StreamSource::doSomeWork() {
+    Mutex::Autolock autoLock(mLock);
+
+    while (!mIndicesAvailable.empty()) {
+        sp<ABuffer> srcBuffer = mOwner->dequeueBuffer();
+        if (srcBuffer == NULL) {
+            break;
+        }
+
+        ++mNumDeqeued;
+
+        if (mNumDeqeued == 1) {
+            ALOGI("fixing real time now.");
+
+            sp<AMessage> extra = new AMessage;
+
+            extra->setInt32(
+                    IStreamListener::kKeyDiscontinuityMask,
+                    ATSParser::DISCONTINUITY_ABSOLUTE_TIME);
+
+            extra->setInt64("timeUs", ALooper::GetNowUs());
+
+            mListener->issueCommand(
+                    IStreamListener::DISCONTINUITY,
+                    false /* synchronous */,
+                    extra);
+        }
+
+        ALOGV("dequeue TS packet of size %d", srcBuffer->size());
+
+        size_t index = *mIndicesAvailable.begin();
+        mIndicesAvailable.erase(mIndicesAvailable.begin());
+
+        sp<IMemory> mem = mBuffers.itemAt(index);
+        CHECK_LE(srcBuffer->size(), mem->size());
+        CHECK_EQ((srcBuffer->size() % 188), 0u);
+
+        memcpy(mem->pointer(), srcBuffer->data(), srcBuffer->size());
+        mListener->queueBuffer(index, srcBuffer->size());
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TunnelRenderer::TunnelRenderer(
+        const sp<AMessage> &notifyLost,
+        const sp<ISurfaceTexture> &surfaceTex)
+    : mNotifyLost(notifyLost),
+      mSurfaceTex(surfaceTex),
+      mTotalBytesQueued(0ll),
+      mLastDequeuedExtSeqNo(-1),
+      mFirstFailedAttemptUs(-1ll),
+      mRequestedRetransmission(false) {
+}
+
+TunnelRenderer::~TunnelRenderer() {
+    destroyPlayer();
+}
+
+void TunnelRenderer::queueBuffer(const sp<ABuffer> &buffer) {
+    Mutex::Autolock autoLock(mLock);
+
+    mTotalBytesQueued += buffer->size();
+
+    if (mPackets.empty()) {
+        mPackets.push_back(buffer);
+        return;
+    }
+
+    int32_t newExtendedSeqNo = buffer->int32Data();
+
+    List<sp<ABuffer> >::iterator firstIt = mPackets.begin();
+    List<sp<ABuffer> >::iterator it = --mPackets.end();
+    for (;;) {
+        int32_t extendedSeqNo = (*it)->int32Data();
+
+        if (extendedSeqNo == newExtendedSeqNo) {
+            // Duplicate packet.
+            return;
+        }
+
+        if (extendedSeqNo < newExtendedSeqNo) {
+            // Insert new packet after the one at "it".
+            mPackets.insert(++it, buffer);
+            return;
+        }
+
+        if (it == firstIt) {
+            // Insert new packet before the first existing one.
+            mPackets.insert(it, buffer);
+            return;
+        }
+
+        --it;
+    }
+}
+
+sp<ABuffer> TunnelRenderer::dequeueBuffer() {
+    Mutex::Autolock autoLock(mLock);
+
+    sp<ABuffer> buffer;
+    int32_t extSeqNo;
+    while (!mPackets.empty()) {
+        buffer = *mPackets.begin();
+        extSeqNo = buffer->int32Data();
+
+        if (mLastDequeuedExtSeqNo < 0 || extSeqNo > mLastDequeuedExtSeqNo) {
+            break;
+        }
+
+        // This is a retransmission of a packet we've already returned.
+
+        mTotalBytesQueued -= buffer->size();
+        buffer.clear();
+        extSeqNo = -1;
+
+        mPackets.erase(mPackets.begin());
+    }
+
+    if (mPackets.empty()) {
+        if (mFirstFailedAttemptUs < 0ll) {
+            mFirstFailedAttemptUs = ALooper::GetNowUs();
+            mRequestedRetransmission = false;
+        } else {
+            ALOGV("no packets available for %.2f secs",
+                    (ALooper::GetNowUs() - mFirstFailedAttemptUs) / 1E6);
+        }
+
+        return NULL;
+    }
+
+    if (mLastDequeuedExtSeqNo < 0 || extSeqNo == mLastDequeuedExtSeqNo + 1) {
+        if (mRequestedRetransmission) {
+            ALOGI("Recovered after requesting retransmission of %d",
+                  extSeqNo);
+        }
+
+        mLastDequeuedExtSeqNo = extSeqNo;
+        mFirstFailedAttemptUs = -1ll;
+        mRequestedRetransmission = false;
+
+        mPackets.erase(mPackets.begin());
+
+        mTotalBytesQueued -= buffer->size();
+
+        return buffer;
+    }
+
+    if (mFirstFailedAttemptUs < 0ll) {
+        mFirstFailedAttemptUs = ALooper::GetNowUs();
+
+        ALOGI("failed to get the correct packet the first time.");
+        return NULL;
+    }
+
+    if (mFirstFailedAttemptUs + 50000ll > ALooper::GetNowUs()) {
+        // We're willing to wait a little while to get the right packet.
+
+        if (!mRequestedRetransmission) {
+            ALOGI("requesting retransmission of seqNo %d",
+                  (mLastDequeuedExtSeqNo + 1) & 0xffff);
+
+            sp<AMessage> notify = mNotifyLost->dup();
+            notify->setInt32("seqNo", (mLastDequeuedExtSeqNo + 1) & 0xffff);
+            notify->post();
+
+            mRequestedRetransmission = true;
+        } else {
+            ALOGI("still waiting for the correct packet to arrive.");
+        }
+
+        return NULL;
+    }
+
+    ALOGI("dropping packet. extSeqNo %d didn't arrive in time",
+            mLastDequeuedExtSeqNo + 1);
+
+    // Permanent failure, we never received the packet.
+    mLastDequeuedExtSeqNo = extSeqNo;
+    mFirstFailedAttemptUs = -1ll;
+    mRequestedRetransmission = false;
+
+    mTotalBytesQueued -= buffer->size();
+
+    mPackets.erase(mPackets.begin());
+
+    return buffer;
+}
+
+void TunnelRenderer::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatQueueBuffer:
+        {
+            sp<ABuffer> buffer;
+            CHECK(msg->findBuffer("buffer", &buffer));
+
+            queueBuffer(buffer);
+
+            if (mStreamSource == NULL) {
+                if (mTotalBytesQueued > 0ll) {
+                    initPlayer();
+                } else {
+                    ALOGI("Have %lld bytes queued...", mTotalBytesQueued);
+                }
+            } else {
+                mStreamSource->doSomeWork();
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+void TunnelRenderer::initPlayer() {
+    if (mSurfaceTex == NULL) {
+        mComposerClient = new SurfaceComposerClient;
+        CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
+
+        DisplayInfo info;
+        SurfaceComposerClient::getDisplayInfo(0, &info);
+        ssize_t displayWidth = info.w;
+        ssize_t displayHeight = info.h;
+
+        mSurfaceControl =
+            mComposerClient->createSurface(
+                    String8("A Surface"),
+                    displayWidth,
+                    displayHeight,
+                    PIXEL_FORMAT_RGB_565,
+                    0);
+
+        CHECK(mSurfaceControl != NULL);
+        CHECK(mSurfaceControl->isValid());
+
+        SurfaceComposerClient::openGlobalTransaction();
+        CHECK_EQ(mSurfaceControl->setLayer(INT_MAX), (status_t)OK);
+        CHECK_EQ(mSurfaceControl->show(), (status_t)OK);
+        SurfaceComposerClient::closeGlobalTransaction();
+
+        mSurface = mSurfaceControl->getSurface();
+        CHECK(mSurface != NULL);
+    }
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binder = sm->getService(String16("media.player"));
+    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
+    CHECK(service.get() != NULL);
+
+    mStreamSource = new StreamSource(this);
+
+    mPlayerClient = new PlayerClient;
+
+    mPlayer = service->create(getpid(), mPlayerClient, 0);
+    CHECK(mPlayer != NULL);
+    CHECK_EQ(mPlayer->setDataSource(mStreamSource), (status_t)OK);
+
+    mPlayer->setVideoSurfaceTexture(
+            mSurfaceTex != NULL ? mSurfaceTex : mSurface->getSurfaceTexture());
+
+    mPlayer->start();
+}
+
+void TunnelRenderer::destroyPlayer() {
+    mStreamSource.clear();
+
+    mPlayer->stop();
+    mPlayer.clear();
+
+    if (mSurfaceTex == NULL) {
+        mSurface.clear();
+        mSurfaceControl.clear();
+
+        mComposerClient->dispose();
+        mComposerClient.clear();
+    }
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/wifi-display/sink/TunnelRenderer.h b/media/libstagefright/wifi-display/sink/TunnelRenderer.h
new file mode 100644
index 0000000..c9597e0
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/TunnelRenderer.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 TUNNEL_RENDERER_H_
+
+#define TUNNEL_RENDERER_H_
+
+#include <gui/Surface.h>
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct SurfaceComposerClient;
+struct SurfaceControl;
+struct Surface;
+struct IMediaPlayer;
+struct IStreamListener;
+
+// This class reassembles incoming RTP packets into the correct order
+// and sends the resulting transport stream to a mediaplayer instance
+// for playback.
+struct TunnelRenderer : public AHandler {
+    TunnelRenderer(
+            const sp<AMessage> &notifyLost,
+            const sp<ISurfaceTexture> &surfaceTex);
+
+    sp<ABuffer> dequeueBuffer();
+
+    enum {
+        kWhatQueueBuffer,
+    };
+
+protected:
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+    virtual ~TunnelRenderer();
+
+private:
+    struct PlayerClient;
+    struct StreamSource;
+
+    mutable Mutex mLock;
+
+    sp<AMessage> mNotifyLost;
+    sp<ISurfaceTexture> mSurfaceTex;
+
+    List<sp<ABuffer> > mPackets;
+    int64_t mTotalBytesQueued;
+
+    sp<SurfaceComposerClient> mComposerClient;
+    sp<SurfaceControl> mSurfaceControl;
+    sp<Surface> mSurface;
+    sp<PlayerClient> mPlayerClient;
+    sp<IMediaPlayer> mPlayer;
+    sp<StreamSource> mStreamSource;
+
+    int32_t mLastDequeuedExtSeqNo;
+    int64_t mFirstFailedAttemptUs;
+    bool mRequestedRetransmission;
+
+    void initPlayer();
+    void destroyPlayer();
+
+    void queueBuffer(const sp<ABuffer> &buffer);
+
+    DISALLOW_EVIL_CONSTRUCTORS(TunnelRenderer);
+};
+
+}  // namespace android
+
+#endif  // TUNNEL_RENDERER_H_
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
index 5e7d9fd..d886f14 100644
--- a/media/libstagefright/wifi-display/wfd.cpp
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -18,11 +18,8 @@
 #define LOG_TAG "wfd"
 #include <utils/Log.h>
 
-#define SUPPORT_SINK    0
-
-#if SUPPORT_SINK
 #include "sink/WifiDisplaySink.h"
-#endif
+#include "source/WifiDisplaySource.h"
 
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
@@ -49,10 +46,8 @@
 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 ip[:port]       \tenable remote display\n"
             "           -d            \tdisable remote display\n",
             me);
@@ -72,7 +67,6 @@
     int res;
     while ((res = getopt(argc, argv, "hc:l:u:e:d")) >= 0) {
         switch (res) {
-#if SUPPORT_SINK
             case 'c':
             {
                 const char *colonPos = strrchr(optarg, ':');
@@ -100,7 +94,6 @@
                 uri = optarg;
                 break;
             }
-#endif
 
             case 'e':
             {
@@ -124,7 +117,6 @@
         }
     }
 
-#if SUPPORT_SINK
     if (connectToPort < 0 && uri.empty()) {
         fprintf(stderr,
                 "You need to select either source host or uri.\n");
@@ -154,7 +146,6 @@
     }
 
     looper->start(true /* runOnCallingThread */);
-#endif
 
     return 0;
 }