blob: fcd20d4718b623886d308109c507c7b5737a95d6 [file] [log] [blame]
/*
* 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 "WifiDisplaySink"
#include <utils/Log.h>
#include "WifiDisplaySink.h"
#include "ParsedMessage.h"
#include "RTPSink.h"
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaErrors.h>
namespace android {
WifiDisplaySink::WifiDisplaySink(
const sp<ANetworkSession> &netSession,
const sp<ISurfaceTexture> &surfaceTex)
: mState(UNDEFINED),
mNetSession(netSession),
mSurfaceTex(surfaceTex),
mSessionID(0),
mNextCSeq(1) {
}
WifiDisplaySink::~WifiDisplaySink() {
}
void WifiDisplaySink::start(const char *sourceHost, int32_t sourcePort) {
sp<AMessage> msg = new AMessage(kWhatStart, id());
msg->setString("sourceHost", sourceHost);
msg->setInt32("sourcePort", sourcePort);
msg->post();
}
void WifiDisplaySink::start(const char *uri) {
sp<AMessage> msg = new AMessage(kWhatStart, id());
msg->setString("setupURI", uri);
msg->post();
}
// static
bool WifiDisplaySink::ParseURL(
const char *url, AString *host, int32_t *port, AString *path,
AString *user, AString *pass) {
host->clear();
*port = 0;
path->clear();
user->clear();
pass->clear();
if (strncasecmp("rtsp://", url, 7)) {
return false;
}
const char *slashPos = strchr(&url[7], '/');
if (slashPos == NULL) {
host->setTo(&url[7]);
path->setTo("/");
} else {
host->setTo(&url[7], slashPos - &url[7]);
path->setTo(slashPos);
}
ssize_t atPos = host->find("@");
if (atPos >= 0) {
// Split of user:pass@ from hostname.
AString userPass(*host, 0, atPos);
host->erase(0, atPos + 1);
ssize_t colonPos = userPass.find(":");
if (colonPos < 0) {
*user = userPass;
} else {
user->setTo(userPass, 0, colonPos);
pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1);
}
}
const char *colonPos = strchr(host->c_str(), ':');
if (colonPos != NULL) {
char *end;
unsigned long x = strtoul(colonPos + 1, &end, 10);
if (end == colonPos + 1 || *end != '\0' || x >= 65536) {
return false;
}
*port = x;
size_t colonOffset = colonPos - host->c_str();
size_t trailing = host->size() - colonOffset;
host->erase(colonOffset, trailing);
} else {
*port = 554;
}
return true;
}
void WifiDisplaySink::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatStart:
{
int32_t sourcePort;
if (msg->findString("setupURI", &mSetupURI)) {
AString path, user, pass;
CHECK(ParseURL(
mSetupURI.c_str(),
&mRTSPHost, &sourcePort, &path, &user, &pass)
&& user.empty() && pass.empty());
} else {
CHECK(msg->findString("sourceHost", &mRTSPHost));
CHECK(msg->findInt32("sourcePort", &sourcePort));
}
sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
status_t err = mNetSession->createRTSPClient(
mRTSPHost.c_str(), sourcePort, notify, &mSessionID);
CHECK_EQ(err, (status_t)OK);
mState = CONNECTING;
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));
if (sessionID == mSessionID) {
ALOGI("Lost control connection.");
// The control connection is dead now.
mNetSession->destroySession(mSessionID);
mSessionID = 0;
looper()->stop();
}
break;
}
case ANetworkSession::kWhatConnected:
{
ALOGI("We're now connected.");
mState = CONNECTED;
if (!mSetupURI.empty()) {
status_t err =
sendDescribe(mSessionID, mSetupURI.c_str());
CHECK_EQ(err, (status_t)OK);
}
break;
}
case ANetworkSession::kWhatData:
{
onReceiveClientData(msg);
break;
}
case ANetworkSession::kWhatBinaryData:
{
CHECK(sUseTCPInterleaving);
int32_t channel;
CHECK(msg->findInt32("channel", &channel));
sp<ABuffer> data;
CHECK(msg->findBuffer("data", &data));
mRTPSink->injectPacket(channel == 0 /* isRTP */, data);
break;
}
default:
TRESPASS();
}
break;
}
case kWhatStop:
{
looper()->stop();
break;
}
default:
TRESPASS();
}
}
void WifiDisplaySink::registerResponseHandler(
int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
ResponseID id;
id.mSessionID = sessionID;
id.mCSeq = cseq;
mResponseHandlers.add(id, func);
}
status_t WifiDisplaySink::sendM2(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, &WifiDisplaySink::onReceiveM2Response);
++mNextCSeq;
return OK;
}
status_t WifiDisplaySink::onReceiveM2Response(
int32_t sessionID, const sp<ParsedMessage> &msg) {
int32_t statusCode;
if (!msg->getStatusCode(&statusCode)) {
return ERROR_MALFORMED;
}
if (statusCode != 200) {
return ERROR_UNSUPPORTED;
}
return OK;
}
status_t WifiDisplaySink::onReceiveDescribeResponse(
int32_t sessionID, const sp<ParsedMessage> &msg) {
int32_t statusCode;
if (!msg->getStatusCode(&statusCode)) {
return ERROR_MALFORMED;
}
if (statusCode != 200) {
return ERROR_UNSUPPORTED;
}
return sendSetup(sessionID, mSetupURI.c_str());
}
status_t WifiDisplaySink::onReceiveSetupResponse(
int32_t sessionID, const sp<ParsedMessage> &msg) {
int32_t statusCode;
if (!msg->getStatusCode(&statusCode)) {
return ERROR_MALFORMED;
}
if (statusCode != 200) {
return ERROR_UNSUPPORTED;
}
if (!msg->findString("session", &mPlaybackSessionID)) {
return ERROR_MALFORMED;
}
if (!ParsedMessage::GetInt32Attribute(
mPlaybackSessionID.c_str(),
"timeout",
&mPlaybackSessionTimeoutSecs)) {
mPlaybackSessionTimeoutSecs = -1;
}
ssize_t colonPos = mPlaybackSessionID.find(";");
if (colonPos >= 0) {
// Strip any options from the returned session id.
mPlaybackSessionID.erase(
colonPos, mPlaybackSessionID.size() - colonPos);
}
status_t err = configureTransport(msg);
if (err != OK) {
return err;
}
mState = PAUSED;
return sendPlay(
sessionID,
!mSetupURI.empty()
? mSetupURI.c_str() : "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
}
status_t WifiDisplaySink::configureTransport(const sp<ParsedMessage> &msg) {
if (sUseTCPInterleaving) {
return OK;
}
AString transport;
if (!msg->findString("transport", &transport)) {
ALOGE("Missing 'transport' field in SETUP response.");
return ERROR_MALFORMED;
}
AString sourceHost;
if (!ParsedMessage::GetAttribute(
transport.c_str(), "source", &sourceHost)) {
sourceHost = mRTSPHost;
}
AString serverPortStr;
if (!ParsedMessage::GetAttribute(
transport.c_str(), "server_port", &serverPortStr)) {
ALOGE("Missing 'server_port' in Transport field.");
return ERROR_MALFORMED;
}
int rtpPort, rtcpPort;
if (sscanf(serverPortStr.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2
|| rtpPort <= 0 || rtpPort > 65535
|| rtcpPort <=0 || rtcpPort > 65535
|| rtcpPort != rtpPort + 1) {
ALOGE("Invalid server_port description '%s'.",
serverPortStr.c_str());
return ERROR_MALFORMED;
}
if (rtpPort & 1) {
ALOGW("Server picked an odd numbered RTP port.");
}
return mRTPSink->connect(sourceHost.c_str(), rtpPort, rtcpPort);
}
status_t WifiDisplaySink::onReceivePlayResponse(
int32_t sessionID, const sp<ParsedMessage> &msg) {
int32_t statusCode;
if (!msg->getStatusCode(&statusCode)) {
return ERROR_MALFORMED;
}
if (statusCode != 200) {
return ERROR_UNSUPPORTED;
}
mState = PLAYING;
return OK;
}
void WifiDisplaySink::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;
}
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);
CHECK_EQ(err, (status_t)OK);
} else {
AString version;
data->getRequestField(2, &version);
if (!(version == AString("RTSP/1.0"))) {
sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
return;
}
if (method == "OPTIONS") {
onOptionsRequest(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 WifiDisplaySink::onOptionsRequest(
int32_t sessionID,
int32_t cseq,
const sp<ParsedMessage> &data) {
AString response = "RTSP/1.0 200 OK\r\n";
AppendCommonResponse(&response, cseq);
response.append("Public: org.wfa.wfd1.0, 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 = sendM2(sessionID);
CHECK_EQ(err, (status_t)OK);
}
void WifiDisplaySink::onGetParameterRequest(
int32_t sessionID,
int32_t cseq,
const sp<ParsedMessage> &data) {
AString body =
"wfd_video_formats: xxx\r\n"
"wfd_audio_codecs: xxx\r\n"
"wfd_client_rtp_ports: RTP/AVP/UDP;unicast xxx 0 mode=play\r\n";
AString response = "RTSP/1.0 200 OK\r\n";
AppendCommonResponse(&response, cseq);
response.append("Content-Type: text/parameters\r\n");
response.append(StringPrintf("Content-Length: %d\r\n", body.size()));
response.append("\r\n");
response.append(body);
status_t err = mNetSession->sendRequest(sessionID, response.c_str());
CHECK_EQ(err, (status_t)OK);
}
status_t WifiDisplaySink::sendDescribe(int32_t sessionID, const char *uri) {
uri = "rtsp://xwgntvx.is.livestream-api.com/livestreamiphone/wgntv";
uri = "rtsp://v2.cache6.c.youtube.com/video.3gp?cid=e101d4bf280055f9&fmt=18";
AString request = StringPrintf("DESCRIBE %s RTSP/1.0\r\n", uri);
AppendCommonResponse(&request, mNextCSeq);
request.append("Accept: application/sdp\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, &WifiDisplaySink::onReceiveDescribeResponse);
++mNextCSeq;
return OK;
}
status_t WifiDisplaySink::sendSetup(int32_t sessionID, const char *uri) {
mRTPSink = new RTPSink(mNetSession, mSurfaceTex);
looper()->registerHandler(mRTPSink);
status_t err = mRTPSink->init(sUseTCPInterleaving);
if (err != OK) {
looper()->unregisterHandler(mRTPSink->id());
mRTPSink.clear();
return err;
}
AString request = StringPrintf("SETUP %s RTSP/1.0\r\n", uri);
AppendCommonResponse(&request, mNextCSeq);
if (sUseTCPInterleaving) {
request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n");
} else {
int32_t rtpPort = mRTPSink->getRTPPort();
request.append(
StringPrintf(
"Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
rtpPort, rtpPort + 1));
}
request.append("\r\n");
ALOGV("request = '%s'", request.c_str());
err = mNetSession->sendRequest(sessionID, request.c_str(), request.size());
if (err != OK) {
return err;
}
registerResponseHandler(
sessionID, mNextCSeq, &WifiDisplaySink::onReceiveSetupResponse);
++mNextCSeq;
return OK;
}
status_t WifiDisplaySink::sendPlay(int32_t sessionID, const char *uri) {
AString request = StringPrintf("PLAY %s RTSP/1.0\r\n", uri);
AppendCommonResponse(&request, mNextCSeq);
request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
request.append("\r\n");
status_t err =
mNetSession->sendRequest(sessionID, request.c_str(), request.size());
if (err != OK) {
return err;
}
registerResponseHandler(
sessionID, mNextCSeq, &WifiDisplaySink::onReceivePlayResponse);
++mNextCSeq;
return OK;
}
void WifiDisplaySink::onSetParameterRequest(
int32_t sessionID,
int32_t cseq,
const sp<ParsedMessage> &data) {
const char *content = data->getContent();
if (strstr(content, "wfd_trigger_method: SETUP\r\n") != NULL) {
status_t err =
sendSetup(
sessionID,
"rtsp://x.x.x.x:x/wfd1.0/streamid=0");
CHECK_EQ(err, (status_t)OK);
}
AString response = "RTSP/1.0 200 OK\r\n";
AppendCommonResponse(&response, cseq);
response.append("\r\n");
status_t err = mNetSession->sendRequest(sessionID, response.c_str());
CHECK_EQ(err, (status_t)OK);
}
void WifiDisplaySink::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);
}
// static
void WifiDisplaySink::AppendCommonResponse(AString *response, int32_t cseq) {
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("User-Agent: stagefright/1.1 (Linux;Android 4.1)\r\n");
if (cseq >= 0) {
response->append(StringPrintf("CSeq: %d\r\n", cseq));
}
}
} // namespace android