status_t WifiDisplaySource::sendM16(int32_t sessionID) { AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; AppendCommonResponse(&request, mNextCSeq); CHECK_EQ(sessionID, mClientSessionID); request.append( AStringPrintf("Session: %d\r\n", mClientInfo.mPlaybackSessionID)); request.append("\r\n"); // Empty body status_t err = mNetSession->sendRequest(sessionID, request.c_str(), request.size()); if (err != OK) { return err; } registerResponseHandler( sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM16Response); ++mNextCSeq; scheduleKeepAlive(sessionID); return OK; }
status_t WifiDisplaySource::onSetParameterRequest( int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data) { int32_t playbackSessionID; sp<PlaybackSession> playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return ERROR_MALFORMED; } if (strstr(data->getContent(), "wfd_idr_request\r\n")) { playbackSession->requestIDRFrame(); } 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()); return err; }
status_t WifiDisplaySource::sendM3(int32_t sessionID) { AString body = "wfd_content_protection\r\n" "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(AStringPrintf("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::onOptionsRequest( int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data) { int32_t playbackSessionID; sp<PlaybackSession> 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, SETUP, TEARDOWN, PLAY, PAUSE, " "GET_PARAMETER, SET_PARAMETER\r\n"); response.append("\r\n"); status_t err = mNetSession->sendRequest(sessionID, response.c_str()); if (err == OK) { err = sendM3(sessionID); } return err; }
status_t WifiDisplaySource::onTeardownRequest( int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data) { ALOGI("Received TEARDOWN request."); int32_t playbackSessionID; sp<PlaybackSession> playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return ERROR_MALFORMED; } AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); response.append("Connection: close\r\n"); response.append("\r\n"); mNetSession->sendRequest(sessionID, response.c_str()); if (mState == AWAITING_CLIENT_TEARDOWN) { CHECK(mStopReplyID != NULL); finishStop(); } else { mClient->onDisplayError(IRemoteDisplayClient::kDisplayErrorUnknown); } return OK; }
status_t WifiDisplaySource::sendM4(int32_t sessionID) { CHECK_EQ(sessionID, mClientSessionID); AString body; if (mSinkSupportsVideo) { body.append("wfd_video_formats: "); VideoFormats chosenVideoFormat; chosenVideoFormat.disableAll(); chosenVideoFormat.setNativeResolution( mChosenVideoResolutionType, mChosenVideoResolutionIndex); chosenVideoFormat.setProfileLevel( mChosenVideoResolutionType, mChosenVideoResolutionIndex, mChosenVideoProfile, mChosenVideoLevel); body.append(chosenVideoFormat.getFormatSpec(true /* forM4Message */)); body.append("\r\n"); } if (mSinkSupportsAudio) { body.append( AStringPrintf("wfd_audio_codecs: %s\r\n", (mUsingPCMAudio ? "LPCM 00000002 00" // 2 ch PCM 48kHz : "AAC 00000001 00"))); // 2 ch AAC 48kHz } body.append( AStringPrintf( "wfd_presentation_URL: rtsp://%s/wfd1.0/streamid=0 none\r\n", mClientInfo.mLocalIP.c_str())); body.append( AStringPrintf( "wfd_client_rtp_ports: %s\r\n", mWfdClientRtpPorts.c_str())); 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(AStringPrintf("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::onPlayRequest( int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data) { int32_t playbackSessionID; sp<PlaybackSession> playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return ERROR_MALFORMED; } if (mState != AWAITING_CLIENT_PLAY && mState != PAUSED_TO_PLAYING && mState != PAUSED) { ALOGW("Received PLAY request but we're in state %d", mState); sendErrorResponse( sessionID, "455 Method Not Valid in This State", cseq); return INVALID_OPERATION; } ALOGI("Received PLAY request."); if (mPlaybackSessionEstablished) { finishPlay(); } else { ALOGI("deferring PLAY request until session established."); } AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); response.append("Range: npt=now-\r\n"); response.append("\r\n"); status_t err = mNetSession->sendRequest(sessionID, response.c_str()); if (err != OK) { return err; } if (mState == PAUSED_TO_PLAYING || mPlaybackSessionEstablished) { mState = PLAYING; return OK; } CHECK_EQ(mState, AWAITING_CLIENT_PLAY); mState = ABOUT_TO_PLAY; return OK; }
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"); mNetSession->sendRequest(sessionID, response.c_str()); }
status_t WifiDisplaySource::sendTrigger( int32_t sessionID, TriggerType triggerType) { AString body = "wfd_trigger_method: "; switch (triggerType) { case TRIGGER_SETUP: body.append("SETUP"); break; case TRIGGER_TEARDOWN: ALOGI("Sending TEARDOWN trigger."); body.append("TEARDOWN"); break; case TRIGGER_PAUSE: body.append("PAUSE"); break; case TRIGGER_PLAY: body.append("PLAY"); break; default: TRESPASS(); } body.append("\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(AStringPrintf("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::onPlayRequest( int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data) { int32_t playbackSessionID; sp<PlaybackSession> playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return ERROR_MALFORMED; } ALOGI("Received PLAY request."); 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()); if (err != OK) { return err; } if (mState == PAUSED_TO_PLAYING) { mState = PLAYING; return OK; } playbackSession->finishPlay(); CHECK_EQ(mState, AWAITING_CLIENT_PLAY); mState = ABOUT_TO_PLAY; return OK; }
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::onPauseRequest( int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data) { int32_t playbackSessionID; sp<PlaybackSession> playbackSession = findPlaybackSession(data, &playbackSessionID); if (playbackSession == NULL) { sendErrorResponse(sessionID, "454 Session Not Found", cseq); return ERROR_MALFORMED; } ALOGI("Received PAUSE request."); if (mState != PLAYING_TO_PAUSED && mState != PLAYING) { return INVALID_OPERATION; } 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()); if (err != OK) { return err; } mState = PAUSED; return err; }
status_t WifiDisplaySource::onSetupRequest( int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data) { CHECK_EQ(sessionID, mClientSessionID); if (mClientInfo.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 ERROR_MALFORMED; } AString transport; if (!data->findString("transport", &transport)) { sendErrorResponse(sessionID, "400 Bad Request", cseq); return ERROR_MALFORMED; } RTPSender::TransportMode rtpMode = RTPSender::TRANSPORT_UDP; 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) { rtpMode = RTPSender::TRANSPORT_TCP_INTERLEAVED; } else { 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 ERROR_MALFORMED; } rtpMode = RTPSender::TRANSPORT_TCP; } } 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 ERROR_MALFORMED; } #if 1 // The older LG dongles doesn't specify client_port=xxx apparently. } else if (transport == "RTP/AVP/UDP;unicast") { clientRtp = 19000; clientRtcp = -1; #endif } else { sendErrorResponse(sessionID, "461 Unsupported Transport", cseq); return ERROR_UNSUPPORTED; } int32_t playbackSessionID = makeUniquePlaybackSessionID(); sp<AMessage> notify = new AMessage(kWhatPlaybackSessionNotify, this); notify->setInt32("playbackSessionID", playbackSessionID); notify->setInt32("sessionID", sessionID); sp<PlaybackSession> playbackSession = new PlaybackSession( mOpPackageName, mNetSession, notify, mInterfaceAddr, mHDCP, mMediaPath.c_str()); looper()->registerHandler(playbackSession); AString uri; data->getRequestField(1, &uri); if (strncasecmp("rtsp://", uri.c_str(), 7)) { sendErrorResponse(sessionID, "400 Bad Request", cseq); return ERROR_MALFORMED; } if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) { sendErrorResponse(sessionID, "404 Not found", cseq); return ERROR_MALFORMED; } RTPSender::TransportMode rtcpMode = RTPSender::TRANSPORT_UDP; if (clientRtcp < 0) { rtcpMode = RTPSender::TRANSPORT_NONE; } status_t err = playbackSession->init( mClientInfo.mRemoteIP.c_str(), clientRtp, rtpMode, clientRtcp, rtcpMode, mSinkSupportsAudio, mUsingPCMAudio, mSinkSupportsVideo, mChosenVideoResolutionType, mChosenVideoResolutionIndex, mChosenVideoProfile, mChosenVideoLevel); if (err != OK) { looper()->unregisterHandler(playbackSession->id()); playbackSession.clear(); } switch (err) { case OK: break; case -ENOENT: sendErrorResponse(sessionID, "404 Not Found", cseq); return err; default: sendErrorResponse(sessionID, "403 Forbidden", cseq); return err; } mClientInfo.mPlaybackSessionID = playbackSessionID; mClientInfo.mPlaybackSession = playbackSession; AString response = "RTSP/1.0 200 OK\r\n"; AppendCommonResponse(&response, cseq, playbackSessionID); if (rtpMode == RTPSender::TRANSPORT_TCP_INTERLEAVED) { response.append( AStringPrintf( "Transport: RTP/AVP/TCP;interleaved=%d-%d;", clientRtp, clientRtcp)); } else { int32_t serverRtp = playbackSession->getRTPPort(); AString transportString = "UDP"; if (rtpMode == RTPSender::TRANSPORT_TCP) { transportString = "TCP"; } if (clientRtcp >= 0) { response.append( AStringPrintf( "Transport: RTP/AVP/%s;unicast;client_port=%d-%d;" "server_port=%d-%d\r\n", transportString.c_str(), clientRtp, clientRtcp, serverRtp, serverRtp + 1)); } else { response.append( AStringPrintf( "Transport: RTP/AVP/%s;unicast;client_port=%d;" "server_port=%d\r\n", transportString.c_str(), clientRtp, serverRtp)); } } response.append("\r\n"); err = mNetSession->sendRequest(sessionID, response.c_str()); if (err != OK) { return err; } mState = AWAITING_CLIENT_PLAY; scheduleReaper(); scheduleKeepAlive(sessionID); 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) CHECK_EQ(sessionID, mClientSessionID); AString transportString = "UDP"; char val[PROPERTY_VALUE_MAX]; if (property_get("media.wfd.enable-tcp", val, NULL) && (!strcasecmp("true", val) || !strcmp("1", val))) { ALOGI("Using TCP transport."); transportString = "TCP"; } // For 720p60: // use "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n" // For 720p30: // use "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n" // For 720p24: // use "78 00 02 02 00008000 00000000 00000000 00 0000 0000 00 none none\r\n" // For 1080p30: // use "38 00 02 02 00000080 00000000 00000000 00 0000 0000 00 none none\r\n" AString body = StringPrintf( "wfd_video_formats: " #if defined(USE_1080P) "38 00 02 02 00000080 00000000 00000000 00 0000 0000 00 none none\r\n" #elif defined(USE_480P) "08 00 02 02 00000010 00000000 00000000 00 0000 0000 00 none none\r\n" #else //if defined(USE_720P) default "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n" #endif "wfd_audio_codecs: %s\r\n" "wfd_presentation_URL: rtsp://%s/wfd1.0/streamid=0 none\r\n" "wfd_client_rtp_ports: RTP/AVP/%s;unicast %d 0 mode=play\r\n", (mUsingPCMAudio ? "LPCM 00000002 00" // 2 ch PCM 48kHz : "AAC 00000001 00"), // 2 ch AAC 48kHz mClientInfo.mLocalIP.c_str(), transportString.c_str(), mChosenRTPPort); 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; }