blob: 55581a67948517b93043a0485f8d75b6bb73a84c [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>
#include <cutils/properties.h>
namespace android {
WifiDisplaySink::WifiDisplaySink(
const sp<ANetworkSession> &netSession,
const sp<IGraphicBufferProducer> &bufferProducer,
const sp<AMessage> &notify)
: mState(UNDEFINED),
mNetSession(netSession),
mSurfaceTex(bufferProducer),
mNotify(notify),
mUsingTCPTransport(false),
mUsingTCPInterleaving(false),
mSessionID(0),
mNextCSeq(1) {
#if 1
// We support any and all resolutions, but prefer 720p30
mSinkSupportedVideoFormats.setNativeResolution(
VideoFormats::RESOLUTION_CEA, 5); // 1280 x 720 p30
mSinkSupportedVideoFormats.enableAll();
#else
// We only support 800 x 600 p60.
mSinkSupportedVideoFormats.disableAll();
mSinkSupportedVideoFormats.setNativeResolution(
VideoFormats::RESOLUTION_VESA, 1); // 800 x 600 p60
#endif
}
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:
{
sleep(2); // XXX
int32_t sourcePort;
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;
if (mNotify == NULL) {
looper()->stop();
} else {
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatDisconnected);
notify->post();
}
}
break;
}
case ANetworkSession::kWhatConnected:
{
ALOGI("We're now connected.");
mState = CONNECTED;
break;
}
case ANetworkSession::kWhatData:
{
onReceiveClientData(msg);
break;
}
case ANetworkSession::kWhatBinaryData:
{
CHECK(mUsingTCPInterleaving);
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;
}
case kWhatRequestIDRFrame:
{
ALOGI("requesting IDR frame");
sendIDRFrameRequest(mSessionID);
break;
}
case kWhatRTPSinkNotify:
{
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::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,
"rtsp://x.x.x.x:x/wfd1.0/streamid=0");
}
status_t WifiDisplaySink::configureTransport(const sp<ParsedMessage> &msg) {
if (mUsingTCPTransport) {
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;
}
status_t WifiDisplaySink::onReceiveIDRFrameRequestResponse(
int32_t sessionID, const sp<ParsedMessage> &msg) {
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;
if (mState == CONNECTED) {
mUsingTCPTransport = false;
mUsingTCPInterleaving = false;
char val[PROPERTY_VALUE_MAX];
if (property_get("media.wfd-sink.tcp-mode", val, NULL)) {
if (!strcasecmp("true", val) || !strcmp("1", val)) {
ALOGI("Using TCP unicast transport.");
mUsingTCPTransport = true;
mUsingTCPInterleaving = false;
} else if (!strcasecmp("interleaved", val)) {
ALOGI("Using TCP interleaved transport.");
mUsingTCPTransport = true;
mUsingTCPInterleaving = true;
}
}
body = "wfd_video_formats: ";
body.append(mSinkSupportedVideoFormats.getFormatSpec());
body.append(
"\r\nwfd_audio_codecs: AAC 0000000F 00\r\n"
"wfd_client_rtp_ports: RTP/AVP/");
if (mUsingTCPTransport) {
body.append("TCP;");
if (mUsingTCPInterleaving) {
body.append("interleaved");
} else {
body.append("unicast 19000 0");
}
} else {
body.append("UDP;unicast 19000 0");
}
body.append(" 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::sendSetup(int32_t sessionID, const char *uri) {
sp<AMessage> notify = new AMessage(kWhatRTPSinkNotify, id());
mRTPSink = new RTPSink(mNetSession, mSurfaceTex, notify);
looper()->registerHandler(mRTPSink);
status_t err = mRTPSink->init(mUsingTCPTransport, mUsingTCPInterleaving);
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 (mUsingTCPInterleaving) {
request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n");
} else {
int32_t rtpPort = mRTPSink->getRTPPort();
request.append(
StringPrintf(
"Transport: RTP/AVP/%s;unicast;client_port=%d-%d\r\n",
mUsingTCPTransport ? "TCP" : "UDP",
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;
}
status_t WifiDisplaySink::sendIDRFrameRequest(int32_t sessionID) {
AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
AppendCommonResponse(&request, mNextCSeq);
AString content = "wfd_idr_request\r\n";
request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
request.append(StringPrintf("Content-Length: %d\r\n", content.size()));
request.append("\r\n");
request.append(content);
status_t err =
mNetSession->sendRequest(sessionID, request.c_str(), request.size());
if (err != OK) {
return err;
}
registerResponseHandler(
sessionID,
mNextCSeq,
&WifiDisplaySink::onReceiveIDRFrameRequestResponse);
++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