summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2012-08-31 14:05:27 -0700
committerAndreas Huber <andih@google.com>2012-09-04 10:41:49 -0700
commitfbe9d81ff5fbdc5aecdcdd13e4a5d7f019824f96 (patch)
tree1c19ca38ef4ec9be83a2be46189a3d6a593a0b81 /media
parent7323115c6e55f2da73317d7ff6a69b808d74264e (diff)
downloadframeworks_av-fbe9d81ff5fbdc5aecdcdd13e4a5d7f019824f96.zip
frameworks_av-fbe9d81ff5fbdc5aecdcdd13e4a5d7f019824f96.tar.gz
frameworks_av-fbe9d81ff5fbdc5aecdcdd13e4a5d7f019824f96.tar.bz2
Support for acting as a wifi display sink.
Change-Id: I0beac87025b93c60164daa865c89f16b72197a47
Diffstat (limited to 'media')
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayer.cpp4
-rw-r--r--media/libstagefright/wifi-display/Android.mk4
-rw-r--r--media/libstagefright/wifi-display/sink/LinearRegression.cpp110
-rw-r--r--media/libstagefright/wifi-display/sink/LinearRegression.h52
-rw-r--r--media/libstagefright/wifi-display/sink/RTPSink.cpp806
-rw-r--r--media/libstagefright/wifi-display/sink/RTPSink.h98
-rw-r--r--media/libstagefright/wifi-display/sink/TunnelRenderer.cpp396
-rw-r--r--media/libstagefright/wifi-display/sink/TunnelRenderer.h84
-rw-r--r--media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp644
-rw-r--r--media/libstagefright/wifi-display/sink/WifiDisplaySink.h147
-rw-r--r--media/libstagefright/wifi-display/wfd.cpp11
11 files changed, 2344 insertions, 12 deletions
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 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
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 @@ include $(CLEAR_VARS)
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/sink/WifiDisplaySink.cpp b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
new file mode 100644
index 0000000..fcd20d4
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
@@ -0,0 +1,644 @@
+/*
+ * 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 "WifiDisplaySink"
+#include <utils/Log.h>
+
+#include "WifiDisplaySink.h"
+#include "ParsedMessage.h"
+#include "RTPSink.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+WifiDisplaySink::WifiDisplaySink(
+ const sp<ANetworkSession> &netSession,
+ const sp<ISurfaceTexture> &surfaceTex)
+ : mState(UNDEFINED),
+ mNetSession(netSession),
+ mSurfaceTex(surfaceTex),
+ mSessionID(0),
+ mNextCSeq(1) {
+}
+
+WifiDisplaySink::~WifiDisplaySink() {
+}
+
+void WifiDisplaySink::start(const char *sourceHost, int32_t sourcePort) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setString("sourceHost", sourceHost);
+ msg->setInt32("sourcePort", sourcePort);
+ msg->post();
+}
+
+void WifiDisplaySink::start(const char *uri) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setString("setupURI", uri);
+ msg->post();
+}
+
+// static
+bool WifiDisplaySink::ParseURL(
+ const char *url, AString *host, int32_t *port, AString *path,
+ AString *user, AString *pass) {
+ host->clear();
+ *port = 0;
+ path->clear();
+ user->clear();
+ pass->clear();
+
+ if (strncasecmp("rtsp://", url, 7)) {
+ return false;
+ }
+
+ const char *slashPos = strchr(&url[7], '/');
+
+ if (slashPos == NULL) {
+ host->setTo(&url[7]);
+ path->setTo("/");
+ } else {
+ host->setTo(&url[7], slashPos - &url[7]);
+ path->setTo(slashPos);
+ }
+
+ ssize_t atPos = host->find("@");
+
+ if (atPos >= 0) {
+ // Split of user:pass@ from hostname.
+
+ AString userPass(*host, 0, atPos);
+ host->erase(0, atPos + 1);
+
+ ssize_t colonPos = userPass.find(":");
+
+ if (colonPos < 0) {
+ *user = userPass;
+ } else {
+ user->setTo(userPass, 0, colonPos);
+ pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1);
+ }
+ }
+
+ const char *colonPos = strchr(host->c_str(), ':');
+
+ if (colonPos != NULL) {
+ char *end;
+ unsigned long x = strtoul(colonPos + 1, &end, 10);
+
+ if (end == colonPos + 1 || *end != '\0' || x >= 65536) {
+ return false;
+ }
+
+ *port = x;
+
+ size_t colonOffset = colonPos - host->c_str();
+ size_t trailing = host->size() - colonOffset;
+ host->erase(colonOffset, trailing);
+ } else {
+ *port = 554;
+ }
+
+ return true;
+}
+
+void WifiDisplaySink::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ int32_t sourcePort;
+
+ if (msg->findString("setupURI", &mSetupURI)) {
+ AString path, user, pass;
+ CHECK(ParseURL(
+ mSetupURI.c_str(),
+ &mRTSPHost, &sourcePort, &path, &user, &pass)
+ && user.empty() && pass.empty());
+ } else {
+ CHECK(msg->findString("sourceHost", &mRTSPHost));
+ CHECK(msg->findInt32("sourcePort", &sourcePort));
+ }
+
+ sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
+
+ status_t err = mNetSession->createRTSPClient(
+ mRTSPHost.c_str(), sourcePort, notify, &mSessionID);
+ CHECK_EQ(err, (status_t)OK);
+
+ mState = CONNECTING;
+ break;
+ }
+
+ case kWhatRTSPNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred in session %d (%d, '%s/%s').",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ if (sessionID == mSessionID) {
+ ALOGI("Lost control connection.");
+
+ // The control connection is dead now.
+ mNetSession->destroySession(mSessionID);
+ mSessionID = 0;
+
+ looper()->stop();
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatConnected:
+ {
+ ALOGI("We're now connected.");
+ mState = CONNECTED;
+
+ if (!mSetupURI.empty()) {
+ status_t err =
+ sendDescribe(mSessionID, mSetupURI.c_str());
+
+ CHECK_EQ(err, (status_t)OK);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatData:
+ {
+ onReceiveClientData(msg);
+ break;
+ }
+
+ case ANetworkSession::kWhatBinaryData:
+ {
+ CHECK(sUseTCPInterleaving);
+
+ int32_t channel;
+ CHECK(msg->findInt32("channel", &channel));
+
+ sp<ABuffer> data;
+ CHECK(msg->findBuffer("data", &data));
+
+ mRTPSink->injectPacket(channel == 0 /* isRTP */, data);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ looper()->stop();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySink::registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+ mResponseHandlers.add(id, func);
+}
+
+status_t WifiDisplaySink::sendM2(int32_t sessionID) {
+ AString request = "OPTIONS * RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append(
+ "Require: org.wfa.wfd1.0\r\n"
+ "\r\n");
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceiveM2Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveM2Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveDescribeResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return sendSetup(sessionID, mSetupURI.c_str());
+}
+
+status_t WifiDisplaySink::onReceiveSetupResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (!msg->findString("session", &mPlaybackSessionID)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (!ParsedMessage::GetInt32Attribute(
+ mPlaybackSessionID.c_str(),
+ "timeout",
+ &mPlaybackSessionTimeoutSecs)) {
+ mPlaybackSessionTimeoutSecs = -1;
+ }
+
+ ssize_t colonPos = mPlaybackSessionID.find(";");
+ if (colonPos >= 0) {
+ // Strip any options from the returned session id.
+ mPlaybackSessionID.erase(
+ colonPos, mPlaybackSessionID.size() - colonPos);
+ }
+
+ status_t err = configureTransport(msg);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mState = PAUSED;
+
+ return sendPlay(
+ sessionID,
+ !mSetupURI.empty()
+ ? mSetupURI.c_str() : "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
+}
+
+status_t WifiDisplaySink::configureTransport(const sp<ParsedMessage> &msg) {
+ if (sUseTCPInterleaving) {
+ return OK;
+ }
+
+ AString transport;
+ if (!msg->findString("transport", &transport)) {
+ ALOGE("Missing 'transport' field in SETUP response.");
+ return ERROR_MALFORMED;
+ }
+
+ AString sourceHost;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "source", &sourceHost)) {
+ sourceHost = mRTSPHost;
+ }
+
+ AString serverPortStr;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "server_port", &serverPortStr)) {
+ ALOGE("Missing 'server_port' in Transport field.");
+ return ERROR_MALFORMED;
+ }
+
+ int rtpPort, rtcpPort;
+ if (sscanf(serverPortStr.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2
+ || rtpPort <= 0 || rtpPort > 65535
+ || rtcpPort <=0 || rtcpPort > 65535
+ || rtcpPort != rtpPort + 1) {
+ ALOGE("Invalid server_port description '%s'.",
+ serverPortStr.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ if (rtpPort & 1) {
+ ALOGW("Server picked an odd numbered RTP port.");
+ }
+
+ return mRTPSink->connect(sourceHost.c_str(), rtpPort, rtcpPort);
+}
+
+status_t WifiDisplaySink::onReceivePlayResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mState = PLAYING;
+
+ return OK;
+}
+
+void WifiDisplaySink::onReceiveClientData(const sp<AMessage> &msg) {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("data", &obj));
+
+ sp<ParsedMessage> data =
+ static_cast<ParsedMessage *>(obj.get());
+
+ ALOGV("session %d received '%s'",
+ sessionID, data->debugString().c_str());
+
+ AString method;
+ AString uri;
+ data->getRequestField(0, &method);
+
+ int32_t cseq;
+ if (!data->findInt32("cseq", &cseq)) {
+ sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
+ return;
+ }
+
+ if (method.startsWith("RTSP/")) {
+ // This is a response.
+
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+
+ ssize_t index = mResponseHandlers.indexOfKey(id);
+
+ if (index < 0) {
+ ALOGW("Received unsolicited server response, cseq %d", cseq);
+ return;
+ }
+
+ HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
+ mResponseHandlers.removeItemsAt(index);
+
+ status_t err = (this->*func)(sessionID, data);
+ CHECK_EQ(err, (status_t)OK);
+ } else {
+ AString version;
+ data->getRequestField(2, &version);
+ if (!(version == AString("RTSP/1.0"))) {
+ sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
+ return;
+ }
+
+ if (method == "OPTIONS") {
+ onOptionsRequest(sessionID, cseq, data);
+ } else if (method == "GET_PARAMETER") {
+ onGetParameterRequest(sessionID, cseq, data);
+ } else if (method == "SET_PARAMETER") {
+ onSetParameterRequest(sessionID, cseq, data);
+ } else {
+ sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
+ }
+ }
+}
+
+void WifiDisplaySink::onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER\r\n");
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+
+ err = sendM2(sessionID);
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySink::onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ AString body =
+ "wfd_video_formats: xxx\r\n"
+ "wfd_audio_codecs: xxx\r\n"
+ "wfd_client_rtp_ports: RTP/AVP/UDP;unicast xxx 0 mode=play\r\n";
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("Content-Type: text/parameters\r\n");
+ response.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+ response.append("\r\n");
+ response.append(body);
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+status_t WifiDisplaySink::sendDescribe(int32_t sessionID, const char *uri) {
+ uri = "rtsp://xwgntvx.is.livestream-api.com/livestreamiphone/wgntv";
+ uri = "rtsp://v2.cache6.c.youtube.com/video.3gp?cid=e101d4bf280055f9&fmt=18";
+
+ AString request = StringPrintf("DESCRIBE %s RTSP/1.0\r\n", uri);
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append("Accept: application/sdp\r\n");
+ request.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(
+ sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceiveDescribeResponse);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::sendSetup(int32_t sessionID, const char *uri) {
+ mRTPSink = new RTPSink(mNetSession, mSurfaceTex);
+ looper()->registerHandler(mRTPSink);
+
+ status_t err = mRTPSink->init(sUseTCPInterleaving);
+
+ if (err != OK) {
+ looper()->unregisterHandler(mRTPSink->id());
+ mRTPSink.clear();
+ return err;
+ }
+
+ AString request = StringPrintf("SETUP %s RTSP/1.0\r\n", uri);
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ if (sUseTCPInterleaving) {
+ request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n");
+ } else {
+ int32_t rtpPort = mRTPSink->getRTPPort();
+
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
+ rtpPort, rtpPort + 1));
+ }
+
+ request.append("\r\n");
+
+ ALOGV("request = '%s'", request.c_str());
+
+ err = mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceiveSetupResponse);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::sendPlay(int32_t sessionID, const char *uri) {
+ AString request = StringPrintf("PLAY %s RTSP/1.0\r\n", uri);
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
+ request.append("\r\n");
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceivePlayResponse);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+void WifiDisplaySink::onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ const char *content = data->getContent();
+
+ if (strstr(content, "wfd_trigger_method: SETUP\r\n") != NULL) {
+ status_t err =
+ sendSetup(
+ sessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
+
+ CHECK_EQ(err, (status_t)OK);
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySink::sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq) {
+ AString response;
+ response.append("RTSP/1.0 ");
+ response.append(errorDetail);
+ response.append("\r\n");
+
+ AppendCommonResponse(&response, cseq);
+
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+// static
+void WifiDisplaySink::AppendCommonResponse(AString *response, int32_t cseq) {
+ time_t now = time(NULL);
+ struct tm *now2 = gmtime(&now);
+ char buf[128];
+ strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", now2);
+
+ response->append("Date: ");
+ response->append(buf);
+ response->append("\r\n");
+
+ response->append("User-Agent: stagefright/1.1 (Linux;Android 4.1)\r\n");
+
+ if (cseq >= 0) {
+ response->append(StringPrintf("CSeq: %d\r\n", cseq));
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
new file mode 100644
index 0000000..f886ee5
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WIFI_DISPLAY_SINK_H_
+
+#define WIFI_DISPLAY_SINK_H_
+
+#include "ANetworkSession.h"
+
+#include <gui/Surface.h>
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ParsedMessage;
+struct RTPSink;
+
+// Represents the RTSP client acting as a wifi display sink.
+// Connects to a wifi display source and renders the incoming
+// transport stream using a MediaPlayer instance.
+struct WifiDisplaySink : public AHandler {
+ WifiDisplaySink(
+ const sp<ANetworkSession> &netSession,
+ const sp<ISurfaceTexture> &surfaceTex = NULL);
+
+ void start(const char *sourceHost, int32_t sourcePort);
+ void start(const char *uri);
+
+protected:
+ virtual ~WifiDisplaySink();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum State {
+ UNDEFINED,
+ CONNECTING,
+ CONNECTED,
+ PAUSED,
+ PLAYING,
+ };
+
+ enum {
+ kWhatStart,
+ kWhatRTSPNotify,
+ kWhatStop,
+ };
+
+ struct ResponseID {
+ int32_t mSessionID;
+ int32_t mCSeq;
+
+ bool operator<(const ResponseID &other) const {
+ return mSessionID < other.mSessionID
+ || (mSessionID == other.mSessionID
+ && mCSeq < other.mCSeq);
+ }
+ };
+
+ typedef status_t (WifiDisplaySink::*HandleRTSPResponseFunc)(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ static const bool sUseTCPInterleaving = false;
+
+ State mState;
+ sp<ANetworkSession> mNetSession;
+ sp<ISurfaceTexture> mSurfaceTex;
+ AString mSetupURI;
+ AString mRTSPHost;
+ int32_t mSessionID;
+
+ int32_t mNextCSeq;
+
+ KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers;
+
+ sp<RTPSink> mRTPSink;
+ AString mPlaybackSessionID;
+ int32_t mPlaybackSessionTimeoutSecs;
+
+ status_t sendM2(int32_t sessionID);
+ status_t sendDescribe(int32_t sessionID, const char *uri);
+ status_t sendSetup(int32_t sessionID, const char *uri);
+ status_t sendPlay(int32_t sessionID, const char *uri);
+
+ status_t onReceiveM2Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveDescribeResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveSetupResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t configureTransport(const sp<ParsedMessage> &msg);
+
+ status_t onReceivePlayResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ void registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
+
+ void onReceiveClientData(const sp<AMessage> &msg);
+
+ void onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq);
+
+ static void AppendCommonResponse(AString *response, int32_t cseq);
+
+ bool ParseURL(
+ const char *url, AString *host, int32_t *port, AString *path,
+ AString *user, AString *pass);
+
+ DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink);
+};
+
+} // namespace android
+
+#endif // WIFI_DISPLAY_SINK_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 enableDisableRemoteDisplay(const char *iface) {
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 main(int argc, char **argv) {
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 @@ int main(int argc, char **argv) {
uri = optarg;
break;
}
-#endif
case 'e':
{
@@ -124,7 +117,6 @@ int main(int argc, char **argv) {
}
}
-#if SUPPORT_SINK
if (connectToPort < 0 && uri.empty()) {
fprintf(stderr,
"You need to select either source host or uri.\n");
@@ -154,7 +146,6 @@ int main(int argc, char **argv) {
}
looper->start(true /* runOnCallingThread */);
-#endif
return 0;
}