DO NOT MERGE: Fix parsing of ntp= PLAY response.
related-to-bug: 3340186 Squashed commit of the following: commit b61c36b7228aec9f5360883b1e1c1e0530488974 Author: Andreas Huber <andih@google.com> Date: Wed Oct 27 13:59:59 2010 -0700 Better support for MP4A-LATM RTP disassembly. This used to fail if mNumSubFrames > 1 and the sub frames did not align with RTP packet boundaries. commit b10f322c07e5bebcaf032e8624cb4a5d733dfc15 Author: Andreas Huber <andih@google.com> Date: Mon Oct 25 09:40:52 2010 -0700 We don't have access to the md5 implementation on the simulator, let's disable digest authentication in rtsp for simulator targets. commit 0aa83cf9e4637adf9501708fcdf7d0d6d4dc4fe1 Author: Andreas Huber <andih@google.com> Date: Wed Oct 20 15:00:34 2010 -0700 Support for BASIC and DIGEST authentication schemes in RTSP. Support for malformed packet descriptions that end lines in LF only, instead of CRLF. related-to-bug: 3084183 Change-Id: I6e512cb73cc8d5624a83f7154aa5699f7fef7534
This commit is contained in:
@ -18,18 +18,381 @@
|
||||
|
||||
#include "ARTPSource.h"
|
||||
|
||||
#include <media/stagefright/foundation/hexdump.h>
|
||||
#include <media/stagefright/foundation/ABitReader.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 <ctype.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
AMPEG4AudioAssembler::AMPEG4AudioAssembler(const sp<AMessage> ¬ify)
|
||||
static bool GetAttribute(const char *s, const char *key, AString *value) {
|
||||
value->clear();
|
||||
|
||||
size_t keyLen = strlen(key);
|
||||
|
||||
for (;;) {
|
||||
while (isspace(*s)) {
|
||||
++s;
|
||||
}
|
||||
|
||||
const char *colonPos = strchr(s, ';');
|
||||
|
||||
size_t len =
|
||||
(colonPos == NULL) ? strlen(s) : colonPos - s;
|
||||
|
||||
if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
|
||||
value->setTo(&s[keyLen + 1], len - keyLen - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (colonPos == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s = colonPos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static sp<ABuffer> decodeHex(const AString &s) {
|
||||
if ((s.size() % 2) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t outLen = s.size() / 2;
|
||||
sp<ABuffer> buffer = new ABuffer(outLen);
|
||||
uint8_t *out = buffer->data();
|
||||
|
||||
uint8_t accum = 0;
|
||||
for (size_t i = 0; i < s.size(); ++i) {
|
||||
char c = s.c_str()[i];
|
||||
unsigned value;
|
||||
if (c >= '0' && c <= '9') {
|
||||
value = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
value = c - 'a' + 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
value = c - 'A' + 10;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
accum = (accum << 4) | value;
|
||||
|
||||
if (i & 1) {
|
||||
*out++ = accum;
|
||||
|
||||
accum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static status_t parseAudioObjectType(
|
||||
ABitReader *bits, unsigned *audioObjectType) {
|
||||
*audioObjectType = bits->getBits(5);
|
||||
if ((*audioObjectType) == 31) {
|
||||
*audioObjectType = 32 + bits->getBits(6);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static status_t parseGASpecificConfig(
|
||||
ABitReader *bits,
|
||||
unsigned audioObjectType, unsigned channelConfiguration) {
|
||||
unsigned frameLengthFlag = bits->getBits(1);
|
||||
unsigned dependsOnCoreCoder = bits->getBits(1);
|
||||
if (dependsOnCoreCoder) {
|
||||
/* unsigned coreCoderDelay = */bits->getBits(1);
|
||||
}
|
||||
unsigned extensionFlag = bits->getBits(1);
|
||||
|
||||
if (!channelConfiguration) {
|
||||
// program_config_element
|
||||
return ERROR_UNSUPPORTED; // XXX to be implemented
|
||||
}
|
||||
|
||||
if (audioObjectType == 6 || audioObjectType == 20) {
|
||||
/* unsigned layerNr = */bits->getBits(3);
|
||||
}
|
||||
|
||||
if (extensionFlag) {
|
||||
if (audioObjectType == 22) {
|
||||
/* unsigned numOfSubFrame = */bits->getBits(5);
|
||||
/* unsigned layerLength = */bits->getBits(11);
|
||||
} else if (audioObjectType == 17 || audioObjectType == 19
|
||||
|| audioObjectType == 20 || audioObjectType == 23) {
|
||||
/* unsigned aacSectionDataResilienceFlag = */bits->getBits(1);
|
||||
/* unsigned aacScalefactorDataResilienceFlag = */bits->getBits(1);
|
||||
/* unsigned aacSpectralDataResilienceFlag = */bits->getBits(1);
|
||||
}
|
||||
|
||||
unsigned extensionFlag3 = bits->getBits(1);
|
||||
CHECK_EQ(extensionFlag3, 0u); // TBD in version 3
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static status_t parseAudioSpecificConfig(ABitReader *bits) {
|
||||
unsigned audioObjectType;
|
||||
CHECK_EQ(parseAudioObjectType(bits, &audioObjectType), (status_t)OK);
|
||||
|
||||
unsigned samplingFreqIndex = bits->getBits(4);
|
||||
if (samplingFreqIndex == 0x0f) {
|
||||
/* unsigned samplingFrequency = */bits->getBits(24);
|
||||
}
|
||||
|
||||
unsigned channelConfiguration = bits->getBits(4);
|
||||
|
||||
unsigned extensionAudioObjectType = 0;
|
||||
unsigned sbrPresent = 0;
|
||||
|
||||
if (audioObjectType == 5) {
|
||||
extensionAudioObjectType = audioObjectType;
|
||||
sbrPresent = 1;
|
||||
unsigned extensionSamplingFreqIndex = bits->getBits(4);
|
||||
if (extensionSamplingFreqIndex == 0x0f) {
|
||||
/* unsigned extensionSamplingFrequency = */bits->getBits(24);
|
||||
}
|
||||
CHECK_EQ(parseAudioObjectType(bits, &audioObjectType), (status_t)OK);
|
||||
}
|
||||
|
||||
CHECK((audioObjectType >= 1 && audioObjectType <= 4)
|
||||
|| (audioObjectType >= 6 && audioObjectType <= 7)
|
||||
|| audioObjectType == 17
|
||||
|| (audioObjectType >= 19 && audioObjectType <= 23));
|
||||
|
||||
CHECK_EQ(parseGASpecificConfig(
|
||||
bits, audioObjectType, channelConfiguration), (status_t)OK);
|
||||
|
||||
if (audioObjectType == 17
|
||||
|| (audioObjectType >= 19 && audioObjectType <= 27)) {
|
||||
unsigned epConfig = bits->getBits(2);
|
||||
if (epConfig == 2 || epConfig == 3) {
|
||||
// ErrorProtectionSpecificConfig
|
||||
return ERROR_UNSUPPORTED; // XXX to be implemented
|
||||
|
||||
if (epConfig == 3) {
|
||||
unsigned directMapping = bits->getBits(1);
|
||||
CHECK_EQ(directMapping, 1u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This is not supported here as the upper layers did not explicitly
|
||||
// signal the length of AudioSpecificConfig.
|
||||
|
||||
if (extensionAudioObjectType != 5 && bits->numBitsLeft() >= 16) {
|
||||
unsigned syncExtensionType = bits->getBits(11);
|
||||
if (syncExtensionType == 0x2b7) {
|
||||
CHECK_EQ(parseAudioObjectType(bits, &extensionAudioObjectType),
|
||||
(status_t)OK);
|
||||
|
||||
sbrPresent = bits->getBits(1);
|
||||
|
||||
if (sbrPresent == 1) {
|
||||
unsigned extensionSamplingFreqIndex = bits->getBits(4);
|
||||
if (extensionSamplingFreqIndex == 0x0f) {
|
||||
/* unsigned extensionSamplingFrequency = */bits->getBits(24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static status_t parseStreamMuxConfig(
|
||||
ABitReader *bits,
|
||||
unsigned *numSubFrames,
|
||||
unsigned *frameLengthType,
|
||||
bool *otherDataPresent,
|
||||
unsigned *otherDataLenBits) {
|
||||
unsigned audioMuxVersion = bits->getBits(1);
|
||||
|
||||
unsigned audioMuxVersionA = 0;
|
||||
if (audioMuxVersion == 1) {
|
||||
audioMuxVersionA = bits->getBits(1);
|
||||
}
|
||||
|
||||
CHECK_EQ(audioMuxVersionA, 0u); // otherwise future spec
|
||||
|
||||
if (audioMuxVersion != 0) {
|
||||
return ERROR_UNSUPPORTED; // XXX to be implemented;
|
||||
}
|
||||
CHECK_EQ(audioMuxVersion, 0u); // XXX to be implemented
|
||||
|
||||
unsigned allStreamsSameTimeFraming = bits->getBits(1);
|
||||
CHECK_EQ(allStreamsSameTimeFraming, 1u); // There's only one stream.
|
||||
|
||||
*numSubFrames = bits->getBits(6);
|
||||
unsigned numProgram = bits->getBits(4);
|
||||
CHECK_EQ(numProgram, 0u); // disabled in RTP LATM
|
||||
|
||||
unsigned numLayer = bits->getBits(3);
|
||||
CHECK_EQ(numLayer, 0u); // disabled in RTP LATM
|
||||
|
||||
if (audioMuxVersion == 0) {
|
||||
// AudioSpecificConfig
|
||||
CHECK_EQ(parseAudioSpecificConfig(bits), (status_t)OK);
|
||||
} else {
|
||||
TRESPASS(); // XXX to be implemented
|
||||
}
|
||||
|
||||
*frameLengthType = bits->getBits(3);
|
||||
switch (*frameLengthType) {
|
||||
case 0:
|
||||
{
|
||||
/* unsigned bufferFullness = */bits->getBits(8);
|
||||
|
||||
// The "coreFrameOffset" does not apply since there's only
|
||||
// a single layer.
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
/* unsigned frameLength = */bits->getBits(9);
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
{
|
||||
/* unsigned CELPframeLengthTableIndex = */bits->getBits(6);
|
||||
break;
|
||||
}
|
||||
|
||||
case 6:
|
||||
case 7:
|
||||
{
|
||||
/* unsigned HVXCframeLengthTableIndex = */bits->getBits(1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
*otherDataPresent = bits->getBits(1);
|
||||
*otherDataLenBits = 0;
|
||||
if (*otherDataPresent) {
|
||||
if (audioMuxVersion == 1) {
|
||||
TRESPASS(); // XXX to be implemented
|
||||
} else {
|
||||
*otherDataLenBits = 0;
|
||||
|
||||
unsigned otherDataLenEsc;
|
||||
do {
|
||||
(*otherDataLenBits) <<= 8;
|
||||
otherDataLenEsc = bits->getBits(1);
|
||||
unsigned otherDataLenTmp = bits->getBits(8);
|
||||
(*otherDataLenBits) += otherDataLenTmp;
|
||||
} while (otherDataLenEsc);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned crcCheckPresent = bits->getBits(1);
|
||||
if (crcCheckPresent) {
|
||||
/* unsigned crcCheckSum = */bits->getBits(8);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
sp<ABuffer> AMPEG4AudioAssembler::removeLATMFraming(const sp<ABuffer> &buffer) {
|
||||
CHECK(!mMuxConfigPresent); // XXX to be implemented
|
||||
|
||||
sp<ABuffer> out = new ABuffer(buffer->size());
|
||||
out->setRange(0, 0);
|
||||
|
||||
size_t offset = 0;
|
||||
uint8_t *ptr = buffer->data();
|
||||
|
||||
for (size_t i = 0; i <= mNumSubFrames; ++i) {
|
||||
// parse PayloadLengthInfo
|
||||
|
||||
unsigned payloadLength = 0;
|
||||
|
||||
switch (mFrameLengthType) {
|
||||
case 0:
|
||||
{
|
||||
unsigned muxSlotLengthBytes = 0;
|
||||
unsigned tmp;
|
||||
do {
|
||||
CHECK_LT(offset, buffer->size());
|
||||
tmp = ptr[offset++];
|
||||
muxSlotLengthBytes += tmp;
|
||||
} while (tmp == 0xff);
|
||||
|
||||
payloadLength = muxSlotLengthBytes;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
TRESPASS(); // XXX to be implemented
|
||||
break;
|
||||
}
|
||||
|
||||
CHECK_LE(offset + payloadLength, buffer->size());
|
||||
|
||||
memcpy(out->data() + out->size(), &ptr[offset], payloadLength);
|
||||
out->setRange(0, out->size() + payloadLength);
|
||||
|
||||
offset += payloadLength;
|
||||
|
||||
if (mOtherDataPresent) {
|
||||
// We want to stay byte-aligned.
|
||||
|
||||
CHECK((mOtherDataLenBits % 8) == 0);
|
||||
CHECK_LE(offset + (mOtherDataLenBits / 8), buffer->size());
|
||||
offset += mOtherDataLenBits / 8;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_EQ(offset, buffer->size());
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
AMPEG4AudioAssembler::AMPEG4AudioAssembler(
|
||||
const sp<AMessage> ¬ify, const AString ¶ms)
|
||||
: mNotifyMsg(notify),
|
||||
mMuxConfigPresent(false),
|
||||
mAccessUnitRTPTime(0),
|
||||
mNextExpectedSeqNoValid(false),
|
||||
mNextExpectedSeqNo(0),
|
||||
mAccessUnitDamaged(false) {
|
||||
AString val;
|
||||
if (!GetAttribute(params.c_str(), "cpresent", &val)) {
|
||||
mMuxConfigPresent = true;
|
||||
} else if (val == "0") {
|
||||
mMuxConfigPresent = false;
|
||||
} else {
|
||||
CHECK(val == "1");
|
||||
mMuxConfigPresent = true;
|
||||
}
|
||||
|
||||
CHECK(GetAttribute(params.c_str(), "config", &val));
|
||||
|
||||
sp<ABuffer> config = decodeHex(val);
|
||||
CHECK(config != NULL);
|
||||
|
||||
ABitReader bits(config->data(), config->size());
|
||||
status_t err = parseStreamMuxConfig(
|
||||
&bits, &mNumSubFrames, &mFrameLengthType,
|
||||
&mOtherDataPresent, &mOtherDataLenBits);
|
||||
|
||||
CHECK_EQ(err, (status_t)NO_ERROR);
|
||||
}
|
||||
|
||||
AMPEG4AudioAssembler::~AMPEG4AudioAssembler() {
|
||||
@ -108,13 +471,7 @@ void AMPEG4AudioAssembler::submitAccessUnit() {
|
||||
while (it != mPackets.end()) {
|
||||
const sp<ABuffer> &unit = *it;
|
||||
|
||||
size_t n = 0;
|
||||
while (unit->data()[n] == 0xff) {
|
||||
++n;
|
||||
}
|
||||
++n;
|
||||
|
||||
totalSize += unit->size() - n;
|
||||
totalSize += unit->size();
|
||||
++it;
|
||||
}
|
||||
|
||||
@ -124,20 +481,13 @@ void AMPEG4AudioAssembler::submitAccessUnit() {
|
||||
while (it != mPackets.end()) {
|
||||
const sp<ABuffer> &unit = *it;
|
||||
|
||||
size_t n = 0;
|
||||
while (unit->data()[n] == 0xff) {
|
||||
++n;
|
||||
}
|
||||
++n;
|
||||
|
||||
memcpy((uint8_t *)accessUnit->data() + offset,
|
||||
unit->data() + n, unit->size() - n);
|
||||
|
||||
offset += unit->size() - n;
|
||||
unit->data(), unit->size());
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
accessUnit = removeLATMFraming(accessUnit);
|
||||
CopyTimes(accessUnit, *mPackets.begin());
|
||||
|
||||
#if 0
|
||||
|
@ -27,9 +27,11 @@
|
||||
namespace android {
|
||||
|
||||
struct AMessage;
|
||||
struct AString;
|
||||
|
||||
struct AMPEG4AudioAssembler : public ARTPAssembler {
|
||||
AMPEG4AudioAssembler(const sp<AMessage> ¬ify);
|
||||
AMPEG4AudioAssembler(
|
||||
const sp<AMessage> ¬ify, const AString ¶ms);
|
||||
|
||||
protected:
|
||||
virtual ~AMPEG4AudioAssembler();
|
||||
@ -40,6 +42,13 @@ protected:
|
||||
|
||||
private:
|
||||
sp<AMessage> mNotifyMsg;
|
||||
|
||||
bool mMuxConfigPresent;
|
||||
unsigned mNumSubFrames;
|
||||
unsigned mFrameLengthType;
|
||||
bool mOtherDataPresent;
|
||||
unsigned mOtherDataLenBits;
|
||||
|
||||
uint32_t mAccessUnitRTPTime;
|
||||
bool mNextExpectedSeqNoValid;
|
||||
uint32_t mNextExpectedSeqNo;
|
||||
@ -49,6 +58,8 @@ private:
|
||||
AssemblyStatus addPacket(const sp<ARTPSource> &source);
|
||||
void submitAccessUnit();
|
||||
|
||||
sp<ABuffer> removeLATMFraming(const sp<ABuffer> &buffer);
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(AMPEG4AudioAssembler);
|
||||
};
|
||||
|
||||
|
@ -57,7 +57,7 @@ ARTPSource::ARTPSource(
|
||||
mAssembler = new AAVCAssembler(notify);
|
||||
mIssueFIRRequests = true;
|
||||
} else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
|
||||
mAssembler = new AMPEG4AudioAssembler(notify);
|
||||
mAssembler = new AMPEG4AudioAssembler(notify, params);
|
||||
} else if (!strncmp(desc.c_str(), "H263-1998/", 10)
|
||||
|| !strncmp(desc.c_str(), "H263-2000/", 10)) {
|
||||
mAssembler = new AH263Assembler(notify);
|
||||
|
@ -23,11 +23,13 @@
|
||||
#include <media/stagefright/foundation/ABuffer.h>
|
||||
#include <media/stagefright/foundation/ADebug.h>
|
||||
#include <media/stagefright/foundation/AMessage.h>
|
||||
#include <media/stagefright/foundation/base64.h>
|
||||
#include <media/stagefright/MediaErrors.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace android {
|
||||
@ -37,6 +39,7 @@ const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll;
|
||||
|
||||
ARTSPConnection::ARTSPConnection()
|
||||
: mState(DISCONNECTED),
|
||||
mAuthType(NONE),
|
||||
mSocket(-1),
|
||||
mConnectionID(0),
|
||||
mNextCSeq(0),
|
||||
@ -114,10 +117,13 @@ void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) {
|
||||
|
||||
// static
|
||||
bool ARTSPConnection::ParseURL(
|
||||
const char *url, AString *host, unsigned *port, AString *path) {
|
||||
const char *url, AString *host, unsigned *port, AString *path,
|
||||
AString *user, AString *pass) {
|
||||
host->clear();
|
||||
*port = 0;
|
||||
path->clear();
|
||||
user->clear();
|
||||
pass->clear();
|
||||
|
||||
if (strncasecmp("rtsp://", url, 7)) {
|
||||
return false;
|
||||
@ -133,7 +139,25 @@ bool ARTSPConnection::ParseURL(
|
||||
path->setTo(slashPos);
|
||||
}
|
||||
|
||||
char *colonPos = strchr(host->c_str(), ':');
|
||||
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) {
|
||||
unsigned long x;
|
||||
@ -187,7 +211,12 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
|
||||
|
||||
AString host, path;
|
||||
unsigned port;
|
||||
if (!ParseURL(url.c_str(), &host, &port, &path)) {
|
||||
if (!ParseURL(url.c_str(), &host, &port, &path, &mUser, &mPass)
|
||||
|| (mUser.size() > 0 && mPass.size() == 0)) {
|
||||
// If we have a user name but no password we have to give up
|
||||
// right here, since we currently have no way of asking the user
|
||||
// for this information.
|
||||
|
||||
LOGE("Malformed rtsp url %s", url.c_str());
|
||||
|
||||
reply->setInt32("result", ERROR_MALFORMED);
|
||||
@ -197,6 +226,10 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mUser.size() > 0) {
|
||||
LOGV("user = '%s', pass = '%s'", mUser.c_str(), mPass.c_str());
|
||||
}
|
||||
|
||||
struct hostent *ent = gethostbyname(host.c_str());
|
||||
if (ent == NULL) {
|
||||
LOGE("Unknown host %s", host.c_str());
|
||||
@ -262,6 +295,11 @@ void ARTSPConnection::onDisconnect(const sp<AMessage> &msg) {
|
||||
reply->setInt32("result", OK);
|
||||
mState = DISCONNECTED;
|
||||
|
||||
mUser.clear();
|
||||
mPass.clear();
|
||||
mAuthType = NONE;
|
||||
mNonce.clear();
|
||||
|
||||
reply->post();
|
||||
}
|
||||
|
||||
@ -335,6 +373,12 @@ void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) {
|
||||
AString request;
|
||||
CHECK(msg->findString("request", &request));
|
||||
|
||||
// Just in case we need to re-issue the request with proper authentication
|
||||
// later, stash it away.
|
||||
reply->setString("original-request", request.c_str(), request.size());
|
||||
|
||||
addAuthentication(&request);
|
||||
|
||||
// Find the boundary between headers and the body.
|
||||
ssize_t i = request.find("\r\n\r\n");
|
||||
CHECK_GE(i, 0);
|
||||
@ -347,7 +391,7 @@ void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) {
|
||||
|
||||
request.insert(cseqHeader, i + 2);
|
||||
|
||||
LOGV("%s", request.c_str());
|
||||
LOGV("request: '%s'", request.c_str());
|
||||
|
||||
size_t numBytesSent = 0;
|
||||
while (numBytesSent < request.size()) {
|
||||
@ -612,6 +656,30 @@ bool ARTSPConnection::receiveRTSPReponse() {
|
||||
}
|
||||
}
|
||||
|
||||
if (response->mStatusCode == 401) {
|
||||
if (mAuthType == NONE && mUser.size() > 0
|
||||
&& parseAuthMethod(response)) {
|
||||
ssize_t i;
|
||||
CHECK_EQ((status_t)OK, findPendingRequest(response, &i));
|
||||
CHECK_GE(i, 0);
|
||||
|
||||
sp<AMessage> reply = mPendingRequests.valueAt(i);
|
||||
mPendingRequests.removeItemsAt(i);
|
||||
|
||||
AString request;
|
||||
CHECK(reply->findString("original-request", &request));
|
||||
|
||||
sp<AMessage> msg = new AMessage(kWhatSendRequest, id());
|
||||
msg->setMessage("reply", reply);
|
||||
msg->setString("request", request.c_str(), request.size());
|
||||
|
||||
LOGI("re-sending request with authentication headers...");
|
||||
onSendRequest(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return notifyResponseListener(response);
|
||||
}
|
||||
|
||||
@ -628,26 +696,47 @@ bool ARTSPConnection::ParseSingleUnsignedLong(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ARTSPConnection::notifyResponseListener(
|
||||
const sp<ARTSPResponse> &response) {
|
||||
status_t ARTSPConnection::findPendingRequest(
|
||||
const sp<ARTSPResponse> &response, ssize_t *index) const {
|
||||
*index = 0;
|
||||
|
||||
ssize_t i = response->mHeaders.indexOfKey("cseq");
|
||||
|
||||
if (i < 0) {
|
||||
return true;
|
||||
// This is an unsolicited server->client message.
|
||||
return OK;
|
||||
}
|
||||
|
||||
AString value = response->mHeaders.valueAt(i);
|
||||
|
||||
unsigned long cseq;
|
||||
if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) {
|
||||
return false;
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
i = mPendingRequests.indexOfKey(cseq);
|
||||
|
||||
if (i < 0) {
|
||||
// Unsolicited response?
|
||||
TRESPASS();
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
*index = i;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool ARTSPConnection::notifyResponseListener(
|
||||
const sp<ARTSPResponse> &response) {
|
||||
ssize_t i;
|
||||
status_t err = findPendingRequest(response, &i);
|
||||
|
||||
if (err == OK && i < 0) {
|
||||
// An unsolicited server response is not a problem.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sp<AMessage> reply = mPendingRequests.valueAt(i);
|
||||
@ -660,4 +749,160 @@ bool ARTSPConnection::notifyResponseListener(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ARTSPConnection::parseAuthMethod(const sp<ARTSPResponse> &response) {
|
||||
ssize_t i = response->mHeaders.indexOfKey("www-authenticate");
|
||||
|
||||
if (i < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AString value = response->mHeaders.valueAt(i);
|
||||
|
||||
if (!strncmp(value.c_str(), "Basic", 5)) {
|
||||
mAuthType = BASIC;
|
||||
} else {
|
||||
#if !defined(HAVE_ANDROID_OS)
|
||||
// We don't have access to the MD5 implementation on the simulator,
|
||||
// so we won't support digest authentication.
|
||||
return false;
|
||||
#endif
|
||||
|
||||
CHECK(!strncmp(value.c_str(), "Digest", 6));
|
||||
mAuthType = DIGEST;
|
||||
|
||||
i = value.find("nonce=");
|
||||
CHECK_GE(i, 0);
|
||||
CHECK_EQ(value.c_str()[i + 6], '\"');
|
||||
ssize_t j = value.find("\"", i + 7);
|
||||
CHECK_GE(j, 0);
|
||||
|
||||
mNonce.setTo(value, i + 7, j - i - 7);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(HAVE_ANDROID_OS)
|
||||
static void H(const AString &s, AString *out) {
|
||||
out->clear();
|
||||
|
||||
MD5_CTX m;
|
||||
MD5_Init(&m);
|
||||
MD5_Update(&m, s.c_str(), s.size());
|
||||
|
||||
uint8_t key[16];
|
||||
MD5_Final(key, &m);
|
||||
|
||||
for (size_t i = 0; i < 16; ++i) {
|
||||
char nibble = key[i] >> 4;
|
||||
if (nibble <= 9) {
|
||||
nibble += '0';
|
||||
} else {
|
||||
nibble += 'a' - 10;
|
||||
}
|
||||
out->append(&nibble, 1);
|
||||
|
||||
nibble = key[i] & 0x0f;
|
||||
if (nibble <= 9) {
|
||||
nibble += '0';
|
||||
} else {
|
||||
nibble += 'a' - 10;
|
||||
}
|
||||
out->append(&nibble, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void GetMethodAndURL(
|
||||
const AString &request, AString *method, AString *url) {
|
||||
ssize_t space1 = request.find(" ");
|
||||
CHECK_GE(space1, 0);
|
||||
|
||||
ssize_t space2 = request.find(" ", space1 + 1);
|
||||
CHECK_GE(space2, 0);
|
||||
|
||||
method->setTo(request, 0, space1);
|
||||
url->setTo(request, space1 + 1, space2 - space1);
|
||||
}
|
||||
|
||||
void ARTSPConnection::addAuthentication(AString *request) {
|
||||
if (mAuthType == NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the boundary between headers and the body.
|
||||
ssize_t i = request->find("\r\n\r\n");
|
||||
CHECK_GE(i, 0);
|
||||
|
||||
if (mAuthType == BASIC) {
|
||||
AString tmp;
|
||||
tmp.append(mUser);
|
||||
tmp.append(":");
|
||||
tmp.append(mPass);
|
||||
|
||||
AString out;
|
||||
encodeBase64(tmp.c_str(), tmp.size(), &out);
|
||||
|
||||
AString fragment;
|
||||
fragment.append("Authorization: Basic ");
|
||||
fragment.append(out);
|
||||
fragment.append("\r\n");
|
||||
|
||||
request->insert(fragment, i + 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(HAVE_ANDROID_OS)
|
||||
CHECK_EQ((int)mAuthType, (int)DIGEST);
|
||||
|
||||
AString method, url;
|
||||
GetMethodAndURL(*request, &method, &url);
|
||||
|
||||
AString A1;
|
||||
A1.append(mUser);
|
||||
A1.append(":");
|
||||
A1.append("Streaming Server");
|
||||
A1.append(":");
|
||||
A1.append(mPass);
|
||||
|
||||
AString A2;
|
||||
A2.append(method);
|
||||
A2.append(":");
|
||||
A2.append(url);
|
||||
|
||||
AString HA1, HA2;
|
||||
H(A1, &HA1);
|
||||
H(A2, &HA2);
|
||||
|
||||
AString tmp;
|
||||
tmp.append(HA1);
|
||||
tmp.append(":");
|
||||
tmp.append(mNonce);
|
||||
tmp.append(":");
|
||||
tmp.append(HA2);
|
||||
|
||||
AString digest;
|
||||
H(tmp, &digest);
|
||||
|
||||
AString fragment;
|
||||
fragment.append("Authorization: Digest ");
|
||||
fragment.append("nonce=\"");
|
||||
fragment.append(mNonce);
|
||||
fragment.append("\", ");
|
||||
fragment.append("username=\"");
|
||||
fragment.append(mUser);
|
||||
fragment.append("\", ");
|
||||
fragment.append("uri=\"");
|
||||
fragment.append(url);
|
||||
fragment.append("\", ");
|
||||
fragment.append("response=\"");
|
||||
fragment.append(digest);
|
||||
fragment.append("\"");
|
||||
fragment.append("\r\n");
|
||||
|
||||
request->insert(fragment, i + 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
@ -42,6 +42,10 @@ struct ARTSPConnection : public AHandler {
|
||||
|
||||
void observeBinaryData(const sp<AMessage> &reply);
|
||||
|
||||
static bool ParseURL(
|
||||
const char *url, AString *host, unsigned *port, AString *path,
|
||||
AString *user, AString *pass);
|
||||
|
||||
protected:
|
||||
virtual ~ARTSPConnection();
|
||||
virtual void onMessageReceived(const sp<AMessage> &msg);
|
||||
@ -62,9 +66,18 @@ private:
|
||||
kWhatObserveBinaryData = 'obin',
|
||||
};
|
||||
|
||||
enum AuthType {
|
||||
NONE,
|
||||
BASIC,
|
||||
DIGEST
|
||||
};
|
||||
|
||||
static const int64_t kSelectTimeoutUs;
|
||||
|
||||
State mState;
|
||||
AString mUser, mPass;
|
||||
AuthType mAuthType;
|
||||
AString mNonce;
|
||||
int mSocket;
|
||||
int32_t mConnectionID;
|
||||
int32_t mNextCSeq;
|
||||
@ -90,8 +103,11 @@ private:
|
||||
sp<ABuffer> receiveBinaryData();
|
||||
bool notifyResponseListener(const sp<ARTSPResponse> &response);
|
||||
|
||||
static bool ParseURL(
|
||||
const char *url, AString *host, unsigned *port, AString *path);
|
||||
bool parseAuthMethod(const sp<ARTSPResponse> &response);
|
||||
void addAuthentication(AString *request);
|
||||
|
||||
status_t findPendingRequest(
|
||||
const sp<ARTSPResponse> &response, ssize_t *index) const;
|
||||
|
||||
static bool ParseSingleUnsignedLong(
|
||||
const char *from, unsigned long *x);
|
||||
|
@ -53,21 +53,30 @@ bool ASessionDescription::parse(const void *data, size_t size) {
|
||||
mFormats.push(AString("[root]"));
|
||||
|
||||
AString desc((const char *)data, size);
|
||||
LOGI("%s", desc.c_str());
|
||||
|
||||
size_t i = 0;
|
||||
for (;;) {
|
||||
ssize_t eolPos = desc.find("\r\n", i);
|
||||
ssize_t eolPos = desc.find("\n", i);
|
||||
|
||||
if (eolPos < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
AString line(desc, i, eolPos - i);
|
||||
AString line;
|
||||
if ((size_t)eolPos > i && desc.c_str()[eolPos - 1] == '\r') {
|
||||
// We accept both '\n' and '\r\n' line endings, if it's
|
||||
// the latter, strip the '\r' as well.
|
||||
line.setTo(desc, i, eolPos - i - 1);
|
||||
} else {
|
||||
line.setTo(desc, i, eolPos - i);
|
||||
}
|
||||
|
||||
if (line.size() < 2 || line.c_str()[1] != '=') {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("%s", line.c_str());
|
||||
|
||||
switch (line.c_str()[0]) {
|
||||
case 'v':
|
||||
{
|
||||
@ -141,7 +150,7 @@ bool ASessionDescription::parse(const void *data, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
i = eolPos + 2;
|
||||
i = eolPos + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -245,7 +254,7 @@ bool ASessionDescription::getDurationUs(int64_t *durationUs) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value == "npt=now-") {
|
||||
if (value == "npt=now-" || value == "npt=0-") {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ LOCAL_C_INCLUDES:= \
|
||||
$(JNI_H_INCLUDE) \
|
||||
$(TOP)/frameworks/base/include/media/stagefright/openmax \
|
||||
$(TOP)/frameworks/base/media/libstagefright/include \
|
||||
$(TOP)/external/openssl/include
|
||||
|
||||
LOCAL_MODULE:= libstagefright_rtsp
|
||||
|
||||
|
@ -96,6 +96,7 @@ struct MyHandler : public AHandler {
|
||||
mNetLooper(new ALooper),
|
||||
mConn(new ARTSPConnection),
|
||||
mRTPConn(new ARTPConnection),
|
||||
mOriginalSessionURL(url),
|
||||
mSessionURL(url),
|
||||
mSetupTracksSuccessful(false),
|
||||
mSeekPending(false),
|
||||
@ -113,6 +114,23 @@ struct MyHandler : public AHandler {
|
||||
mNetLooper->start(false /* runOnCallingThread */,
|
||||
false /* canCallJava */,
|
||||
PRIORITY_HIGHEST);
|
||||
|
||||
// Strip any authentication info from the session url, we don't
|
||||
// want to transmit user/pass in cleartext.
|
||||
AString host, path, user, pass;
|
||||
unsigned port;
|
||||
if (ARTSPConnection::ParseURL(
|
||||
mSessionURL.c_str(), &host, &port, &path, &user, &pass)
|
||||
&& user.size() > 0) {
|
||||
mSessionURL.clear();
|
||||
mSessionURL.append("rtsp://");
|
||||
mSessionURL.append(host);
|
||||
mSessionURL.append(":");
|
||||
mSessionURL.append(StringPrintf("%u", port));
|
||||
mSessionURL.append(path);
|
||||
|
||||
LOGI("rewritten session url: '%s'", mSessionURL.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void connect(const sp<AMessage> &doneMsg) {
|
||||
@ -126,7 +144,7 @@ struct MyHandler : public AHandler {
|
||||
mConn->observeBinaryData(notify);
|
||||
|
||||
sp<AMessage> reply = new AMessage('conn', id());
|
||||
mConn->connect(mSessionURL.c_str(), reply);
|
||||
mConn->connect(mOriginalSessionURL.c_str(), reply);
|
||||
}
|
||||
|
||||
void disconnect(const sp<AMessage> &doneMsg) {
|
||||
@ -312,7 +330,7 @@ struct MyHandler : public AHandler {
|
||||
int32_t reconnect;
|
||||
if (msg->findInt32("reconnect", &reconnect) && reconnect) {
|
||||
sp<AMessage> reply = new AMessage('conn', id());
|
||||
mConn->connect(mSessionURL.c_str(), reply);
|
||||
mConn->connect(mOriginalSessionURL.c_str(), reply);
|
||||
} else {
|
||||
(new AMessage('quit', id()))->post();
|
||||
}
|
||||
@ -922,7 +940,7 @@ struct MyHandler : public AHandler {
|
||||
CHECK(GetAttribute(range.c_str(), "npt", &val));
|
||||
float npt1, npt2;
|
||||
|
||||
if (val == "now-") {
|
||||
if (val == "now-" || val == "0-") {
|
||||
// This is a live stream and therefore not seekable.
|
||||
return;
|
||||
} else {
|
||||
@ -992,6 +1010,7 @@ private:
|
||||
sp<ARTSPConnection> mConn;
|
||||
sp<ARTPConnection> mRTPConn;
|
||||
sp<ASessionDescription> mSessionDesc;
|
||||
AString mOriginalSessionURL; // This one still has user:pass@
|
||||
AString mSessionURL;
|
||||
AString mBaseURL;
|
||||
AString mSessionID;
|
||||
|
Reference in New Issue
Block a user