| /* |
| * 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 |