WifiDisplaySource.cpp revision 0224bf170a3904576bba81593eaab113c5d3a4e7
1/* 2 * Copyright 2012, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17//#define LOG_NDEBUG 0 18#define LOG_TAG "WifiDisplaySource" 19#include <utils/Log.h> 20 21#include "WifiDisplaySource.h" 22#include "PlaybackSession.h" 23#include "Parameters.h" 24#include "ParsedMessage.h" 25#include "Sender.h" 26 27#include <binder/IServiceManager.h> 28#include <gui/ISurfaceTexture.h> 29#include <media/IHDCP.h> 30#include <media/IMediaPlayerService.h> 31#include <media/IRemoteDisplayClient.h> 32#include <media/stagefright/foundation/ABuffer.h> 33#include <media/stagefright/foundation/ADebug.h> 34#include <media/stagefright/foundation/AMessage.h> 35#include <media/stagefright/MediaErrors.h> 36 37#include <arpa/inet.h> 38#include <cutils/properties.h> 39 40#include <ctype.h> 41 42namespace android { 43 44WifiDisplaySource::WifiDisplaySource( 45 const sp<ANetworkSession> &netSession, 46 const sp<IRemoteDisplayClient> &client) 47 : mState(INITIALIZED), 48 mNetSession(netSession), 49 mClient(client), 50 mSessionID(0), 51 mStopReplyID(0), 52 mChosenRTPPort(-1), 53 mUsingPCMAudio(false), 54 mClientSessionID(0), 55 mReaperPending(false), 56 mNextCSeq(1), 57 mUsingHDCP(false), 58 mIsHDCP2_0(false), 59 mHDCPPort(0), 60 mHDCPInitializationComplete(false), 61 mSetupTriggerDeferred(false) 62{ 63} 64 65WifiDisplaySource::~WifiDisplaySource() { 66} 67 68status_t WifiDisplaySource::start(const char *iface) { 69 CHECK_EQ(mState, INITIALIZED); 70 71 sp<AMessage> msg = new AMessage(kWhatStart, id()); 72 msg->setString("iface", iface); 73 74 sp<AMessage> response; 75 status_t err = msg->postAndAwaitResponse(&response); 76 77 if (err != OK) { 78 return err; 79 } 80 81 if (!response->findInt32("err", &err)) { 82 err = OK; 83 } 84 85 return err; 86} 87 88status_t WifiDisplaySource::stop() { 89 sp<AMessage> msg = new AMessage(kWhatStop, id()); 90 91 sp<AMessage> response; 92 status_t err = msg->postAndAwaitResponse(&response); 93 94 if (err != OK) { 95 return err; 96 } 97 98 if (!response->findInt32("err", &err)) { 99 err = OK; 100 } 101 102 return err; 103} 104 105void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) { 106 switch (msg->what()) { 107 case kWhatStart: 108 { 109 uint32_t replyID; 110 CHECK(msg->senderAwaitsResponse(&replyID)); 111 112 AString iface; 113 CHECK(msg->findString("iface", &iface)); 114 115 status_t err = OK; 116 117 ssize_t colonPos = iface.find(":"); 118 119 unsigned long port; 120 121 if (colonPos >= 0) { 122 const char *s = iface.c_str() + colonPos + 1; 123 124 char *end; 125 port = strtoul(s, &end, 10); 126 127 if (end == s || *end != '\0' || port > 65535) { 128 err = -EINVAL; 129 } else { 130 iface.erase(colonPos, iface.size() - colonPos); 131 } 132 } else { 133 port = kWifiDisplayDefaultPort; 134 } 135 136 if (err == OK) { 137 if (inet_aton(iface.c_str(), &mInterfaceAddr) != 0) { 138 sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id()); 139 140 err = mNetSession->createRTSPServer( 141 mInterfaceAddr, port, notify, &mSessionID); 142 } else { 143 err = -EINVAL; 144 } 145 } 146 147 if (err == OK) { 148 mState = AWAITING_CLIENT_CONNECTION; 149 } 150 151 sp<AMessage> response = new AMessage; 152 response->setInt32("err", err); 153 response->postReply(replyID); 154 break; 155 } 156 157 case kWhatRTSPNotify: 158 { 159 int32_t reason; 160 CHECK(msg->findInt32("reason", &reason)); 161 162 switch (reason) { 163 case ANetworkSession::kWhatError: 164 { 165 int32_t sessionID; 166 CHECK(msg->findInt32("sessionID", &sessionID)); 167 168 int32_t err; 169 CHECK(msg->findInt32("err", &err)); 170 171 AString detail; 172 CHECK(msg->findString("detail", &detail)); 173 174 ALOGE("An error occurred in session %d (%d, '%s/%s').", 175 sessionID, 176 err, 177 detail.c_str(), 178 strerror(-err)); 179 180 mNetSession->destroySession(sessionID); 181 182 if (sessionID == mClientSessionID) { 183 mClientSessionID = 0; 184 185 mClient->onDisplayError( 186 IRemoteDisplayClient::kDisplayErrorUnknown); 187 } 188 break; 189 } 190 191 case ANetworkSession::kWhatClientConnected: 192 { 193 int32_t sessionID; 194 CHECK(msg->findInt32("sessionID", &sessionID)); 195 196 if (mClientSessionID > 0) { 197 ALOGW("A client tried to connect, but we already " 198 "have one."); 199 200 mNetSession->destroySession(sessionID); 201 break; 202 } 203 204 CHECK_EQ(mState, AWAITING_CLIENT_CONNECTION); 205 206 CHECK(msg->findString("client-ip", &mClientInfo.mRemoteIP)); 207 CHECK(msg->findString("server-ip", &mClientInfo.mLocalIP)); 208 209 if (mClientInfo.mRemoteIP == mClientInfo.mLocalIP) { 210 // Disallow connections from the local interface 211 // for security reasons. 212 mNetSession->destroySession(sessionID); 213 break; 214 } 215 216 CHECK(msg->findInt32( 217 "server-port", &mClientInfo.mLocalPort)); 218 mClientInfo.mPlaybackSessionID = -1; 219 220 mClientSessionID = sessionID; 221 222 ALOGI("We now have a client (%d) connected.", sessionID); 223 224 mState = AWAITING_CLIENT_SETUP; 225 226 status_t err = sendM1(sessionID); 227 CHECK_EQ(err, (status_t)OK); 228 break; 229 } 230 231 case ANetworkSession::kWhatData: 232 { 233 status_t err = onReceiveClientData(msg); 234 235 if (err != OK) { 236 mClient->onDisplayError( 237 IRemoteDisplayClient::kDisplayErrorUnknown); 238 } 239 break; 240 } 241 242 default: 243 TRESPASS(); 244 } 245 break; 246 } 247 248 case kWhatStop: 249 { 250 CHECK(msg->senderAwaitsResponse(&mStopReplyID)); 251 252 CHECK_LT(mState, AWAITING_CLIENT_TEARDOWN); 253 254 if (mState >= AWAITING_CLIENT_PLAY) { 255 // We have a session, i.e. a previous SETUP succeeded. 256 257 status_t err = sendM5( 258 mClientSessionID, true /* requestShutdown */); 259 260 if (err == OK) { 261 mState = AWAITING_CLIENT_TEARDOWN; 262 263 (new AMessage(kWhatTeardownTriggerTimedOut, id()))->post( 264 kTeardownTriggerTimeouSecs * 1000000ll); 265 266 break; 267 } 268 269 // fall through. 270 } 271 272 finishStop(); 273 break; 274 } 275 276 case kWhatReapDeadClients: 277 { 278 mReaperPending = false; 279 280 if (mClientSessionID == 0 281 || mClientInfo.mPlaybackSession == NULL) { 282 break; 283 } 284 285 if (mClientInfo.mPlaybackSession->getLastLifesignUs() 286 + kPlaybackSessionTimeoutUs < ALooper::GetNowUs()) { 287 ALOGI("playback session timed out, reaping."); 288 289 mNetSession->destroySession(mClientSessionID); 290 mClientSessionID = 0; 291 292 mClient->onDisplayError( 293 IRemoteDisplayClient::kDisplayErrorUnknown); 294 } else { 295 scheduleReaper(); 296 } 297 break; 298 } 299 300 case kWhatPlaybackSessionNotify: 301 { 302 int32_t playbackSessionID; 303 CHECK(msg->findInt32("playbackSessionID", &playbackSessionID)); 304 305 int32_t what; 306 CHECK(msg->findInt32("what", &what)); 307 308 if (what == PlaybackSession::kWhatSessionDead) { 309 ALOGI("playback session wants to quit."); 310 311 mClient->onDisplayError( 312 IRemoteDisplayClient::kDisplayErrorUnknown); 313 } else if (what == PlaybackSession::kWhatSessionEstablished) { 314 if (mClient != NULL) { 315 mClient->onDisplayConnected( 316 mClientInfo.mPlaybackSession->getSurfaceTexture(), 317 mClientInfo.mPlaybackSession->width(), 318 mClientInfo.mPlaybackSession->height(), 319 mUsingHDCP 320 ? IRemoteDisplayClient::kDisplayFlagSecure 321 : 0); 322 } 323 324 if (mState == ABOUT_TO_PLAY) { 325 mState = PLAYING; 326 } 327 } else if (what == PlaybackSession::kWhatSessionDestroyed) { 328 disconnectClient2(); 329 } else { 330 CHECK_EQ(what, PlaybackSession::kWhatBinaryData); 331 332 int32_t channel; 333 CHECK(msg->findInt32("channel", &channel)); 334 335 sp<ABuffer> data; 336 CHECK(msg->findBuffer("data", &data)); 337 338 CHECK_LE(channel, 0xffu); 339 CHECK_LE(data->size(), 0xffffu); 340 341 int32_t sessionID; 342 CHECK(msg->findInt32("sessionID", &sessionID)); 343 344 char header[4]; 345 header[0] = '$'; 346 header[1] = channel; 347 header[2] = data->size() >> 8; 348 header[3] = data->size() & 0xff; 349 350 mNetSession->sendRequest( 351 sessionID, header, sizeof(header)); 352 353 mNetSession->sendRequest( 354 sessionID, data->data(), data->size()); 355 } 356 break; 357 } 358 359 case kWhatKeepAlive: 360 { 361 int32_t sessionID; 362 CHECK(msg->findInt32("sessionID", &sessionID)); 363 364 if (mClientSessionID != sessionID) { 365 // Obsolete event, client is already gone. 366 break; 367 } 368 369 sendM16(sessionID); 370 break; 371 } 372 373 case kWhatTeardownTriggerTimedOut: 374 { 375 if (mState == AWAITING_CLIENT_TEARDOWN) { 376 ALOGI("TEARDOWN trigger timed out, forcing disconnection."); 377 378 CHECK_NE(mStopReplyID, 0); 379 finishStop(); 380 break; 381 } 382 break; 383 } 384 385 case kWhatHDCPNotify: 386 { 387 int32_t msgCode, ext1, ext2; 388 CHECK(msg->findInt32("msg", &msgCode)); 389 CHECK(msg->findInt32("ext1", &ext1)); 390 CHECK(msg->findInt32("ext2", &ext2)); 391 392 ALOGI("Saw HDCP notification code %d, ext1 %d, ext2 %d", 393 msgCode, ext1, ext2); 394 395 switch (msgCode) { 396 case HDCPModule::HDCP_INITIALIZATION_COMPLETE: 397 { 398 mHDCPInitializationComplete = true; 399 400 if (mSetupTriggerDeferred) { 401 mSetupTriggerDeferred = false; 402 403 sendM5(mClientSessionID, false /* requestShutdown */); 404 } 405 break; 406 } 407 408 case HDCPModule::HDCP_SHUTDOWN_COMPLETE: 409 case HDCPModule::HDCP_SHUTDOWN_FAILED: 410 { 411 // Ugly hack to make sure that the call to 412 // HDCPObserver::notify is completely handled before 413 // we clear the HDCP instance and unload the shared 414 // library :( 415 (new AMessage(kWhatFinishStop2, id()))->post(300000ll); 416 break; 417 } 418 419 default: 420 { 421 ALOGE("HDCP failure, shutting down."); 422 423 mClient->onDisplayError( 424 IRemoteDisplayClient::kDisplayErrorUnknown); 425 break; 426 } 427 } 428 break; 429 } 430 431 case kWhatFinishStop2: 432 { 433 finishStop2(); 434 break; 435 } 436 437 default: 438 TRESPASS(); 439 } 440} 441 442void WifiDisplaySource::registerResponseHandler( 443 int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) { 444 ResponseID id; 445 id.mSessionID = sessionID; 446 id.mCSeq = cseq; 447 mResponseHandlers.add(id, func); 448} 449 450status_t WifiDisplaySource::sendM1(int32_t sessionID) { 451 AString request = "OPTIONS * RTSP/1.0\r\n"; 452 AppendCommonResponse(&request, mNextCSeq); 453 454 request.append( 455 "Require: org.wfa.wfd1.0\r\n" 456 "\r\n"); 457 458 status_t err = 459 mNetSession->sendRequest(sessionID, request.c_str(), request.size()); 460 461 if (err != OK) { 462 return err; 463 } 464 465 registerResponseHandler( 466 sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM1Response); 467 468 ++mNextCSeq; 469 470 return OK; 471} 472 473status_t WifiDisplaySource::sendM3(int32_t sessionID) { 474 AString body = 475 "wfd_content_protection\r\n" 476 "wfd_video_formats\r\n" 477 "wfd_audio_codecs\r\n" 478 "wfd_client_rtp_ports\r\n"; 479 480 AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; 481 AppendCommonResponse(&request, mNextCSeq); 482 483 request.append("Content-Type: text/parameters\r\n"); 484 request.append(StringPrintf("Content-Length: %d\r\n", body.size())); 485 request.append("\r\n"); 486 request.append(body); 487 488 status_t err = 489 mNetSession->sendRequest(sessionID, request.c_str(), request.size()); 490 491 if (err != OK) { 492 return err; 493 } 494 495 registerResponseHandler( 496 sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM3Response); 497 498 ++mNextCSeq; 499 500 return OK; 501} 502 503status_t WifiDisplaySource::sendM4(int32_t sessionID) { 504 // wfd_video_formats: 505 // 1 byte "native" 506 // 1 byte "preferred-display-mode-supported" 0 or 1 507 // one or more avc codec structures 508 // 1 byte profile 509 // 1 byte level 510 // 4 byte CEA mask 511 // 4 byte VESA mask 512 // 4 byte HH mask 513 // 1 byte latency 514 // 2 byte min-slice-slice 515 // 2 byte slice-enc-params 516 // 1 byte framerate-control-support 517 // max-hres (none or 2 byte) 518 // max-vres (none or 2 byte) 519 520 CHECK_EQ(sessionID, mClientSessionID); 521 522 AString transportString = "UDP"; 523 524 char val[PROPERTY_VALUE_MAX]; 525 if (property_get("media.wfd.enable-tcp", val, NULL) 526 && (!strcasecmp("true", val) || !strcmp("1", val))) { 527 ALOGI("Using TCP transport."); 528 transportString = "TCP"; 529 } 530 531 // For 720p60: 532 // use "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n" 533 // For 720p30: 534 // use "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n" 535 // For 720p24: 536 // use "78 00 02 02 00008000 00000000 00000000 00 0000 0000 00 none none\r\n" 537 // For 1080p30: 538 // use "38 00 02 02 00000080 00000000 00000000 00 0000 0000 00 none none\r\n" 539 AString body = StringPrintf( 540 "wfd_video_formats: " 541#if USE_1080P 542 "38 00 02 02 00000080 00000000 00000000 00 0000 0000 00 none none\r\n" 543#else 544 "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n" 545#endif 546 "wfd_audio_codecs: %s\r\n" 547 "wfd_presentation_URL: rtsp://%s/wfd1.0/streamid=0 none\r\n" 548 "wfd_client_rtp_ports: RTP/AVP/%s;unicast %d 0 mode=play\r\n", 549 (mUsingPCMAudio 550 ? "LPCM 00000002 00" // 2 ch PCM 48kHz 551 : "AAC 00000001 00"), // 2 ch AAC 48kHz 552 mClientInfo.mLocalIP.c_str(), transportString.c_str(), mChosenRTPPort); 553 554 AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; 555 AppendCommonResponse(&request, mNextCSeq); 556 557 request.append("Content-Type: text/parameters\r\n"); 558 request.append(StringPrintf("Content-Length: %d\r\n", body.size())); 559 request.append("\r\n"); 560 request.append(body); 561 562 status_t err = 563 mNetSession->sendRequest(sessionID, request.c_str(), request.size()); 564 565 if (err != OK) { 566 return err; 567 } 568 569 registerResponseHandler( 570 sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM4Response); 571 572 ++mNextCSeq; 573 574 return OK; 575} 576 577status_t WifiDisplaySource::sendM5(int32_t sessionID, bool requestShutdown) { 578 AString body = "wfd_trigger_method: "; 579 if (requestShutdown) { 580 ALOGI("Sending TEARDOWN trigger."); 581 body.append("TEARDOWN"); 582 } else { 583 body.append("SETUP"); 584 } 585 586 body.append("\r\n"); 587 588 AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; 589 AppendCommonResponse(&request, mNextCSeq); 590 591 request.append("Content-Type: text/parameters\r\n"); 592 request.append(StringPrintf("Content-Length: %d\r\n", body.size())); 593 request.append("\r\n"); 594 request.append(body); 595 596 status_t err = 597 mNetSession->sendRequest(sessionID, request.c_str(), request.size()); 598 599 if (err != OK) { 600 return err; 601 } 602 603 registerResponseHandler( 604 sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM5Response); 605 606 ++mNextCSeq; 607 608 return OK; 609} 610 611status_t WifiDisplaySource::sendM16(int32_t sessionID) { 612 AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; 613 AppendCommonResponse(&request, mNextCSeq); 614 615 CHECK_EQ(sessionID, mClientSessionID); 616 request.append( 617 StringPrintf("Session: %d\r\n", mClientInfo.mPlaybackSessionID)); 618 request.append("\r\n"); // Empty body 619 620 status_t err = 621 mNetSession->sendRequest(sessionID, request.c_str(), request.size()); 622 623 if (err != OK) { 624 return err; 625 } 626 627 registerResponseHandler( 628 sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM16Response); 629 630 ++mNextCSeq; 631 632 return OK; 633} 634 635status_t WifiDisplaySource::onReceiveM1Response( 636 int32_t sessionID, const sp<ParsedMessage> &msg) { 637 int32_t statusCode; 638 if (!msg->getStatusCode(&statusCode)) { 639 return ERROR_MALFORMED; 640 } 641 642 if (statusCode != 200) { 643 return ERROR_UNSUPPORTED; 644 } 645 646 return OK; 647} 648 649// sink_audio_list := ("LPCM"|"AAC"|"AC3" HEXDIGIT*8 HEXDIGIT*2) 650// (", " sink_audio_list)* 651static void GetAudioModes(const char *s, const char *prefix, uint32_t *modes) { 652 *modes = 0; 653 654 size_t prefixLen = strlen(prefix); 655 656 while (*s != '0') { 657 if (!strncmp(s, prefix, prefixLen) && s[prefixLen] == ' ') { 658 unsigned latency; 659 if (sscanf(&s[prefixLen + 1], "%08x %02x", modes, &latency) != 2) { 660 *modes = 0; 661 } 662 663 return; 664 } 665 666 char *commaPos = strchr(s, ','); 667 if (commaPos != NULL) { 668 s = commaPos + 1; 669 670 while (isspace(*s)) { 671 ++s; 672 } 673 } else { 674 break; 675 } 676 } 677} 678 679status_t WifiDisplaySource::onReceiveM3Response( 680 int32_t sessionID, const sp<ParsedMessage> &msg) { 681 int32_t statusCode; 682 if (!msg->getStatusCode(&statusCode)) { 683 return ERROR_MALFORMED; 684 } 685 686 if (statusCode != 200) { 687 return ERROR_UNSUPPORTED; 688 } 689 690 sp<Parameters> params = 691 Parameters::Parse(msg->getContent(), strlen(msg->getContent())); 692 693 if (params == NULL) { 694 return ERROR_MALFORMED; 695 } 696 697 AString value; 698 if (!params->findParameter("wfd_client_rtp_ports", &value)) { 699 ALOGE("Sink doesn't report its choice of wfd_client_rtp_ports."); 700 return ERROR_MALFORMED; 701 } 702 703 unsigned port0, port1; 704 if (sscanf(value.c_str(), 705 "RTP/AVP/UDP;unicast %u %u mode=play", 706 &port0, 707 &port1) != 2 708 || port0 == 0 || port0 > 65535 || port1 != 0) { 709 ALOGE("Sink chose its wfd_client_rtp_ports poorly (%s)", 710 value.c_str()); 711 712 return ERROR_MALFORMED; 713 } 714 715 mChosenRTPPort = port0; 716 717 if (!params->findParameter("wfd_audio_codecs", &value)) { 718 ALOGE("Sink doesn't report its choice of wfd_audio_codecs."); 719 return ERROR_MALFORMED; 720 } 721 722 if (value == "none") { 723 ALOGE("Sink doesn't support audio at all."); 724 return ERROR_UNSUPPORTED; 725 } 726 727 uint32_t modes; 728 GetAudioModes(value.c_str(), "AAC", &modes); 729 730 bool supportsAAC = (modes & 1) != 0; // AAC 2ch 48kHz 731 732 GetAudioModes(value.c_str(), "LPCM", &modes); 733 734 bool supportsPCM = (modes & 2) != 0; // LPCM 2ch 48kHz 735 736 char val[PROPERTY_VALUE_MAX]; 737 if (supportsPCM 738 && property_get("media.wfd.use-pcm-audio", val, NULL) 739 && (!strcasecmp("true", val) || !strcmp("1", val))) { 740 ALOGI("Using PCM audio."); 741 mUsingPCMAudio = true; 742 } else if (supportsAAC) { 743 ALOGI("Using AAC audio."); 744 mUsingPCMAudio = false; 745 } else if (supportsPCM) { 746 ALOGI("Using PCM audio."); 747 mUsingPCMAudio = true; 748 } else { 749 ALOGI("Sink doesn't support an audio format we do."); 750 return ERROR_UNSUPPORTED; 751 } 752 753 mUsingHDCP = false; 754 if (!params->findParameter("wfd_content_protection", &value)) { 755 ALOGI("Sink doesn't appear to support content protection."); 756 } else if (value == "none") { 757 ALOGI("Sink does not support content protection."); 758 } else { 759 mUsingHDCP = true; 760 761 bool isHDCP2_0 = false; 762 if (value.startsWith("HDCP2.0 ")) { 763 isHDCP2_0 = true; 764 } else if (!value.startsWith("HDCP2.1 ")) { 765 ALOGE("malformed wfd_content_protection: '%s'", value.c_str()); 766 767 return ERROR_MALFORMED; 768 } 769 770 int32_t hdcpPort; 771 if (!ParsedMessage::GetInt32Attribute( 772 value.c_str() + 8, "port", &hdcpPort) 773 || hdcpPort < 1 || hdcpPort > 65535) { 774 return ERROR_MALFORMED; 775 } 776 777 mIsHDCP2_0 = isHDCP2_0; 778 mHDCPPort = hdcpPort; 779 780 status_t err = makeHDCP(); 781 if (err != OK) { 782 ALOGE("Unable to instantiate HDCP component. " 783 "Not using HDCP after all."); 784 785 mUsingHDCP = false; 786 } 787 } 788 789 return sendM4(sessionID); 790} 791 792status_t WifiDisplaySource::onReceiveM4Response( 793 int32_t sessionID, const sp<ParsedMessage> &msg) { 794 int32_t statusCode; 795 if (!msg->getStatusCode(&statusCode)) { 796 return ERROR_MALFORMED; 797 } 798 799 if (statusCode != 200) { 800 return ERROR_UNSUPPORTED; 801 } 802 803 if (mUsingHDCP && !mHDCPInitializationComplete) { 804 ALOGI("Deferring SETUP trigger until HDCP initialization completes."); 805 806 mSetupTriggerDeferred = true; 807 return OK; 808 } 809 810 return sendM5(sessionID, false /* requestShutdown */); 811} 812 813status_t WifiDisplaySource::onReceiveM5Response( 814 int32_t sessionID, const sp<ParsedMessage> &msg) { 815 int32_t statusCode; 816 if (!msg->getStatusCode(&statusCode)) { 817 return ERROR_MALFORMED; 818 } 819 820 if (statusCode != 200) { 821 return ERROR_UNSUPPORTED; 822 } 823 824 return OK; 825} 826 827status_t WifiDisplaySource::onReceiveM16Response( 828 int32_t sessionID, const sp<ParsedMessage> &msg) { 829 // If only the response was required to include a "Session:" header... 830 831 CHECK_EQ(sessionID, mClientSessionID); 832 833 if (mClientInfo.mPlaybackSession != NULL) { 834 mClientInfo.mPlaybackSession->updateLiveness(); 835 836 scheduleKeepAlive(sessionID); 837 } 838 839 return OK; 840} 841 842void WifiDisplaySource::scheduleReaper() { 843 if (mReaperPending) { 844 return; 845 } 846 847 mReaperPending = true; 848 (new AMessage(kWhatReapDeadClients, id()))->post(kReaperIntervalUs); 849} 850 851void WifiDisplaySource::scheduleKeepAlive(int32_t sessionID) { 852 // We need to send updates at least 5 secs before the timeout is set to 853 // expire, make sure the timeout is greater than 5 secs to begin with. 854 CHECK_GT(kPlaybackSessionTimeoutUs, 5000000ll); 855 856 sp<AMessage> msg = new AMessage(kWhatKeepAlive, id()); 857 msg->setInt32("sessionID", sessionID); 858 msg->post(kPlaybackSessionTimeoutUs - 5000000ll); 859} 860 861status_t WifiDisplaySource::onReceiveClientData(const sp<AMessage> &msg) { 862 int32_t sessionID; 863 CHECK(msg->findInt32("sessionID", &sessionID)); 864 865 sp<RefBase> obj; 866 CHECK(msg->findObject("data", &obj)); 867 868 sp<ParsedMessage> data = 869 static_cast<ParsedMessage *>(obj.get()); 870 871 ALOGV("session %d received '%s'", 872 sessionID, data->debugString().c_str()); 873 874 AString method; 875 AString uri; 876 data->getRequestField(0, &method); 877 878 int32_t cseq; 879 if (!data->findInt32("cseq", &cseq)) { 880 sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */); 881 return ERROR_MALFORMED; 882 } 883 884 if (method.startsWith("RTSP/")) { 885 // This is a response. 886 887 ResponseID id; 888 id.mSessionID = sessionID; 889 id.mCSeq = cseq; 890 891 ssize_t index = mResponseHandlers.indexOfKey(id); 892 893 if (index < 0) { 894 ALOGW("Received unsolicited server response, cseq %d", cseq); 895 return ERROR_MALFORMED; 896 } 897 898 HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index); 899 mResponseHandlers.removeItemsAt(index); 900 901 status_t err = (this->*func)(sessionID, data); 902 903 if (err != OK) { 904 ALOGW("Response handler for session %d, cseq %d returned " 905 "err %d (%s)", 906 sessionID, cseq, err, strerror(-err)); 907 908 return err; 909 } 910 911 return OK; 912 } 913 914 AString version; 915 data->getRequestField(2, &version); 916 if (!(version == AString("RTSP/1.0"))) { 917 sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq); 918 return ERROR_UNSUPPORTED; 919 } 920 921 status_t err; 922 if (method == "OPTIONS") { 923 err = onOptionsRequest(sessionID, cseq, data); 924 } else if (method == "SETUP") { 925 err = onSetupRequest(sessionID, cseq, data); 926 } else if (method == "PLAY") { 927 err = onPlayRequest(sessionID, cseq, data); 928 } else if (method == "PAUSE") { 929 err = onPauseRequest(sessionID, cseq, data); 930 } else if (method == "TEARDOWN") { 931 err = onTeardownRequest(sessionID, cseq, data); 932 } else if (method == "GET_PARAMETER") { 933 err = onGetParameterRequest(sessionID, cseq, data); 934 } else if (method == "SET_PARAMETER") { 935 err = onSetParameterRequest(sessionID, cseq, data); 936 } else { 937 sendErrorResponse(sessionID, "405 Method Not Allowed", cseq); 938 939 err = ERROR_UNSUPPORTED; 940 } 941 942 return err; 943} 944 945status_t WifiDisplaySource::onOptionsRequest( 946 int32_t sessionID, 947 int32_t cseq, 948 const sp<ParsedMessage> &data) { 949 int32_t playbackSessionID; 950 sp<PlaybackSession> playbackSession = 951 findPlaybackSession(data, &playbackSessionID); 952 953 if (playbackSession != NULL) { 954 playbackSession->updateLiveness(); 955 } 956 957 AString response = "RTSP/1.0 200 OK\r\n"; 958 AppendCommonResponse(&response, cseq); 959 960 response.append( 961 "Public: org.wfa.wfd1.0, SETUP, TEARDOWN, PLAY, PAUSE, " 962 "GET_PARAMETER, SET_PARAMETER\r\n"); 963 964 response.append("\r\n"); 965 966 status_t err = mNetSession->sendRequest(sessionID, response.c_str()); 967 968 if (err == OK) { 969 err = sendM3(sessionID); 970 } 971 972 return err; 973} 974 975status_t WifiDisplaySource::onSetupRequest( 976 int32_t sessionID, 977 int32_t cseq, 978 const sp<ParsedMessage> &data) { 979 CHECK_EQ(sessionID, mClientSessionID); 980 if (mClientInfo.mPlaybackSessionID != -1) { 981 // We only support a single playback session per client. 982 // This is due to the reversed keep-alive design in the wfd specs... 983 sendErrorResponse(sessionID, "400 Bad Request", cseq); 984 return ERROR_MALFORMED; 985 } 986 987 AString transport; 988 if (!data->findString("transport", &transport)) { 989 sendErrorResponse(sessionID, "400 Bad Request", cseq); 990 return ERROR_MALFORMED; 991 } 992 993 Sender::TransportMode transportMode = Sender::TRANSPORT_UDP; 994 995 int clientRtp, clientRtcp; 996 if (transport.startsWith("RTP/AVP/TCP;")) { 997 AString interleaved; 998 if (ParsedMessage::GetAttribute( 999 transport.c_str(), "interleaved", &interleaved) 1000 && sscanf(interleaved.c_str(), "%d-%d", 1001 &clientRtp, &clientRtcp) == 2) { 1002 transportMode = Sender::TRANSPORT_TCP_INTERLEAVED; 1003 } else { 1004 bool badRequest = false; 1005 1006 AString clientPort; 1007 if (!ParsedMessage::GetAttribute( 1008 transport.c_str(), "client_port", &clientPort)) { 1009 badRequest = true; 1010 } else if (sscanf(clientPort.c_str(), "%d-%d", 1011 &clientRtp, &clientRtcp) == 2) { 1012 } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) { 1013 // No RTCP. 1014 clientRtcp = -1; 1015 } else { 1016 badRequest = true; 1017 } 1018 1019 if (badRequest) { 1020 sendErrorResponse(sessionID, "400 Bad Request", cseq); 1021 return ERROR_MALFORMED; 1022 } 1023 1024 transportMode = Sender::TRANSPORT_TCP; 1025 } 1026 } else if (transport.startsWith("RTP/AVP;unicast;") 1027 || transport.startsWith("RTP/AVP/UDP;unicast;")) { 1028 bool badRequest = false; 1029 1030 AString clientPort; 1031 if (!ParsedMessage::GetAttribute( 1032 transport.c_str(), "client_port", &clientPort)) { 1033 badRequest = true; 1034 } else if (sscanf(clientPort.c_str(), "%d-%d", 1035 &clientRtp, &clientRtcp) == 2) { 1036 } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) { 1037 // No RTCP. 1038 clientRtcp = -1; 1039 } else { 1040 badRequest = true; 1041 } 1042 1043 if (badRequest) { 1044 sendErrorResponse(sessionID, "400 Bad Request", cseq); 1045 return ERROR_MALFORMED; 1046 } 1047#if 1 1048 // The older LG dongles doesn't specify client_port=xxx apparently. 1049 } else if (transport == "RTP/AVP/UDP;unicast") { 1050 clientRtp = 19000; 1051 clientRtcp = -1; 1052#endif 1053 } else { 1054 sendErrorResponse(sessionID, "461 Unsupported Transport", cseq); 1055 return ERROR_UNSUPPORTED; 1056 } 1057 1058 int32_t playbackSessionID = makeUniquePlaybackSessionID(); 1059 1060 sp<AMessage> notify = new AMessage(kWhatPlaybackSessionNotify, id()); 1061 notify->setInt32("playbackSessionID", playbackSessionID); 1062 notify->setInt32("sessionID", sessionID); 1063 1064 sp<PlaybackSession> playbackSession = 1065 new PlaybackSession( 1066 mNetSession, notify, mInterfaceAddr, mHDCP); 1067 1068 looper()->registerHandler(playbackSession); 1069 1070 AString uri; 1071 data->getRequestField(1, &uri); 1072 1073 if (strncasecmp("rtsp://", uri.c_str(), 7)) { 1074 sendErrorResponse(sessionID, "400 Bad Request", cseq); 1075 return ERROR_MALFORMED; 1076 } 1077 1078 if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) { 1079 sendErrorResponse(sessionID, "404 Not found", cseq); 1080 return ERROR_MALFORMED; 1081 } 1082 1083 status_t err = playbackSession->init( 1084 mClientInfo.mRemoteIP.c_str(), 1085 clientRtp, 1086 clientRtcp, 1087 transportMode, 1088 mUsingPCMAudio); 1089 1090 if (err != OK) { 1091 looper()->unregisterHandler(playbackSession->id()); 1092 playbackSession.clear(); 1093 } 1094 1095 switch (err) { 1096 case OK: 1097 break; 1098 case -ENOENT: 1099 sendErrorResponse(sessionID, "404 Not Found", cseq); 1100 return err; 1101 default: 1102 sendErrorResponse(sessionID, "403 Forbidden", cseq); 1103 return err; 1104 } 1105 1106 mClientInfo.mPlaybackSessionID = playbackSessionID; 1107 mClientInfo.mPlaybackSession = playbackSession; 1108 1109 AString response = "RTSP/1.0 200 OK\r\n"; 1110 AppendCommonResponse(&response, cseq, playbackSessionID); 1111 1112 if (transportMode == Sender::TRANSPORT_TCP_INTERLEAVED) { 1113 response.append( 1114 StringPrintf( 1115 "Transport: RTP/AVP/TCP;interleaved=%d-%d;", 1116 clientRtp, clientRtcp)); 1117 } else { 1118 int32_t serverRtp = playbackSession->getRTPPort(); 1119 1120 AString transportString = "UDP"; 1121 if (transportMode == Sender::TRANSPORT_TCP) { 1122 transportString = "TCP"; 1123 } 1124 1125 if (clientRtcp >= 0) { 1126 response.append( 1127 StringPrintf( 1128 "Transport: RTP/AVP/%s;unicast;client_port=%d-%d;" 1129 "server_port=%d-%d\r\n", 1130 transportString.c_str(), 1131 clientRtp, clientRtcp, serverRtp, serverRtp + 1)); 1132 } else { 1133 response.append( 1134 StringPrintf( 1135 "Transport: RTP/AVP/%s;unicast;client_port=%d;" 1136 "server_port=%d\r\n", 1137 transportString.c_str(), 1138 clientRtp, serverRtp)); 1139 } 1140 } 1141 1142 response.append("\r\n"); 1143 1144 err = mNetSession->sendRequest(sessionID, response.c_str()); 1145 1146 if (err != OK) { 1147 return err; 1148 } 1149 1150 mState = AWAITING_CLIENT_PLAY; 1151 1152 scheduleReaper(); 1153 scheduleKeepAlive(sessionID); 1154 1155 return OK; 1156} 1157 1158status_t WifiDisplaySource::onPlayRequest( 1159 int32_t sessionID, 1160 int32_t cseq, 1161 const sp<ParsedMessage> &data) { 1162 int32_t playbackSessionID; 1163 sp<PlaybackSession> playbackSession = 1164 findPlaybackSession(data, &playbackSessionID); 1165 1166 if (playbackSession == NULL) { 1167 sendErrorResponse(sessionID, "454 Session Not Found", cseq); 1168 return ERROR_MALFORMED; 1169 } 1170 1171 ALOGI("Received PLAY request."); 1172 1173 status_t err = playbackSession->play(); 1174 CHECK_EQ(err, (status_t)OK); 1175 1176 AString response = "RTSP/1.0 200 OK\r\n"; 1177 AppendCommonResponse(&response, cseq, playbackSessionID); 1178 response.append("Range: npt=now-\r\n"); 1179 response.append("\r\n"); 1180 1181 err = mNetSession->sendRequest(sessionID, response.c_str()); 1182 1183 if (err != OK) { 1184 return err; 1185 } 1186 1187 playbackSession->finishPlay(); 1188 1189 CHECK_EQ(mState, AWAITING_CLIENT_PLAY); 1190 mState = ABOUT_TO_PLAY; 1191 1192 return OK; 1193} 1194 1195status_t WifiDisplaySource::onPauseRequest( 1196 int32_t sessionID, 1197 int32_t cseq, 1198 const sp<ParsedMessage> &data) { 1199 int32_t playbackSessionID; 1200 sp<PlaybackSession> playbackSession = 1201 findPlaybackSession(data, &playbackSessionID); 1202 1203 if (playbackSession == NULL) { 1204 sendErrorResponse(sessionID, "454 Session Not Found", cseq); 1205 return ERROR_MALFORMED; 1206 } 1207 1208 status_t err = playbackSession->pause(); 1209 CHECK_EQ(err, (status_t)OK); 1210 1211 AString response = "RTSP/1.0 200 OK\r\n"; 1212 AppendCommonResponse(&response, cseq, playbackSessionID); 1213 response.append("\r\n"); 1214 1215 err = mNetSession->sendRequest(sessionID, response.c_str()); 1216 1217 return err; 1218} 1219 1220status_t WifiDisplaySource::onTeardownRequest( 1221 int32_t sessionID, 1222 int32_t cseq, 1223 const sp<ParsedMessage> &data) { 1224 ALOGI("Received TEARDOWN request."); 1225 1226 int32_t playbackSessionID; 1227 sp<PlaybackSession> playbackSession = 1228 findPlaybackSession(data, &playbackSessionID); 1229 1230 if (playbackSession == NULL) { 1231 sendErrorResponse(sessionID, "454 Session Not Found", cseq); 1232 return ERROR_MALFORMED; 1233 } 1234 1235 AString response = "RTSP/1.0 200 OK\r\n"; 1236 AppendCommonResponse(&response, cseq, playbackSessionID); 1237 response.append("Connection: close\r\n"); 1238 response.append("\r\n"); 1239 1240 mNetSession->sendRequest(sessionID, response.c_str()); 1241 1242 if (mState == AWAITING_CLIENT_TEARDOWN) { 1243 CHECK_NE(mStopReplyID, 0); 1244 finishStop(); 1245 } else { 1246 mClient->onDisplayError(IRemoteDisplayClient::kDisplayErrorUnknown); 1247 } 1248 1249 return OK; 1250} 1251 1252void WifiDisplaySource::finishStop() { 1253 ALOGV("finishStop"); 1254 1255 mState = STOPPING; 1256 1257 disconnectClientAsync(); 1258} 1259 1260void WifiDisplaySource::finishStopAfterDisconnectingClient() { 1261 ALOGV("finishStopAfterDisconnectingClient"); 1262 1263 if (mHDCP != NULL) { 1264 ALOGI("Initiating HDCP shutdown."); 1265 mHDCP->shutdownAsync(); 1266 return; 1267 } 1268 1269 finishStop2(); 1270} 1271 1272void WifiDisplaySource::finishStop2() { 1273 ALOGV("finishStop2"); 1274 1275 if (mHDCP != NULL) { 1276 mHDCP->setObserver(NULL); 1277 mHDCPObserver.clear(); 1278 mHDCP.clear(); 1279 } 1280 1281 if (mSessionID != 0) { 1282 mNetSession->destroySession(mSessionID); 1283 mSessionID = 0; 1284 } 1285 1286 ALOGI("We're stopped."); 1287 mState = STOPPED; 1288 1289 status_t err = OK; 1290 1291 sp<AMessage> response = new AMessage; 1292 response->setInt32("err", err); 1293 response->postReply(mStopReplyID); 1294} 1295 1296status_t WifiDisplaySource::onGetParameterRequest( 1297 int32_t sessionID, 1298 int32_t cseq, 1299 const sp<ParsedMessage> &data) { 1300 int32_t playbackSessionID; 1301 sp<PlaybackSession> playbackSession = 1302 findPlaybackSession(data, &playbackSessionID); 1303 1304 if (playbackSession == NULL) { 1305 sendErrorResponse(sessionID, "454 Session Not Found", cseq); 1306 return ERROR_MALFORMED; 1307 } 1308 1309 playbackSession->updateLiveness(); 1310 1311 AString response = "RTSP/1.0 200 OK\r\n"; 1312 AppendCommonResponse(&response, cseq, playbackSessionID); 1313 response.append("\r\n"); 1314 1315 status_t err = mNetSession->sendRequest(sessionID, response.c_str()); 1316 return err; 1317} 1318 1319status_t WifiDisplaySource::onSetParameterRequest( 1320 int32_t sessionID, 1321 int32_t cseq, 1322 const sp<ParsedMessage> &data) { 1323 int32_t playbackSessionID; 1324 sp<PlaybackSession> playbackSession = 1325 findPlaybackSession(data, &playbackSessionID); 1326 1327 if (playbackSession == NULL) { 1328 sendErrorResponse(sessionID, "454 Session Not Found", cseq); 1329 return ERROR_MALFORMED; 1330 } 1331 1332 if (strstr(data->getContent(), "wfd_idr_request\r\n")) { 1333 playbackSession->requestIDRFrame(); 1334 } 1335 1336 playbackSession->updateLiveness(); 1337 1338 AString response = "RTSP/1.0 200 OK\r\n"; 1339 AppendCommonResponse(&response, cseq, playbackSessionID); 1340 response.append("\r\n"); 1341 1342 status_t err = mNetSession->sendRequest(sessionID, response.c_str()); 1343 return err; 1344} 1345 1346// static 1347void WifiDisplaySource::AppendCommonResponse( 1348 AString *response, int32_t cseq, int32_t playbackSessionID) { 1349 time_t now = time(NULL); 1350 struct tm *now2 = gmtime(&now); 1351 char buf[128]; 1352 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", now2); 1353 1354 response->append("Date: "); 1355 response->append(buf); 1356 response->append("\r\n"); 1357 1358 response->append("Server: Mine/1.0\r\n"); 1359 1360 if (cseq >= 0) { 1361 response->append(StringPrintf("CSeq: %d\r\n", cseq)); 1362 } 1363 1364 if (playbackSessionID >= 0ll) { 1365 response->append( 1366 StringPrintf( 1367 "Session: %d;timeout=%lld\r\n", 1368 playbackSessionID, kPlaybackSessionTimeoutSecs)); 1369 } 1370} 1371 1372void WifiDisplaySource::sendErrorResponse( 1373 int32_t sessionID, 1374 const char *errorDetail, 1375 int32_t cseq) { 1376 AString response; 1377 response.append("RTSP/1.0 "); 1378 response.append(errorDetail); 1379 response.append("\r\n"); 1380 1381 AppendCommonResponse(&response, cseq); 1382 1383 response.append("\r\n"); 1384 1385 mNetSession->sendRequest(sessionID, response.c_str()); 1386} 1387 1388int32_t WifiDisplaySource::makeUniquePlaybackSessionID() const { 1389 return rand(); 1390} 1391 1392sp<WifiDisplaySource::PlaybackSession> WifiDisplaySource::findPlaybackSession( 1393 const sp<ParsedMessage> &data, int32_t *playbackSessionID) const { 1394 if (!data->findInt32("session", playbackSessionID)) { 1395 // XXX the older dongles do not always include a "Session:" header. 1396 *playbackSessionID = mClientInfo.mPlaybackSessionID; 1397 return mClientInfo.mPlaybackSession; 1398 } 1399 1400 if (*playbackSessionID != mClientInfo.mPlaybackSessionID) { 1401 return NULL; 1402 } 1403 1404 return mClientInfo.mPlaybackSession; 1405} 1406 1407void WifiDisplaySource::disconnectClientAsync() { 1408 ALOGV("disconnectClient"); 1409 1410 if (mClientInfo.mPlaybackSession == NULL) { 1411 disconnectClient2(); 1412 return; 1413 } 1414 1415 if (mClientInfo.mPlaybackSession != NULL) { 1416 ALOGV("Destroying PlaybackSession"); 1417 mClientInfo.mPlaybackSession->destroyAsync(); 1418 } 1419} 1420 1421void WifiDisplaySource::disconnectClient2() { 1422 ALOGV("disconnectClient2"); 1423 1424 if (mClientInfo.mPlaybackSession != NULL) { 1425 looper()->unregisterHandler(mClientInfo.mPlaybackSession->id()); 1426 mClientInfo.mPlaybackSession.clear(); 1427 } 1428 1429 if (mClientSessionID != 0) { 1430 mNetSession->destroySession(mClientSessionID); 1431 mClientSessionID = 0; 1432 } 1433 1434 mClient->onDisplayDisconnected(); 1435 1436 finishStopAfterDisconnectingClient(); 1437} 1438 1439struct WifiDisplaySource::HDCPObserver : public BnHDCPObserver { 1440 HDCPObserver(const sp<AMessage> ¬ify); 1441 1442 virtual void notify( 1443 int msg, int ext1, int ext2, const Parcel *obj); 1444 1445private: 1446 sp<AMessage> mNotify; 1447 1448 DISALLOW_EVIL_CONSTRUCTORS(HDCPObserver); 1449}; 1450 1451WifiDisplaySource::HDCPObserver::HDCPObserver( 1452 const sp<AMessage> ¬ify) 1453 : mNotify(notify) { 1454} 1455 1456void WifiDisplaySource::HDCPObserver::notify( 1457 int msg, int ext1, int ext2, const Parcel *obj) { 1458 sp<AMessage> notify = mNotify->dup(); 1459 notify->setInt32("msg", msg); 1460 notify->setInt32("ext1", ext1); 1461 notify->setInt32("ext2", ext2); 1462 notify->post(); 1463} 1464 1465status_t WifiDisplaySource::makeHDCP() { 1466 sp<IServiceManager> sm = defaultServiceManager(); 1467 sp<IBinder> binder = sm->getService(String16("media.player")); 1468 sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); 1469 CHECK(service != NULL); 1470 1471 mHDCP = service->makeHDCP(); 1472 1473 if (mHDCP == NULL) { 1474 return ERROR_UNSUPPORTED; 1475 } 1476 1477 sp<AMessage> notify = new AMessage(kWhatHDCPNotify, id()); 1478 mHDCPObserver = new HDCPObserver(notify); 1479 1480 status_t err = mHDCP->setObserver(mHDCPObserver); 1481 1482 if (err != OK) { 1483 ALOGE("Failed to set HDCP observer."); 1484 1485 mHDCPObserver.clear(); 1486 mHDCP.clear(); 1487 1488 return err; 1489 } 1490 1491 ALOGI("Initiating HDCP negotiation w/ host %s:%d", 1492 mClientInfo.mRemoteIP.c_str(), mHDCPPort); 1493 1494 err = mHDCP->initAsync(mClientInfo.mRemoteIP.c_str(), mHDCPPort); 1495 1496 if (err != OK) { 1497 return err; 1498 } 1499 1500 return OK; 1501} 1502 1503} // namespace android 1504 1505