status_t ASessionDescription::getSessionUrl(String8& uri) const{ AString line; if(findAttribute(0, "a=control", &line)) { // rtsp without aggregation control url will be considered as pure RTP if (!line.startsWith("rtsp://")) return ERROR_UNSUPPORTED; uri.setTo(line.c_str()); } else { // assume as rtp streaming uri.setTo("rtp://0.0.0.0"); } return OK; }
status_t MediaCodecList::includeXMLFile(const char **attrs) { const char *href = NULL; size_t i = 0; while (attrs[i] != NULL) { if (!strcmp(attrs[i], "href")) { if (attrs[i + 1] == NULL) { return -EINVAL; } href = attrs[i + 1]; ++i; } else { return -EINVAL; } ++i; } // For security reasons and for simplicity, file names can only contain // [a-zA-Z0-9_.] and must start with media_codecs_ and end with .xml for (i = 0; href[i] != '\0'; i++) { if (href[i] == '.' || href[i] == '_' || (href[i] >= '0' && href[i] <= '9') || (href[i] >= 'A' && href[i] <= 'Z') || (href[i] >= 'a' && href[i] <= 'z')) { continue; } ALOGE("invalid include file name: %s", href); return -EINVAL; } AString filename = href; if (!filename.startsWith("media_codecs_") || !filename.endsWith(".xml")) { ALOGE("invalid include file name: %s", href); return -EINVAL; } filename.insert(mHrefBase, 0); parseXMLFile(filename.c_str()); return mInitCheck; }
static sp<AMessage> getMeasureFormat( bool isEncoder, const AString &mime, const sp<MediaCodecInfo::Capabilities> &caps) { sp<AMessage> format = new AMessage(); format->setString("mime", mime); if (isEncoder) { int32_t bitrate = 0; getMeasureBitrate(caps, &bitrate); format->setInt32("bitrate", bitrate); format->setInt32("encoder", 1); } if (mime.startsWith("video/")) { int32_t width = 0; int32_t height = 0; if (!getMeasureSize(caps, &width, &height)) { return NULL; } format->setInt32("width", width); format->setInt32("height", height); Vector<uint32_t> colorFormats; caps->getSupportedColorFormats(&colorFormats); if (colorFormats.size() == 0) { return NULL; } format->setInt32("color-format", colorFormats[0]); format->setFloat("frame-rate", 10.0); format->setInt32("i-frame-interval", 10); } else { // TODO: profile hw audio return NULL; } return format; }
// static AMPEG4ElementaryAssembler::AMPEG4ElementaryAssembler( const sp<AMessage> ¬ify, const AString &desc, const AString ¶ms) : mNotifyMsg(notify), mIsGeneric(false), mParams(params), mSizeLength(0), mIndexLength(0), mIndexDeltaLength(0), mCTSDeltaLength(0), mDTSDeltaLength(0), mRandomAccessIndication(false), mStreamStateIndication(0), mAuxiliaryDataSizeLength(0), mHasAUHeader(false), mAccessUnitRTPTime(0), mNextExpectedSeqNoValid(false), mNextExpectedSeqNo(0), mAccessUnitDamaged(false) { mIsGeneric = desc.startsWith("mpeg4-generic/"); if (mIsGeneric) { AString value; CHECK(GetAttribute(params.c_str(), "mode", &value)); if (!GetIntegerAttribute(params.c_str(), "sizeLength", &mSizeLength)) { mSizeLength = 0; } if (!GetIntegerAttribute( params.c_str(), "indexLength", &mIndexLength)) { mIndexLength = 0; } if (!GetIntegerAttribute( params.c_str(), "indexDeltaLength", &mIndexDeltaLength)) { mIndexDeltaLength = 0; } if (!GetIntegerAttribute( params.c_str(), "CTSDeltaLength", &mCTSDeltaLength)) { mCTSDeltaLength = 0; } if (!GetIntegerAttribute( params.c_str(), "DTSDeltaLength", &mDTSDeltaLength)) { mDTSDeltaLength = 0; } unsigned x; if (!GetIntegerAttribute( params.c_str(), "randomAccessIndication", &x)) { mRandomAccessIndication = false; } else { CHECK(x == 0 || x == 1); mRandomAccessIndication = (x != 0); } if (!GetIntegerAttribute( params.c_str(), "streamStateIndication", &mStreamStateIndication)) { mStreamStateIndication = 0; } if (!GetIntegerAttribute( params.c_str(), "auxiliaryDataSizeLength", &mAuxiliaryDataSizeLength)) { mAuxiliaryDataSizeLength = 0; } mHasAUHeader = mSizeLength > 0 || mIndexLength > 0 || mIndexDeltaLength > 0 || mCTSDeltaLength > 0 || mDTSDeltaLength > 0 || mRandomAccessIndication || mStreamStateIndication > 0; } }
status_t M3UParser::parse(const void *_data, size_t size) { int32_t lineNo = 0; sp<AMessage> itemMeta; const char *data = (const char *)_data; size_t offset = 0; while (offset < size) { size_t offsetLF = offset; while (offsetLF < size && data[offsetLF] != '\n') { ++offsetLF; } if (offsetLF >= size) { break; } AString line; if (offsetLF > offset && data[offsetLF - 1] == '\r') { line.setTo(&data[offset], offsetLF - offset - 1); } else { line.setTo(&data[offset], offsetLF - offset); } // LOGI("#%s#", line.c_str()); if (line.empty()) { offset = offsetLF + 1; continue; } if (lineNo == 0 && line == "#EXTM3U") { mIsExtM3U = true; } if (mIsExtM3U) { status_t err = OK; if (line.startsWith("#EXT-X-TARGETDURATION")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &mMeta, "target-duration"); } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &mMeta, "media-sequence"); } else if (line.startsWith("#EXT-X-ENDLIST")) { mIsComplete = true; } else if (line.startsWith("#EXTINF")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &itemMeta, "duration"); } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } if (itemMeta == NULL) { itemMeta = new AMessage; } itemMeta->setInt32("discontinuity", true); } else if (line.startsWith("#EXT-X-STREAM-INF")) { if (mMeta != NULL) { return ERROR_MALFORMED; } mIsVariantPlaylist = true; err = parseStreamInf(line, &itemMeta); } if (err != OK) { return err; } } if (!line.startsWith("#")) { if (!mIsVariantPlaylist) { int32_t durationSecs; if (itemMeta == NULL || !itemMeta->findInt32("duration", &durationSecs)) { return ERROR_MALFORMED; } } mItems.push(); Item *item = &mItems.editItemAt(mItems.size() - 1); CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); item->mMeta = itemMeta; itemMeta.clear(); } offset = offsetLF + 1; ++lineNo; } return OK; }
void profileCodecs( const Vector<sp<MediaCodecInfo>> &infos, CodecSettings *global_results, KeyedVector<AString, CodecSettings> *encoder_results, KeyedVector<AString, CodecSettings> *decoder_results, bool forceToMeasure) { KeyedVector<AString, sp<MediaCodecInfo::Capabilities>> codecsNeedMeasure; AString supportMultipleSecureCodecs = "true"; size_t maxEncoderInputBuffers = 0; for (size_t i = 0; i < infos.size(); ++i) { const sp<MediaCodecInfo> info = infos[i]; AString name = info->getCodecName(); if (name.startsWith("OMX.google.") || // TODO: reenable below codecs once fixed name == "OMX.Intel.VideoDecoder.VP9.hybrid") { continue; } Vector<AString> mimes; info->getSupportedMimes(&mimes); for (size_t i = 0; i < mimes.size(); ++i) { const sp<MediaCodecInfo::Capabilities> &caps = info->getCapabilitiesFor(mimes[i].c_str()); if (!forceToMeasure && (caps->getDetails()->contains("max-supported-instances") || caps->getDetails()->contains("max-concurrent-instances"))) { continue; } size_t max = doProfileCodecs(info->isEncoder(), name, mimes[i], caps); if (max > 0) { CodecSettings settings; char maxStr[32]; sprintf(maxStr, "%zu", max); settings.add("max-supported-instances", maxStr); AString key = name; key.append(" "); key.append(mimes[i]); if (info->isEncoder()) { encoder_results->add(key, settings); } else { decoder_results->add(key, settings); } if (name.endsWith(".secure")) { if (max <= 1) { supportMultipleSecureCodecs = "false"; } } if (info->isEncoder() && mimes[i].startsWith("video/")) { size_t encoderInputBuffers = doProfileEncoderInputBuffers(name, mimes[i], caps); if (encoderInputBuffers > maxEncoderInputBuffers) { maxEncoderInputBuffers = encoderInputBuffers; } } } } } if (maxEncoderInputBuffers > 0) { char tmp[32]; sprintf(tmp, "%zu", maxEncoderInputBuffers); global_results->add(kMaxEncoderInputBuffers, tmp); } global_results->add(kPolicySupportsMultipleSecureCodecs, supportMultipleSecureCodecs); }
status_t WifiDisplaySource::onReceiveM3Response( int32_t sessionID, const sp<ParsedMessage> &msg) { int32_t statusCode; if (!msg->getStatusCode(&statusCode)) { return ERROR_MALFORMED; } if (statusCode != 200) { return ERROR_UNSUPPORTED; } sp<Parameters> params = Parameters::Parse(msg->getContent(), strlen(msg->getContent())); if (params == NULL) { return ERROR_MALFORMED; } AString value; if (!params->findParameter("wfd_client_rtp_ports", &value)) { ALOGE("Sink doesn't report its choice of wfd_client_rtp_ports."); return ERROR_MALFORMED; } unsigned port0 = 0, port1 = 0; if (sscanf(value.c_str(), "RTP/AVP/UDP;unicast %u %u mode=play", &port0, &port1) == 2 || sscanf(value.c_str(), "RTP/AVP/TCP;unicast %u %u mode=play", &port0, &port1) == 2) { if (port0 == 0 || port0 > 65535 || port1 != 0) { ALOGE("Sink chose its wfd_client_rtp_ports poorly (%s)", value.c_str()); return ERROR_MALFORMED; } } else if (strcmp(value.c_str(), "RTP/AVP/TCP;interleaved mode=play")) { ALOGE("Unsupported value for wfd_client_rtp_ports (%s)", value.c_str()); return ERROR_UNSUPPORTED; } mWfdClientRtpPorts = value; mChosenRTPPort = port0; if (!params->findParameter("wfd_video_formats", &value)) { ALOGE("Sink doesn't report its choice of wfd_video_formats."); return ERROR_MALFORMED; } mSinkSupportsVideo = false; if (!(value == "none")) { mSinkSupportsVideo = true; if (!mSupportedSinkVideoFormats.parseFormatSpec(value.c_str())) { ALOGE("Failed to parse sink provided wfd_video_formats (%s)", value.c_str()); return ERROR_MALFORMED; } if (!VideoFormats::PickBestFormat( mSupportedSinkVideoFormats, mSupportedSourceVideoFormats, &mChosenVideoResolutionType, &mChosenVideoResolutionIndex, &mChosenVideoProfile, &mChosenVideoLevel)) { ALOGE("Sink and source share no commonly supported video " "formats."); return ERROR_UNSUPPORTED; } size_t width, height, framesPerSecond; bool interlaced; CHECK(VideoFormats::GetConfiguration( mChosenVideoResolutionType, mChosenVideoResolutionIndex, &width, &height, &framesPerSecond, &interlaced)); ALOGI("Picked video resolution %zu x %zu %c%zu", width, height, interlaced ? 'i' : 'p', framesPerSecond); ALOGI("Picked AVC profile %d, level %d", mChosenVideoProfile, mChosenVideoLevel); } else { ALOGI("Sink doesn't support video at all."); } if (!params->findParameter("wfd_audio_codecs", &value)) { ALOGE("Sink doesn't report its choice of wfd_audio_codecs."); return ERROR_MALFORMED; } mSinkSupportsAudio = false; if (!(value == "none")) { mSinkSupportsAudio = true; uint32_t modes; GetAudioModes(value.c_str(), "AAC", &modes); bool supportsAAC = (modes & 1) != 0; // AAC 2ch 48kHz GetAudioModes(value.c_str(), "LPCM", &modes); bool supportsPCM = (modes & 2) != 0; // LPCM 2ch 48kHz if (supportsPCM && property_get_bool("media.wfd.use-pcm-audio", false)) { ALOGI("Using PCM audio."); mUsingPCMAudio = true; } else if (supportsAAC) { ALOGI("Using AAC audio."); mUsingPCMAudio = false; } else if (supportsPCM) { ALOGI("Using PCM audio."); mUsingPCMAudio = true; } else { ALOGI("Sink doesn't support an audio format we do."); return ERROR_UNSUPPORTED; } } else { ALOGI("Sink doesn't support audio at all."); } if (!mSinkSupportsVideo && !mSinkSupportsAudio) { ALOGE("Sink supports neither video nor audio..."); return ERROR_UNSUPPORTED; } mUsingHDCP = false; if (!params->findParameter("wfd_content_protection", &value)) { ALOGI("Sink doesn't appear to support content protection."); } else if (value == "none") { ALOGI("Sink does not support content protection."); } else { mUsingHDCP = true; bool isHDCP2_0 = false; if (value.startsWith("HDCP2.0 ")) { isHDCP2_0 = true; } else if (!value.startsWith("HDCP2.1 ")) { ALOGE("malformed wfd_content_protection: '%s'", value.c_str()); return ERROR_MALFORMED; } int32_t hdcpPort; if (!ParsedMessage::GetInt32Attribute( value.c_str() + 8, "port", &hdcpPort) || hdcpPort < 1 || hdcpPort > 65535) { return ERROR_MALFORMED; } mIsHDCP2_0 = isHDCP2_0; mHDCPPort = hdcpPort; status_t err = makeHDCP(); if (err != OK) { ALOGE("Unable to instantiate HDCP component. " "Not using HDCP after all."); mUsingHDCP = false; } } return sendM4(sessionID); }
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::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 ERROR_MALFORMED; } 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 ERROR_MALFORMED; } 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)); return err; } return OK; } AString version; data->getRequestField(2, &version); if (!(version == AString("RTSP/1.0"))) { sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq); return ERROR_UNSUPPORTED; } status_t err; if (method == "OPTIONS") { err = onOptionsRequest(sessionID, cseq, data); } else if (method == "SETUP") { err = onSetupRequest(sessionID, cseq, data); } else if (method == "PLAY") { err = onPlayRequest(sessionID, cseq, data); } else if (method == "PAUSE") { err = onPauseRequest(sessionID, cseq, data); } else if (method == "TEARDOWN") { err = onTeardownRequest(sessionID, cseq, data); } else if (method == "GET_PARAMETER") { err = onGetParameterRequest(sessionID, cseq, data); } else if (method == "SET_PARAMETER") { err = onSetParameterRequest(sessionID, cseq, data); } else { sendErrorResponse(sessionID, "405 Method Not Allowed", cseq); err = ERROR_UNSUPPORTED; } return err; }
status_t WifiDisplaySource::onReceiveM3Response( int32_t sessionID, const sp<ParsedMessage> &msg) { int32_t statusCode; if (!msg->getStatusCode(&statusCode)) { return ERROR_MALFORMED; } if (statusCode != 200) { return ERROR_UNSUPPORTED; } sp<Parameters> params = Parameters::Parse(msg->getContent(), strlen(msg->getContent())); if (params == NULL) { return ERROR_MALFORMED; } AString value; if (!params->findParameter("wfd_client_rtp_ports", &value)) { ALOGE("Sink doesn't report its choice of wfd_client_rtp_ports."); return ERROR_MALFORMED; } unsigned port0, port1; if (sscanf(value.c_str(), "RTP/AVP/UDP;unicast %u %u mode=play", &port0, &port1) != 2 || port0 == 0 || port0 > 65535 || port1 != 0) { ALOGE("Sink chose its wfd_client_rtp_ports poorly (%s)", value.c_str()); ALOGE("onReceiveM3Response() SKIP!! port check."); port0 = 19000; port1 = 0; //return ERROR_MALFORMED; } mChosenRTPPort = port0; if (!params->findParameter("wfd_audio_codecs", &value)) { ALOGE("Sink doesn't report its choice of wfd_audio_codecs."); return ERROR_MALFORMED; } if (value == "none") { ALOGE("Sink doesn't support audio at all."); return ERROR_UNSUPPORTED; } if (value == "xxx") { ALOGE("onReceiveM3Response() Force Apply wfd_audio_codecs to AAC"); value.clear(); value.append("LPCM 00000003 00, AAC 0000000F 00"); } uint32_t modes; GetAudioModes(value.c_str(), "AAC", &modes); bool supportsAAC = (modes & 1) != 0; // AAC 2ch 48kHz GetAudioModes(value.c_str(), "LPCM", &modes); bool supportsPCM = (modes & 2) != 0; // LPCM 2ch 48kHz char val[PROPERTY_VALUE_MAX]; if (supportsPCM && property_get("media.wfd.use-pcm-audio", val, NULL) && (!strcasecmp("true", val) || !strcmp("1", val))) { ALOGI("Using PCM audio."); mUsingPCMAudio = true; } else if (supportsAAC) { ALOGI("Using AAC audio."); mUsingPCMAudio = false; } else if (supportsPCM) { ALOGI("Using PCM audio."); mUsingPCMAudio = true; } else { ALOGI("Sink doesn't support an audio format we do."); return ERROR_UNSUPPORTED; } mUsingHDCP = false; if (!params->findParameter("wfd_content_protection", &value)) { ALOGI("Sink doesn't appear to support content protection."); } else if (value == "none") { ALOGI("Sink does not support content protection."); } else { mUsingHDCP = true; bool isHDCP2_0 = false; if (value.startsWith("HDCP2.0 ")) { isHDCP2_0 = true; } else if (!value.startsWith("HDCP2.1 ")) { ALOGE("malformed wfd_content_protection: '%s'", value.c_str()); return ERROR_MALFORMED; } int32_t hdcpPort; if (!ParsedMessage::GetInt32Attribute( value.c_str() + 8, "port", &hdcpPort) || hdcpPort < 1 || hdcpPort > 65535) { return ERROR_MALFORMED; } mIsHDCP2_0 = isHDCP2_0; mHDCPPort = hdcpPort; status_t err = makeHDCP(); if (err != OK) { ALOGE("Unable to instantiate HDCP component. " "Not using HDCP after all."); mUsingHDCP = false; } } return sendM4(sessionID); }