/* * 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 "WifiDisplaySource" #include #include "WifiDisplaySource.h" #include "PlaybackSession.h" #include "ParsedMessage.h" #include #include #include #include #include #include #include #include namespace android { WifiDisplaySource::WifiDisplaySource( const sp &netSession, const sp &client) : mNetSession(netSession), mClient(client), mSessionID(0), mReaperPending(false), mNextCSeq(1) { } WifiDisplaySource::~WifiDisplaySource() { } status_t WifiDisplaySource::start(const char *iface) { sp msg = new AMessage(kWhatStart, id()); msg->setString("iface", iface); sp response; status_t err = msg->postAndAwaitResponse(&response); if (err != OK) { return err; } if (!response->findInt32("err", &err)) { err = OK; } return err; } status_t WifiDisplaySource::stop() { sp msg = new AMessage(kWhatStop, id()); sp response; status_t err = msg->postAndAwaitResponse(&response); if (err != OK) { return err; } if (!response->findInt32("err", &err)) { err = OK; } return err; } void WifiDisplaySource::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatStart: { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); AString iface; CHECK(msg->findString("iface", &iface)); status_t err = OK; ssize_t colonPos = iface.find(":"); unsigned long port; if (colonPos >= 0) { const char *s = iface.c_str() + colonPos + 1; char *end; port = strtoul(s, &end, 10); if (end == s || *end != '\0' || port > 65535) { err = -EINVAL; } else { iface.erase(colonPos, iface.size() - colonPos); } } else { port = kWifiDisplayDefaultPort; } struct in_addr addr; if (err == OK) { if (inet_aton(iface.c_str(), &addr) != 0) { sp notify = new AMessage(kWhatRTSPNotify, id()); err = mNetSession->createRTSPServer( addr, port, notify, &mSessionID); } else { err = -EINVAL; } } sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); 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)); mNetSession->destroySession(sessionID); mClientInfos.removeItem(sessionID); break; } case ANetworkSession::kWhatClientConnected: { int32_t sessionID; CHECK(msg->findInt32("sessionID", &sessionID)); ClientInfo info; CHECK(msg->findString("client-ip", &info.mRemoteIP)); CHECK(msg->findString("server-ip", &info.mLocalIP)); CHECK(msg->findInt32("server-port", &info.mLocalPort)); info.mPlaybackSessionID = -1; ALOGI("We now have a client (%d) connected.", sessionID); mClientInfos.add(sessionID, info); status_t err = sendM1(sessionID); CHECK_EQ(err, (status_t)OK); break; } case ANetworkSession::kWhatData: { onReceiveClientData(msg); break; } default: TRESPASS(); } break; } case kWhatStop: { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); for (size_t i = mPlaybackSessions.size(); i-- > 0;) { const sp &playbackSession = mPlaybackSessions.valueAt(i); looper()->unregisterHandler(playbackSession->id()); mPlaybackSessions.removeItemsAt(i); } if (mClient != NULL) { mClient->onDisplayDisconnected(); } status_t err = OK; sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); break; } case kWhatReapDeadClients: { mReaperPending = false; for (size_t i = mPlaybackSessions.size(); i-- > 0;) { const sp &playbackSession = mPlaybackSessions.valueAt(i); if (playbackSession->getLastLifesignUs() + kPlaybackSessionTimeoutUs < ALooper::GetNowUs()) { ALOGI("playback session %d timed out, reaping.", mPlaybackSessions.keyAt(i)); looper()->unregisterHandler(playbackSession->id()); mPlaybackSessions.removeItemsAt(i); } } if (!mPlaybackSessions.isEmpty()) { scheduleReaper(); } break; } case kWhatPlaybackSessionNotify: { int32_t playbackSessionID; CHECK(msg->findInt32("playbackSessionID", &playbackSessionID)); int32_t what; CHECK(msg->findInt32("what", &what)); ssize_t index = mPlaybackSessions.indexOfKey(playbackSessionID); if (index >= 0) { const sp &playbackSession = mPlaybackSessions.valueAt(index); if (what == PlaybackSession::kWhatSessionDead) { ALOGI("playback sessions %d wants to quit.", playbackSessionID); looper()->unregisterHandler(playbackSession->id()); mPlaybackSessions.removeItemsAt(index); } else { CHECK_EQ(what, PlaybackSession::kWhatBinaryData); int32_t channel; CHECK(msg->findInt32("channel", &channel)); sp data; CHECK(msg->findBuffer("data", &data)); CHECK_LE(channel, 0xffu); CHECK_LE(data->size(), 0xffffu); int32_t sessionID; CHECK(msg->findInt32("sessionID", &sessionID)); char header[4]; header[0] = '$'; header[1] = channel; header[2] = data->size() >> 8; header[3] = data->size() & 0xff; mNetSession->sendRequest( sessionID, header, sizeof(header)); mNetSession->sendRequest( sessionID, data->data(), data->size()); } } break; } case kWhatKeepAlive: { int32_t sessionID; CHECK(msg->findInt32("sessionID", &sessionID)); if (mClientInfos.indexOfKey(sessionID) < 0) { // Obsolete event, client is already gone. break; } sendM16(sessionID); break; } default: TRESPASS(); } } void WifiDisplaySource::registerResponseHandler( int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) { ResponseID id; id.mSessionID = sessionID; id.mCSeq = cseq; mResponseHandlers.add(id, func); } status_t WifiDisplaySource::sendM1(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, &WifiDisplaySource::onReceiveM1Response); ++mNextCSeq; return OK; } status_t WifiDisplaySource::sendM3(int32_t sessionID) { AString body = "wfd_video_formats\r\n" "wfd_audio_codecs\r\n" "wfd_client_rtp_ports\r\n"; AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; AppendCommonResponse(&request, mNextCSeq); request.append("Content-Type: text/parameters\r\n"); request.append(StringPrintf("Content-Length: %d\r\n", body.size())); request.append("\r\n"); request.append(body); status_t err = mNetSession->sendRequest(sessionID, request.c_str(), request.size()); if (err != OK) { return err; } registerResponseHandler( sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM3Response); ++mNextCSeq; return OK; } status_t WifiDisplaySource::sendM4(int32_t sessionID) { // wfd_video_formats: // 1 byte "native" // 1 byte "preferred-display-mode-supported" 0 or 1 // one or more avc codec structures // 1 byte profile // 1 byte level // 4 byte CEA mask // 4 byte VESA mask // 4 byte HH mask // 1 byte latency // 2 byte min-slice-slice // 2 byte slice-enc-params // 1 byte framerate-control-support // max-hres (none or 2 byte) // max-vres (none or 2 byte) const ClientInfo &info = mClientInfos.valueFor(sessionID); AString body = StringPrintf( "wfd_video_formats: " "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n" "wfd_audio_codecs: AAC 00000001 00\r\n" // 2 ch AAC 48kHz "wfd_presentation_URL: rtsp://%s:%d/wfd1.0/streamid=0 none\r\n" "wfd_client_rtp_ports: RTP/AVP/UDP;unicast 19000 0 mode=play\r\n", info.mLocalIP.c_str(), info.mLocalPort); AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; AppendCommonResponse(&request, mNextCSeq); request.append("Content-Type: text/parameters\r\n"); request.append(StringPrintf("Content-Length: %d\r\n", body.size())); request.append("\r\n"); request.append(body); status_t err = mNetSession->sendRequest(sessionID, request.c_str(), request.size()); if (err != OK) { return err; } registerResponseHandler( sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM4Response); ++mNextCSeq; return OK; } status_t WifiDisplaySource::sendM5(int32_t sessionID) { AString body = "wfd_trigger_method: SETUP\r\n"; AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; AppendCommonResponse(&request, mNextCSeq); request.append("Content-Type: text/parameters\r\n"); request.append(StringPrintf("Content-Length: %d\r\n", body.size())); request.append("\r\n"); request.append(body); status_t err = mNetSession->sendRequest(sessionID, request.c_str(), request.size()); if (err != OK) { return err; } registerResponseHandler( sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM5Response); ++mNextCSeq; return OK; } status_t WifiDisplaySource::sendM16(int32_t sessionID) { AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; AppendCommonResponse(&request, mNextCSeq); const ClientInfo &info = mClientInfos.valueFor(sessionID); request.append(StringPrintf("Session: %d\r\n", info.mPlaybackSessionID)); request.append("Content-Length: 0\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, &WifiDisplaySource::onReceiveM16Response); ++mNextCSeq; return OK; } status_t WifiDisplaySource::onReceiveM1Response( int32_t sessionID, const sp &msg) { int32_t statusCode; if (!msg->getStatusCode(&statusCode)) { return ERROR_MALFORMED; } if (statusCode != 200) { return ERROR_UNSUPPORTED; } return OK; } status_t WifiDisplaySource::onReceiveM3Response( int32_t sessionID, const sp &msg) { int32_t statusCode; if (!msg->getStatusCode(&statusCode)) { return ERROR_MALFORMED; } if (statusCode != 200) { return ERROR_UNSUPPORTED; } return sendM4(sessionID); } status_t WifiDisplaySource::onReceiveM4Response( int32_t sessionID, const sp &msg) { int32_t statusCode; if (!msg->getStatusCode(&statusCode)) { return ERROR_MALFORMED; } if (statusCode != 200) { return ERROR_UNSUPPORTED; } return sendM5(sessionID); } status_t WifiDisplaySource::onReceiveM5Response( int32_t sessionID, const sp &msg) { int32_t statusCode; if (!msg->getStatusCode(&statusCode)) { return ERROR_MALFORMED; } if (statusCode != 200) { return ERROR_UNSUPPORTED; } return OK; } status_t WifiDisplaySource::onReceiveM16Response( int32_t sessionID, const sp &msg) { // If only the response was required to include a "Session:" header... const ClientInfo &info = mClientInfos.valueFor(sessionID); ssize_t index = mPlaybackSessions.indexOfKey(info.mPlaybackSessionID); if (index >= 0) { mPlaybackSessions.valueAt(index)->updateLiveness(); scheduleKeepAlive(sessionID); } return OK; } void WifiDisplaySource::scheduleReaper() { if (mReaperPending) { return; } mReaperPending = true; (new AMessage(kWhatReapDeadClients, id()))->post(kReaperIntervalUs); } void WifiDisplaySource::scheduleKeepAlive(int32_t sessionID) { // We need to send updates at least 5 secs before the timeout is set to // expire, make sure the timeout is greater than 5 secs to begin with. CHECK_GT(kPlaybackSessionTimeoutUs, 5000000ll); sp msg = new AMessage(kWhatKeepAlive, id()); msg->setInt32("sessionID", sessionID); msg->post(kPlaybackSessionTimeoutUs - 5000000ll); } void WifiDisplaySource::onReceiveClientData(const sp &msg) { int32_t sessionID; CHECK(msg->findInt32("sessionID", &sessionID)); sp obj; CHECK(msg->findObject("data", &obj)); sp data = static_cast(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); if (err != OK) { ALOGW("Response handler for session %d, cseq %d returned " "err %d (%s)", sessionID, cseq, err, strerror(-err)); } } else { AString version; data->getRequestField(2, &version); if (!(version == AString("RTSP/1.0"))) { sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq); return; } if (method == "DESCRIBE") { onDescribeRequest(sessionID, cseq, data); } else if (method == "OPTIONS") { onOptionsRequest(sessionID, cseq, data); } else if (method == "SETUP") { onSetupRequest(sessionID, cseq, data); } else if (method == "PLAY") { onPlayRequest(sessionID, cseq, data); } else if (method == "PAUSE") { onPauseRequest(sessionID, cseq, data); } else if (method == "TEARDOWN") { onTeardownRequest(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 WifiDisplaySource::onDescribeRequest( int32_t sessionID, int32_t cseq, const sp &data) { int64_t nowUs = ALooper::GetNowUs(); AString sdp; sdp.append("v=0\r\n"); sdp.append(StringPrintf( "o=- %lld %lld IN IP4 0.0.0.0\r\n", nowUs, nowUs)); sdp.append( "o=- 0 0 IN IP4 127.0.0.0\r\n" "s=Sample\r\n" "c=IN IP4 0.0.0.0\r\n" "b=AS:502\r\n" "t=0 0\r\n" "a=control:*\r\n" "a=range:npt=now-\r\n" "m=video 0 RTP/AVP 33\r\n" "a=rtpmap:33 MP2T/90000\r\n" "a=control:\r\n"); AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq); response.append("Content-Type: application/sdp\r\n"); // response.append("Content-Base: rtsp://0.0.0.0:7236\r\n"); response.append(StringPrintf("Content-Length: %d\r\n", sdp.size())); response.append("\r\n"); response.append(sdp); status_t err = mNetSession->sendRequest(sessionID, response.c_str()); CHECK_EQ(err, (status_t)OK); } void WifiDisplaySource::onOptionsRequest( int32_t sessionID, int32_t cseq, const sp &data) { int32_t playbackSessionID; sp playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession != NULL) { playbackSession->updateLiveness(); } AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq); response.append( "Public: org.wfa.wfd1.0, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, " "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 = sendM3(sessionID); CHECK_EQ(err, (status_t)OK); } void WifiDisplaySource::onSetupRequest( int32_t sessionID, int32_t cseq, const sp &data) { ClientInfo *info = &mClientInfos.editValueFor(sessionID); if (info->mPlaybackSessionID != -1) { // We only support a single playback session per client. // This is due to the reversed keep-alive design in the wfd specs... sendErrorResponse(sessionID, "400 Bad Request", cseq); return; } AString transport; if (!data->findString("transport", &transport)) { sendErrorResponse(sessionID, "400 Bad Request", cseq); return; } bool useInterleavedTCP = false; int clientRtp, clientRtcp; if (transport.startsWith("RTP/AVP/TCP;")) { AString interleaved; if (!ParsedMessage::GetAttribute( transport.c_str(), "interleaved", &interleaved) || sscanf(interleaved.c_str(), "%d-%d", &clientRtp, &clientRtcp) != 2) { sendErrorResponse(sessionID, "400 Bad Request", cseq); return; } useInterleavedTCP = true; } else if (transport.startsWith("RTP/AVP;unicast;") || transport.startsWith("RTP/AVP/UDP;unicast;")) { bool badRequest = false; AString clientPort; if (!ParsedMessage::GetAttribute( transport.c_str(), "client_port", &clientPort)) { badRequest = true; } else if (sscanf(clientPort.c_str(), "%d-%d", &clientRtp, &clientRtcp) == 2) { } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) { // No RTCP. clientRtcp = -1; } else { badRequest = true; } if (badRequest) { sendErrorResponse(sessionID, "400 Bad Request", cseq); return; } #if 1 // The LG dongle doesn't specify client_port=xxx apparently. } else if (transport == "RTP/AVP/UDP;unicast") { clientRtp = 19000; clientRtcp = clientRtp + 1; #endif } else { sendErrorResponse(sessionID, "461 Unsupported Transport", cseq); return; } int32_t playbackSessionID = makeUniquePlaybackSessionID(); sp notify = new AMessage(kWhatPlaybackSessionNotify, id()); notify->setInt32("playbackSessionID", playbackSessionID); notify->setInt32("sessionID", sessionID); sp playbackSession = new PlaybackSession( mNetSession, notify, mClient == NULL /* legacyMode */); looper()->registerHandler(playbackSession); AString uri; data->getRequestField(1, &uri); if (strncasecmp("rtsp://", uri.c_str(), 7)) { sendErrorResponse(sessionID, "400 Bad Request", cseq); return; } if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) { sendErrorResponse(sessionID, "404 Not found", cseq); return; } status_t err = playbackSession->init( info->mRemoteIP.c_str(), clientRtp, clientRtcp, useInterleavedTCP); if (err != OK) { looper()->unregisterHandler(playbackSession->id()); playbackSession.clear(); } switch (err) { case OK: break; case -ENOENT: sendErrorResponse(sessionID, "404 Not Found", cseq); return; default: sendErrorResponse(sessionID, "403 Forbidden", cseq); return; } mPlaybackSessions.add(playbackSessionID, playbackSession); info->mPlaybackSessionID = playbackSessionID; AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); if (useInterleavedTCP) { response.append( StringPrintf( "Transport: RTP/AVP/TCP;interleaved=%d-%d;", clientRtp, clientRtcp)); } else { int32_t serverRtp = playbackSession->getRTPPort(); if (clientRtcp >= 0) { response.append( StringPrintf( "Transport: RTP/AVP;unicast;client_port=%d-%d;" "server_port=%d-%d\r\n", clientRtp, clientRtcp, serverRtp, serverRtp + 1)); } else { response.append( StringPrintf( "Transport: RTP/AVP;unicast;client_port=%d;" "server_port=%d\r\n", clientRtp, serverRtp)); } } response.append("\r\n"); err = mNetSession->sendRequest(sessionID, response.c_str()); CHECK_EQ(err, (status_t)OK); scheduleReaper(); scheduleKeepAlive(sessionID); } void WifiDisplaySource::onPlayRequest( int32_t sessionID, int32_t cseq, const sp &data) { int32_t playbackSessionID; sp playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return; } status_t err = playbackSession->play(); CHECK_EQ(err, (status_t)OK); AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); response.append("Range: npt=now-\r\n"); response.append("\r\n"); err = mNetSession->sendRequest(sessionID, response.c_str()); CHECK_EQ(err, (status_t)OK); if (mClient != NULL) { mClient->onDisplayConnected( playbackSession->getSurfaceTexture(), playbackSession->width(), playbackSession->height(), 0 /* flags */); } } void WifiDisplaySource::onPauseRequest( int32_t sessionID, int32_t cseq, const sp &data) { int32_t playbackSessionID; sp playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return; } status_t err = playbackSession->pause(); CHECK_EQ(err, (status_t)OK); AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); response.append("\r\n"); err = mNetSession->sendRequest(sessionID, response.c_str()); CHECK_EQ(err, (status_t)OK); } void WifiDisplaySource::onTeardownRequest( int32_t sessionID, int32_t cseq, const sp &data) { int32_t playbackSessionID; sp playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return; } looper()->unregisterHandler(playbackSession->id()); mPlaybackSessions.removeItem(playbackSessionID); AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); response.append("\r\n"); status_t err = mNetSession->sendRequest(sessionID, response.c_str()); CHECK_EQ(err, (status_t)OK); } void WifiDisplaySource::onGetParameterRequest( int32_t sessionID, int32_t cseq, const sp &data) { int32_t playbackSessionID; sp playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return; } playbackSession->updateLiveness(); AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); response.append("\r\n"); status_t err = mNetSession->sendRequest(sessionID, response.c_str()); CHECK_EQ(err, (status_t)OK); } void WifiDisplaySource::onSetParameterRequest( int32_t sessionID, int32_t cseq, const sp &data) { int32_t playbackSessionID; #if 0 // XXX the dongle does not include a "Session:" header in this request. sp playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return; } #else CHECK_EQ(mPlaybackSessions.size(), 1u); playbackSessionID = mPlaybackSessions.keyAt(0); sp playbackSession = mPlaybackSessions.valueAt(0); #endif playbackSession->updateLiveness(); AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); response.append("\r\n"); status_t err = mNetSession->sendRequest(sessionID, response.c_str()); CHECK_EQ(err, (status_t)OK); } // static void WifiDisplaySource::AppendCommonResponse( AString *response, int32_t cseq, int32_t playbackSessionID) { 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("Server: Mine/1.0\r\n"); if (cseq >= 0) { response->append(StringPrintf("CSeq: %d\r\n", cseq)); } if (playbackSessionID >= 0ll) { response->append( StringPrintf( "Session: %d;timeout=%lld\r\n", playbackSessionID, kPlaybackSessionTimeoutSecs)); } } void WifiDisplaySource::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); } int32_t WifiDisplaySource::makeUniquePlaybackSessionID() const { for (;;) { int32_t playbackSessionID = rand(); if (playbackSessionID == -1) { // reserved. continue; } for (size_t i = 0; i < mPlaybackSessions.size(); ++i) { if (mPlaybackSessions.keyAt(i) == playbackSessionID) { continue; } } return playbackSessionID; } } sp WifiDisplaySource::findPlaybackSession( const sp &data, int32_t *playbackSessionID) const { if (!data->findInt32("session", playbackSessionID)) { *playbackSessionID = 0; return NULL; } ssize_t index = mPlaybackSessions.indexOfKey(*playbackSessionID); if (index < 0) { return NULL; } return mPlaybackSessions.valueAt(index); } } // namespace android