summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp')
-rw-r--r--media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp917
1 files changed, 917 insertions, 0 deletions
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
new file mode 100644
index 0000000..5db2099
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
@@ -0,0 +1,917 @@
+/*
+ * 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 "DirectRenderer.h"
+#include "MediaReceiver.h"
+#include "ParsedMessage.h"
+#include "TimeSyncer.h"
+
+#include <cutils/properties.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+// static
+const AString WifiDisplaySink::sUserAgent = MakeUserAgent();
+
+WifiDisplaySink::WifiDisplaySink(
+ uint32_t flags,
+ const sp<ANetworkSession> &netSession,
+ const sp<IGraphicBufferProducer> &bufferProducer,
+ const sp<AMessage> &notify)
+ : mState(UNDEFINED),
+ mFlags(flags),
+ mNetSession(netSession),
+ mSurfaceTex(bufferProducer),
+ mNotify(notify),
+ mUsingTCPTransport(false),
+ mUsingTCPInterleaving(false),
+ mSessionID(0),
+ mNextCSeq(1),
+ mIDRFrameRequestPending(false),
+ mTimeOffsetUs(0ll),
+ mTimeOffsetValid(false),
+ mSetupDeferred(false),
+ mLatencyCount(0),
+ mLatencySumUs(0ll),
+ mLatencyMaxUs(0ll),
+ mMaxDelayMs(-1ll) {
+ // We support any and all resolutions, but prefer 720p30
+ mSinkSupportedVideoFormats.setNativeResolution(
+ VideoFormats::RESOLUTION_CEA, 5); // 1280 x 720 p30
+
+ mSinkSupportedVideoFormats.enableAll();
+}
+
+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:
+ {
+ sleep(2); // XXX
+
+ int32_t sourcePort;
+ 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;
+
+ if (mNotify == NULL) {
+ looper()->stop();
+ } else {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatDisconnected);
+ notify->post();
+ }
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatConnected:
+ {
+ ALOGI("We're now connected.");
+ mState = CONNECTED;
+
+ if (mFlags & FLAG_SPECIAL_MODE) {
+ sp<AMessage> notify = new AMessage(
+ kWhatTimeSyncerNotify, id());
+
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ mTimeSyncer->startClient(mRTSPHost.c_str(), 8123);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatData:
+ {
+ onReceiveClientData(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ looper()->stop();
+ break;
+ }
+
+ case kWhatMediaReceiverNotify:
+ {
+ onMediaReceiverNotify(msg);
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ if (what == TimeSyncer::kWhatTimeOffset) {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+
+ if (mSetupDeferred) {
+ CHECK_EQ((status_t)OK,
+ sendSetup(
+ mSessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0"));
+
+ mSetupDeferred = false;
+ }
+ }
+ break;
+ }
+
+ case kWhatReportLateness:
+ {
+ if (mLatencyCount > 0) {
+ int64_t avgLatencyUs = mLatencySumUs / mLatencyCount;
+
+ ALOGV("avg. latency = %lld ms (max %lld ms)",
+ avgLatencyUs / 1000ll,
+ mLatencyMaxUs / 1000ll);
+
+ sp<AMessage> params = new AMessage;
+ params->setInt64("avgLatencyUs", avgLatencyUs);
+ params->setInt64("maxLatencyUs", mLatencyMaxUs);
+ mMediaReceiver->informSender(0 /* trackIndex */, params);
+ }
+
+ mLatencyCount = 0;
+ mLatencySumUs = 0ll;
+ mLatencyMaxUs = 0ll;
+
+ msg->post(kReportLatenessEveryUs);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySink::dumpDelay(size_t trackIndex, int64_t timeUs) {
+ int64_t delayMs = (ALooper::GetNowUs() - timeUs) / 1000ll;
+
+ if (delayMs > mMaxDelayMs) {
+ mMaxDelayMs = delayMs;
+ }
+
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ ALOGI("[%lld]: (%4lld ms / %4lld ms) %s",
+ timeUs / 1000,
+ delayMs,
+ mMaxDelayMs,
+ kPattern + kPatternSize - n);
+}
+
+void WifiDisplaySink::onMediaReceiverNotify(const sp<AMessage> &msg) {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case MediaReceiver::kWhatInitDone:
+ {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGI("MediaReceiver initialization completed w/ err %d", err);
+ break;
+ }
+
+ case MediaReceiver::kWhatError:
+ {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGE("MediaReceiver signaled error %d", err);
+ break;
+ }
+
+ case MediaReceiver::kWhatAccessUnit:
+ {
+ if (mRenderer == NULL) {
+ mRenderer = new DirectRenderer(mSurfaceTex);
+ looper()->registerHandler(mRenderer);
+ }
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (!mTimeOffsetValid && !(mFlags & FLAG_SPECIAL_MODE)) {
+ mTimeOffsetUs = timeUs - ALooper::GetNowUs();
+ mTimeOffsetValid = true;
+ }
+
+ CHECK(mTimeOffsetValid);
+
+ // We are the timesync _client_,
+ // client time = server time - time offset.
+ timeUs -= mTimeOffsetUs;
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayUs = nowUs - timeUs;
+
+ mLatencySumUs += delayUs;
+ if (mLatencyCount == 0 || delayUs > mLatencyMaxUs) {
+ mLatencyMaxUs = delayUs;
+ }
+ ++mLatencyCount;
+
+ // dumpDelay(trackIndex, timeUs);
+
+ timeUs += 220000ll; // Assume 220 ms of latency
+ accessUnit->meta()->setInt64("timeUs", timeUs);
+
+ sp<AMessage> format;
+ if (msg->findMessage("format", &format)) {
+ mRenderer->setFormat(trackIndex, format);
+ }
+
+ mRenderer->queueAccessUnit(trackIndex, accessUnit);
+ break;
+ }
+
+ case MediaReceiver::kWhatPacketLost:
+ {
+#if 0
+ if (!mIDRFrameRequestPending) {
+ ALOGI("requesting IDR frame");
+
+ sendIDRFrameRequest(mSessionID);
+ }
+#endif
+ 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::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,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
+}
+
+status_t WifiDisplaySink::configureTransport(const sp<ParsedMessage> &msg) {
+ if (mUsingTCPTransport && !(mFlags & FLAG_SPECIAL_MODE)) {
+ // In "special" mode we still use a UDP RTCP back-channel that
+ // needs connecting.
+ 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 mMediaReceiver->connectTrack(
+ 0 /* trackIndex */, 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;
+
+ (new AMessage(kWhatReportLateness, id()))->post(kReportLatenessEveryUs);
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveIDRFrameRequestResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ CHECK(mIDRFrameRequestPending);
+ mIDRFrameRequestPending = false;
+
+ 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;
+
+ if (mState == CONNECTED) {
+ mUsingTCPTransport = false;
+ mUsingTCPInterleaving = false;
+
+ char val[PROPERTY_VALUE_MAX];
+ if (property_get("media.wfd-sink.tcp-mode", val, NULL)) {
+ if (!strcasecmp("true", val) || !strcmp("1", val)) {
+ ALOGI("Using TCP unicast transport.");
+ mUsingTCPTransport = true;
+ mUsingTCPInterleaving = false;
+ } else if (!strcasecmp("interleaved", val)) {
+ ALOGI("Using TCP interleaved transport.");
+ mUsingTCPTransport = true;
+ mUsingTCPInterleaving = true;
+ }
+ } else if (mFlags & FLAG_SPECIAL_MODE) {
+ mUsingTCPTransport = true;
+ }
+
+ body = "wfd_video_formats: ";
+ body.append(mSinkSupportedVideoFormats.getFormatSpec());
+
+ body.append(
+ "\r\nwfd_audio_codecs: AAC 0000000F 00\r\n"
+ "wfd_client_rtp_ports: RTP/AVP/");
+
+ if (mUsingTCPTransport) {
+ body.append("TCP;");
+ if (mUsingTCPInterleaving) {
+ body.append("interleaved");
+ } else {
+ body.append("unicast 19000 0");
+ }
+ } else {
+ body.append("UDP;unicast 19000 0");
+ }
+
+ body.append(" 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::sendSetup(int32_t sessionID, const char *uri) {
+ sp<AMessage> notify = new AMessage(kWhatMediaReceiverNotify, id());
+
+ mMediaReceiverLooper = new ALooper;
+ mMediaReceiverLooper->setName("media_receiver");
+
+ mMediaReceiverLooper->start(
+ false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_AUDIO);
+
+ mMediaReceiver = new MediaReceiver(mNetSession, notify);
+ mMediaReceiverLooper->registerHandler(mMediaReceiver);
+
+ RTPReceiver::TransportMode rtpMode = RTPReceiver::TRANSPORT_UDP;
+ if (mUsingTCPTransport) {
+ if (mUsingTCPInterleaving) {
+ rtpMode = RTPReceiver::TRANSPORT_TCP_INTERLEAVED;
+ } else {
+ rtpMode = RTPReceiver::TRANSPORT_TCP;
+ }
+ }
+
+ int32_t localRTPPort;
+ status_t err = mMediaReceiver->addTrack(
+ rtpMode, RTPReceiver::TRANSPORT_UDP /* rtcpMode */, &localRTPPort);
+
+ if (err == OK) {
+ err = mMediaReceiver->initAsync(MediaReceiver::MODE_TRANSPORT_STREAM);
+ }
+
+ if (err != OK) {
+ mMediaReceiverLooper->unregisterHandler(mMediaReceiver->id());
+ mMediaReceiver.clear();
+
+ mMediaReceiverLooper->stop();
+ mMediaReceiverLooper.clear();
+
+ return err;
+ }
+
+ AString request = StringPrintf("SETUP %s RTSP/1.0\r\n", uri);
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ if (rtpMode == RTPReceiver::TRANSPORT_TCP_INTERLEAVED) {
+ request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n");
+ } else if (rtpMode == RTPReceiver::TRANSPORT_TCP) {
+ if (mFlags & FLAG_SPECIAL_MODE) {
+ // This isn't quite true, since the RTP connection is through TCP
+ // and the RTCP connection through UDP...
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/TCP;unicast;client_port=%d-%d\r\n",
+ localRTPPort, localRTPPort + 1));
+ } else {
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/TCP;unicast;client_port=%d\r\n",
+ localRTPPort));
+ }
+ } else {
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
+ localRTPPort,
+ localRTPPort + 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;
+}
+
+status_t WifiDisplaySink::sendIDRFrameRequest(int32_t sessionID) {
+ CHECK(!mIDRFrameRequestPending);
+
+ AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ AString content = "wfd_idr_request\r\n";
+
+ request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
+ request.append(StringPrintf("Content-Length: %d\r\n", content.size()));
+ request.append("\r\n");
+ request.append(content);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID,
+ mNextCSeq,
+ &WifiDisplaySink::onReceiveIDRFrameRequestResponse);
+
+ ++mNextCSeq;
+
+ mIDRFrameRequestPending = true;
+
+ 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) {
+ if ((mFlags & FLAG_SPECIAL_MODE) && !mTimeOffsetValid) {
+ mSetupDeferred = true;
+ } else {
+ 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(StringPrintf("User-Agent: %s\r\n", sUserAgent.c_str()));
+
+ if (cseq >= 0) {
+ response->append(StringPrintf("CSeq: %d\r\n", cseq));
+ }
+}
+
+} // namespace android