static AString codecResultsToXml(const KeyedVector<AString, CodecSettings> &results) { AString ret; for (size_t i = 0; i < results.size(); ++i) { AString name; AString mime; if (!splitString(results.keyAt(i), " ", &name, &mime)) { continue; } AString codec = AStringPrintf(" <MediaCodec name=\"%s\" type=\"%s\" update=\"true\" >\n", name.c_str(), mime.c_str()); ret.append(codec); const CodecSettings &settings = results.valueAt(i); for (size_t i = 0; i < settings.size(); ++i) { // WARNING: we assume all the settings are "Limit". Currently we have only one type // of setting in this case, which is "max-supported-instances". AString setting = AStringPrintf( " <Limit name=\"%s\" value=\"%s\" />\n", settings.keyAt(i).c_str(), settings.valueAt(i).c_str()); ret.append(setting); } ret.append(" </MediaCodec>\n"); } return ret; }
TEST_F(TimedTextSRTSourceTest, readAll) { for (int i = 1; i <= 5; i++) { err = mSource->read(&startTimeUs, &endTimeUs, &parcel); EXPECT_EQ(OK, err); CheckStartTimeMs(parcel, i * kSecToMsec); subtitle = AStringPrintf("%d\n\n", i); CheckDataEquals(parcel, subtitle.c_str()); } // read edge cases err = mSource->read(&startTimeUs, &endTimeUs, &parcel); EXPECT_EQ(OK, err); CheckStartTimeMs(parcel, 5500); subtitle = AStringPrintf("6\n\n"); CheckDataEquals(parcel, subtitle.c_str()); err = mSource->read(&startTimeUs, &endTimeUs, &parcel); EXPECT_EQ(OK, err); CheckStartTimeMs(parcel, 5800); subtitle = AStringPrintf("7\n\n"); CheckDataEquals(parcel, subtitle.c_str()); err = mSource->read(&startTimeUs, &endTimeUs, &parcel); EXPECT_EQ(OK, err); CheckStartTimeMs(parcel, 6000); subtitle = AStringPrintf("8\n\n"); CheckDataEquals(parcel, subtitle.c_str()); err = mSource->read(&startTimeUs, &endTimeUs, &parcel); EXPECT_EQ(ERROR_END_OF_STREAM, err); }
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::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::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; }
AString getProfilingVersionString() { char val[PROPERTY_VALUE_MAX]; if (property_get("ro.build.display.id", val, NULL) && (strlen(val) > 0)) { return AStringPrintf("<!-- Profiled-with: %s -->", val); } return "<!-- Profiled-with: UNKNOWN_BUILD_ID -->"; }
static AString globalResultsToXml(const CodecSettings &results) { AString ret; for (size_t i = 0; i < results.size(); ++i) { AString setting = AStringPrintf( " <Setting name=\"%s\" value=\"%s\" />\n", results.keyAt(i).c_str(), results.valueAt(i).c_str()); ret.append(setting); } return ret; }
TEST_F(TimedTextSRTSourceTest, checkEdgeCase) { MediaSource::ReadOptions options; options.setSeekTo(5500 * kMsecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); EXPECT_EQ(OK, err); EXPECT_EQ(5500 * kMsecToUsec, startTimeUs); subtitle = AStringPrintf("6\n\n"); CheckDataEquals(parcel, subtitle.c_str()); options.setSeekTo(5800 * kMsecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); EXPECT_EQ(OK, err); EXPECT_EQ(5800 * kMsecToUsec, startTimeUs); subtitle = AStringPrintf("7\n\n"); CheckDataEquals(parcel, subtitle.c_str()); options.setSeekTo(6000 * kMsecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); EXPECT_EQ(OK, err); EXPECT_EQ(6000 * kMsecToUsec, startTimeUs); subtitle = AStringPrintf("8\n\n"); CheckDataEquals(parcel, subtitle.c_str()); }
// 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(AStringPrintf("Server: %s\r\n", sUserAgent.c_str())); if (cseq >= 0) { response->append(AStringPrintf("CSeq: %d\r\n", cseq)); } if (playbackSessionID >= 0ll) { response->append( AStringPrintf( "Session: %d;timeout=%lld\r\n", playbackSessionID, kPlaybackSessionTimeoutSecs)); } }
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::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; }