Better support for rtsp (normal play-)time display. Better seek support, timeout if no packets arrive for too long.

Change-Id: Id491541a6ae501604cda815f8e961a3bfe26db7d
related-to-bug: 2556656
This commit is contained in:
Andreas Huber
2010-08-27 13:29:08 -07:00
parent d6a4004741
commit eeb97d91b9
14 changed files with 302 additions and 65 deletions

View File

@@ -196,7 +196,6 @@ AwesomePlayer::AwesomePlayer()
mExtractorFlags(0),
mLastVideoBuffer(NULL),
mVideoBuffer(NULL),
mRTSPTimeOffset(0),
mSuspensionState(NULL) {
CHECK_EQ(mClient.connect(), OK);
@@ -739,7 +738,10 @@ status_t AwesomePlayer::getDuration(int64_t *durationUs) {
}
status_t AwesomePlayer::getPosition(int64_t *positionUs) {
if (mSeeking) {
if (mRTSPController != NULL) {
*positionUs = mRTSPController->getNormalPlayTimeUs();
}
else if (mSeeking) {
*positionUs = mSeekTimeUs;
} else if (mVideoSource != NULL) {
Mutex::Autolock autoLock(mMiscStateLock);
@@ -750,10 +752,6 @@ status_t AwesomePlayer::getPosition(int64_t *positionUs) {
*positionUs = 0;
}
if (mRTSPController != NULL) {
*positionUs += mRTSPTimeOffset;
}
return OK;
}
@@ -770,13 +768,10 @@ status_t AwesomePlayer::seekTo(int64_t timeUs) {
status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
if (mRTSPController != NULL) {
pause_l();
mRTSPController->seek(timeUs);
play_l();
notifyListener_l(MEDIA_SEEK_COMPLETE);
mSeekNotificationSent = true;
mRTSPTimeOffset = timeUs;
return OK;
}

View File

@@ -41,6 +41,8 @@ struct ARTSPController : public MediaExtractor {
virtual sp<MetaData> getTrackMetaData(
size_t index, uint32_t flags);
int64_t getNormalPlayTimeUs();
void onMessageReceived(const sp<AMessage> &msg);
protected:

View File

@@ -180,7 +180,6 @@ private:
sp<ALooper> mLooper;
sp<ARTSPController> mRTSPController;
int64_t mRTSPTimeOffset;
sp<ARTPSession> mRTPSession;
sp<UDPPusher> mRTPPusher, mRTCPPusher;

View File

@@ -178,12 +178,8 @@ ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket(
}
}
uint64_t ntpTime;
CHECK(buffer->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
sp<ABuffer> accessUnit = new ABuffer(totalSize);
accessUnit->meta()->setInt64("ntp-time", ntpTime);
CopyTimes(accessUnit, buffer);
size_t dstOffset = 0;
for (size_t i = 0; i < tableOfContents.size(); ++i) {

View File

@@ -155,7 +155,7 @@ bool AAVCAssembler::addSingleTimeAggregationPacket(const sp<ABuffer> &buffer) {
sp<ABuffer> unit = new ABuffer(nalSize);
memcpy(unit->data(), &data[2], nalSize);
PropagateTimes(buffer, unit);
CopyTimes(unit, buffer);
addSingleNALUnit(unit);
@@ -287,7 +287,7 @@ ARTPAssembler::AssemblyStatus AAVCAssembler::addFragmentedNALUnit(
++totalSize;
sp<ABuffer> unit = new ABuffer(totalSize);
PropagateTimes(buffer, unit);
CopyTimes(unit, *queue->begin());
unit->data()[0] = (nri << 5) | nalType;
@@ -325,10 +325,6 @@ void AAVCAssembler::submitAccessUnit() {
LOG(VERBOSE) << "Access unit complete (" << mNALUnits.size() << " nal units)";
#endif
uint64_t ntpTime;
CHECK((*mNALUnits.begin())->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
size_t totalSize = 0;
for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
it != mNALUnits.end(); ++it) {
@@ -347,7 +343,7 @@ void AAVCAssembler::submitAccessUnit() {
offset += nal->size();
}
accessUnit->meta()->setInt64("ntp-time", ntpTime);
CopyTimes(accessUnit, *mNALUnits.begin());
#if 0
printf(mAccessUnitDamaged ? "X" : ".");

View File

@@ -128,10 +128,6 @@ void AH263Assembler::submitAccessUnit() {
LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)";
#endif
uint64_t ntpTime;
CHECK((*mPackets.begin())->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
size_t totalSize = 0;
List<sp<ABuffer> >::iterator it = mPackets.begin();
while (it != mPackets.end()) {
@@ -155,7 +151,7 @@ void AH263Assembler::submitAccessUnit() {
++it;
}
accessUnit->meta()->setInt64("ntp-time", ntpTime);
CopyTimes(accessUnit, *mPackets.begin());
#if 0
printf(mAccessUnitDamaged ? "X" : ".");

View File

@@ -103,10 +103,6 @@ void AMPEG4AudioAssembler::submitAccessUnit() {
LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)";
#endif
uint64_t ntpTime;
CHECK((*mPackets.begin())->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
size_t totalSize = 0;
List<sp<ABuffer> >::iterator it = mPackets.begin();
while (it != mPackets.end()) {
@@ -142,7 +138,7 @@ void AMPEG4AudioAssembler::submitAccessUnit() {
++it;
}
accessUnit->meta()->setInt64("ntp-time", ntpTime);
CopyTimes(accessUnit, *mPackets.begin());
#if 0
printf(mAccessUnitDamaged ? "X" : ".");

View File

@@ -101,10 +101,6 @@ void AMPEG4ElementaryAssembler::submitAccessUnit() {
LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " nal units)";
#endif
uint64_t ntpTime;
CHECK((*mPackets.begin())->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
size_t totalSize = 0;
for (List<sp<ABuffer> >::iterator it = mPackets.begin();
it != mPackets.end(); ++it) {
@@ -120,7 +116,7 @@ void AMPEG4ElementaryAssembler::submitAccessUnit() {
offset += nal->size();
}
accessUnit->meta()->setInt64("ntp-time", ntpTime);
CopyTimes(accessUnit, *mPackets.begin());
#if 0
printf(mAccessUnitDamaged ? "X" : ".");

View File

@@ -402,16 +402,41 @@ sp<ABuffer> MakeMPEG4VideoCodecSpecificData(
return csd;
}
static bool GetClockRate(const AString &desc, uint32_t *clockRate) {
ssize_t slashPos = desc.find("/");
if (slashPos < 0) {
return false;
}
const char *s = desc.c_str() + slashPos + 1;
char *end;
unsigned long x = strtoul(s, &end, 10);
if (end == s || (*end != '\0' && *end != '/')) {
return false;
}
*clockRate = x;
return true;
}
APacketSource::APacketSource(
const sp<ASessionDescription> &sessionDesc, size_t index)
: mInitCheck(NO_INIT),
mFormat(new MetaData),
mEOSResult(OK) {
mEOSResult(OK),
mRTPTimeBase(0),
mNormalPlayTimeBaseUs(0),
mLastNormalPlayTimeUs(0) {
unsigned long PT;
AString desc;
AString params;
sessionDesc->getFormatType(index, &PT, &desc, &params);
CHECK(GetClockRate(desc, &mClockRate));
int64_t durationUs;
if (sessionDesc->getDurationUs(&durationUs)) {
mFormat->setInt64(kKeyDuration, durationUs);
@@ -571,6 +596,8 @@ status_t APacketSource::read(
if (!mBuffers.empty()) {
const sp<ABuffer> buffer = *mBuffers.begin();
updateNormalPlayTime_l(buffer);
MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
int64_t timeUs;
@@ -588,6 +615,16 @@ status_t APacketSource::read(
return mEOSResult;
}
void APacketSource::updateNormalPlayTime_l(const sp<ABuffer> &buffer) {
uint32_t rtpTime;
CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
mLastNormalPlayTimeUs =
(((double)rtpTime - (double)mRTPTimeBase) / mClockRate)
* 1000000ll
+ mNormalPlayTimeBaseUs;
}
void APacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
int32_t damaged;
if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
@@ -613,4 +650,17 @@ void APacketSource::flushQueue() {
mBuffers.clear();
}
int64_t APacketSource::getNormalPlayTimeUs() {
Mutex::Autolock autoLock(mLock);
return mLastNormalPlayTimeUs;
}
void APacketSource::setNormalPlayTimeMapping(
uint32_t rtpTime, int64_t normalPlayTimeUs) {
Mutex::Autolock autoLock(mLock);
mRTPTimeBase = rtpTime;
mNormalPlayTimeBaseUs = normalPlayTimeUs;
}
} // namespace android

View File

@@ -45,6 +45,11 @@ struct APacketSource : public MediaSource {
void flushQueue();
int64_t getNormalPlayTimeUs();
void setNormalPlayTimeMapping(
uint32_t rtpTime, int64_t normalPlayTimeUs);
protected:
virtual ~APacketSource();
@@ -58,6 +63,15 @@ private:
List<sp<ABuffer> > mBuffers;
status_t mEOSResult;
uint32_t mClockRate;
uint32_t mRTPTimeBase;
int64_t mNormalPlayTimeBaseUs;
int64_t mLastNormalPlayTimeUs;
void updateNormalPlayTime_l(const sp<ABuffer> &buffer);
DISALLOW_EVIL_CONSTRUCTORS(APacketSource);
};

View File

@@ -35,18 +35,6 @@ ARTPAssembler::ARTPAssembler()
: mFirstFailureTimeUs(-1) {
}
void ARTPAssembler::PropagateTimes(
const sp<ABuffer> &from, const sp<ABuffer> &to) {
uint32_t rtpTime;
CHECK(from->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
uint64_t ntpTime = 0;
CHECK(from->meta()->findInt64("ntp-time", (int64_t *)&ntpTime));
to->meta()->setInt32("rtp-time", rtpTime);
to->meta()->setInt64("ntp-time", ntpTime);
}
void ARTPAssembler::onPacketReceived(const sp<ARTPSource> &source) {
AssemblyStatus status;
for (;;) {
@@ -75,4 +63,19 @@ void ARTPAssembler::onPacketReceived(const sp<ARTPSource> &source) {
}
}
// static
void ARTPAssembler::CopyTimes(const sp<ABuffer> &to, const sp<ABuffer> &from) {
uint64_t ntpTime;
CHECK(from->meta()->findInt64("ntp-time", (int64_t *)&ntpTime));
uint32_t rtpTime;
CHECK(from->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
to->meta()->setInt64("ntp-time", ntpTime);
to->meta()->setInt32("rtp-time", rtpTime);
// Copy the seq number.
to->setInt32Data(from->int32Data());
}
} // namespace android

View File

@@ -40,12 +40,11 @@ struct ARTPAssembler : public RefBase {
virtual void onByeReceived() = 0;
protected:
static void PropagateTimes(
const sp<ABuffer> &from, const sp<ABuffer> &to);
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source) = 0;
virtual void packetLost() = 0;
static void CopyTimes(const sp<ABuffer> &to, const sp<ABuffer> &from);
private:
int64_t mFirstFailureTimeUs;

View File

@@ -138,4 +138,9 @@ void ARTSPController::onMessageReceived(const sp<AMessage> &msg) {
}
}
int64_t ARTSPController::getNormalPlayTimeUs() {
CHECK(mHandler != NULL);
return mHandler->getNormalPlayTimeUs();
}
} // namespace android

View File

@@ -23,6 +23,8 @@
#include "ARTSPConnection.h"
#include "ASessionDescription.h"
#include <ctype.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
@@ -33,6 +35,34 @@
namespace android {
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;
}
}
struct MyHandler : public AHandler {
MyHandler(const char *url, const sp<ALooper> &looper)
: mLooper(looper),
@@ -43,7 +73,9 @@ struct MyHandler : public AHandler {
mSetupTracksSuccessful(false),
mSeekPending(false),
mFirstAccessUnit(true),
mFirstAccessUnitNTP(0) {
mFirstAccessUnitNTP(0),
mNumAccessUnitsReceived(0),
mCheckPending(false) {
mNetLooper->start(false /* runOnCallingThread */,
false /* canCallJava */,
@@ -76,6 +108,20 @@ struct MyHandler : public AHandler {
msg->post();
}
int64_t getNormalPlayTimeUs() {
int64_t maxTimeUs = 0;
for (size_t i = 0; i < mTracks.size(); ++i) {
int64_t timeUs = mTracks.editItemAt(i).mPacketSource
->getNormalPlayTimeUs();
if (i == 0 || timeUs > maxTimeUs) {
maxTimeUs = timeUs;
}
}
return maxTimeUs;
}
virtual void onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case 'conn':
@@ -269,6 +315,8 @@ struct MyHandler : public AHandler {
CHECK_EQ(response->mStatusCode, 200u);
parsePlayResponse(response);
mDoneMsg->setInt32("result", OK);
mDoneMsg->post();
mDoneMsg = NULL;
@@ -332,16 +380,38 @@ struct MyHandler : public AHandler {
break;
}
case 'chek':
{
if (mNumAccessUnitsReceived == 0) {
LOG(INFO) << "stream ended? aborting.";
(new AMessage('abor', id()))->post();
break;
}
mNumAccessUnitsReceived = 0;
msg->post(500000);
break;
}
case 'accu':
{
++mNumAccessUnitsReceived;
if (!mCheckPending) {
mCheckPending = true;
sp<AMessage> check = new AMessage('chek', id());
check->post(500000);
}
size_t trackIndex;
CHECK(msg->findSize("track-index", &trackIndex));
TrackInfo *track = &mTracks.editItemAt(trackIndex);
int32_t eos;
if (msg->findInt32("eos", &eos)) {
LOG(INFO) << "received BYE on track index " << trackIndex;
#if 0
TrackInfo *track = &mTracks.editItemAt(trackIndex);
track->mPacketSource->signalEOS(ERROR_END_OF_STREAM);
#endif
return;
@@ -352,10 +422,32 @@ struct MyHandler : public AHandler {
sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());
uint32_t seqNum = (uint32_t)accessUnit->int32Data();
if (seqNum < track->mFirstSeqNumInSegment) {
LOG(INFO) << "dropping stale access-unit "
<< "(" << seqNum << " < "
<< track->mFirstSeqNumInSegment << ")";
break;
}
uint64_t ntpTime;
CHECK(accessUnit->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
uint32_t rtpTime;
CHECK(accessUnit->meta()->findInt32(
"rtp-time", (int32_t *)&rtpTime));
if (track->mNewSegment) {
track->mNewSegment = false;
LOG(VERBOSE) << "first segment unit ntpTime="
<< StringPrintf("0x%016llx", ntpTime)
<< " rtpTime=" << rtpTime
<< " seq=" << seqNum;
}
if (mFirstAccessUnit) {
mFirstAccessUnit = false;
mFirstAccessUnitNTP = ntpTime;
@@ -414,6 +506,11 @@ struct MyHandler : public AHandler {
case 'see1':
{
// Session is paused now.
for (size_t i = 0; i < mTracks.size(); ++i) {
mTracks.editItemAt(i).mPacketSource->flushQueue();
}
int64_t timeUs;
CHECK(msg->findInt64("time", &timeUs));
@@ -440,15 +537,13 @@ struct MyHandler : public AHandler {
{
CHECK(mSeekPending);
LOG(INFO) << "seek completed.";
mSeekPending = false;
int32_t result;
CHECK(msg->findInt32("result", &result));
if (result != OK) {
LOG(ERROR) << "seek FAILED";
break;
}
LOG(INFO) << "PLAY completed with result "
<< result << " (" << strerror(-result) << ")";
CHECK_EQ(result, (status_t)OK);
sp<RefBase> obj;
CHECK(msg->findObject("response", &obj));
@@ -457,9 +552,10 @@ struct MyHandler : public AHandler {
CHECK_EQ(response->mStatusCode, 200u);
for (size_t i = 0; i < mTracks.size(); ++i) {
mTracks.editItemAt(i).mPacketSource->flushQueue();
}
parsePlayResponse(response);
LOG(INFO) << "seek completed.";
mSeekPending = false;
break;
}
@@ -491,6 +587,90 @@ struct MyHandler : public AHandler {
}
}
static void SplitString(
const AString &s, const char *separator, List<AString> *items) {
items->clear();
size_t start = 0;
while (start < s.size()) {
ssize_t offset = s.find(separator, start);
if (offset < 0) {
items->push_back(AString(s, start, s.size() - start));
break;
}
items->push_back(AString(s, start, offset - start));
start = offset + strlen(separator);
}
}
void parsePlayResponse(const sp<ARTSPResponse> &response) {
ssize_t i = response->mHeaders.indexOfKey("range");
if (i < 0) {
// Server doesn't even tell use what range it is going to
// play, therefore we won't support seeking.
return;
}
AString range = response->mHeaders.valueAt(i);
LOG(VERBOSE) << "Range: " << range;
AString val;
CHECK(GetAttribute(range.c_str(), "npt", &val));
float npt1, npt2;
if (val == "now-") {
// This is a live stream and therefore not seekable.
return;
} else {
CHECK_EQ(sscanf(val.c_str(), "%f-%f", &npt1, &npt2), 2);
}
i = response->mHeaders.indexOfKey("rtp-info");
CHECK_GE(i, 0);
AString rtpInfo = response->mHeaders.valueAt(i);
List<AString> streamInfos;
SplitString(rtpInfo, ",", &streamInfos);
int n = 1;
for (List<AString>::iterator it = streamInfos.begin();
it != streamInfos.end(); ++it) {
(*it).trim();
LOG(VERBOSE) << "streamInfo[" << n << "] = " << *it;
CHECK(GetAttribute((*it).c_str(), "url", &val));
size_t trackIndex = 0;
while (trackIndex < mTracks.size()
&& !(val == mTracks.editItemAt(trackIndex).mURL)) {
++trackIndex;
}
CHECK_LT(trackIndex, mTracks.size());
CHECK(GetAttribute((*it).c_str(), "seq", &val));
char *end;
unsigned long seq = strtoul(val.c_str(), &end, 10);
TrackInfo *info = &mTracks.editItemAt(trackIndex);
info->mFirstSeqNumInSegment = seq;
info->mNewSegment = true;
CHECK(GetAttribute((*it).c_str(), "rtptime", &val));
uint32_t rtpTime = strtoul(val.c_str(), &end, 10);
LOG(VERBOSE) << "track #" << n
<< ": rtpTime=" << rtpTime << " <=> npt=" << npt1;
info->mPacketSource->setNormalPlayTimeMapping(
rtpTime, (int64_t)(npt1 * 1E6));
++n;
}
}
sp<APacketSource> getPacketSource(size_t index) {
CHECK_GE(index, 0u);
CHECK_LT(index, mTracks.size());
@@ -515,11 +695,16 @@ private:
bool mSeekPending;
bool mFirstAccessUnit;
uint64_t mFirstAccessUnitNTP;
int64_t mNumAccessUnitsReceived;
bool mCheckPending;
struct TrackInfo {
AString mURL;
int mRTPSocket;
int mRTCPSocket;
bool mUsingInterleavedTCP;
uint32_t mFirstSeqNumInSegment;
bool mNewSegment;
sp<APacketSource> mPacketSource;
};
@@ -549,8 +734,13 @@ private:
mTracks.push(TrackInfo());
TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
info->mURL = trackURL;
info->mPacketSource = source;
info->mUsingInterleavedTCP = false;
info->mFirstSeqNumInSegment = 0;
info->mNewSegment = true;
LOG(VERBOSE) << "track #" << mTracks.size() << " URL=" << trackURL;
AString request = "SETUP ";
request.append(trackURL);