related-to-bug:
6870049
Squashed commit of the following:
commit
eee2f3ba6bb7335f4e285632726db85645669929
Author: Andreas Huber <andih@google.com>
Date: Tue Nov 27 15:02:01 2012 -0800
Make everything a lot less verbose by default.
Change-Id: I884d7a7901aa1e7d4ff590f065ca57a79d2af8b3
commit
6bbdb837ed5bd88008e45efb8faf595e4051ba26
Author: Andreas Huber <andih@google.com>
Date: Tue Nov 27 14:34:46 2012 -0800
HLS now properly signals media time changes at discontinuities including
the start of playback (which may not necessarily be at time 0 if the playlist
is of type 'event' and hasn't completed yet).
Change-Id: I5ab747d024f9b8d0df72a4e06a12ebb29f62802e
commit
1555589832b1878a144a976a643e1af4d61f877c
Author: Andreas Huber <andih@google.com>
Date: Tue Nov 27 14:32:28 2012 -0800
As part of a time discontinuity, clients of IStreamListener can now
signal the corresponding media time after the discontinuity, i.e. the first PTS
timestamp following the discontinuity will be considered equivalent to the
specified media time and media buffers timestamped accordingly.
Change-Id: Id7db7679b7faa6efd6270620ff52e34e884f3e92
commit
5c24c605c073a11c426d025b1e7478fc1ad8365a
Author: Andreas Huber <andih@google.com>
Date: Tue Nov 27 13:00:56 2012 -0800
NuPlayer sources now expose flags() and can announce
that duration may change (increase) dynamically, in which case duration
will be polled at 1 second intervals and communicated to the upper layers.
Change-Id: I45102909b7a19eed0dda576747e3814d742a0eea
commit
ecb71de8e281e61971a2cd73e7161a97540bc357
Author: Andreas Huber <andih@google.com>
Date: Tue Nov 27 12:57:47 2012 -0800
Stop caching duration in MediaPlayer, duration could increase dynamically.
Change-Id: I7bb2f16c0abe49debdf45c776d2266aa069d7791
commit
544aec5823e6d7a3e97e15b6b23546616bcd343e
Author: Andreas Huber <andih@google.com>
Date: Tue Nov 27 08:46:28 2012 -0800
An attempt to add support for "event" style HLS playlists.
Change-Id: I3dfb2e801ecaff8f5d8bdb3a4fca1b18aeeb2c60
Change-Id: I48cf7f65a654d33f2f49ded74f8be22aed9e3b98
// ATSParser::DiscontinuityType.
static const char *const kKeyDiscontinuityMask;
+ // Optionally signalled as part of a discontinuity that includes
+ // DISCONTINUITY_TIME. It indicates the media time (in us) to be associated
+ // with the next PTS occuring in the stream. The value is of type int64_t.
+ static const char *const kKeyMediaTimeUs;
+
virtual void issueCommand(
Command cmd, bool synchronous, const sp<AMessage> &msg = NULL) = 0;
};
sp<MediaPlayerListener> mListener;
void* mCookie;
media_player_states mCurrentState;
- int mDuration;
int mCurrentPosition;
int mSeekPosition;
bool mPrepareSync;
// static
const char *const IStreamListener::kKeyDiscontinuityMask = "discontinuity-mask";
+// static
+const char *const IStreamListener::kKeyMediaTimeUs = "media-time-us";
+
enum {
// IStreamSource
SET_LISTENER = IBinder::FIRST_CALL_TRANSACTION,
ALOGV("constructor");
mListener = NULL;
mCookie = NULL;
- mDuration = -1;
mStreamType = AUDIO_STREAM_MUSIC;
mCurrentPosition = -1;
mSeekPosition = -1;
// always call with lock held
void MediaPlayer::clear_l()
{
- mDuration = -1;
mCurrentPosition = -1;
mSeekPosition = -1;
mVideoWidth = mVideoHeight = 0;
status_t MediaPlayer::getDuration_l(int *msec)
{
- ALOGV("getDuration");
+ ALOGV("getDuration_l");
bool isValidState = (mCurrentState & (MEDIA_PLAYER_PREPARED | MEDIA_PLAYER_STARTED | MEDIA_PLAYER_PAUSED | MEDIA_PLAYER_STOPPED | MEDIA_PLAYER_PLAYBACK_COMPLETE));
if (mPlayer != 0 && isValidState) {
- status_t ret = NO_ERROR;
- if (mDuration <= 0)
- ret = mPlayer->getDuration(&mDuration);
- if (msec)
- *msec = mDuration;
+ int durationMs;
+ status_t ret = mPlayer->getDuration(&durationMs);
+ if (msec) {
+ *msec = durationMs;
+ }
return ret;
}
ALOGE("Attempt to call getDuration without a valid mediaplayer");
if ( msec < 0 ) {
ALOGW("Attempt to seek to invalid position: %d", msec);
msec = 0;
- } else if ((mDuration > 0) && (msec > mDuration)) {
- ALOGW("Attempt to seek to past end of file: request = %d, EOF = %d", msec, mDuration);
- msec = mDuration;
}
+
+ int durationMs;
+ status_t err = mPlayer->getDuration(&durationMs);
+
+ if (err != OK) {
+ ALOGW("Stream has no duration and is therefore not seekable.");
+ return err;
+ }
+
+ if (msec > durationMs) {
+ ALOGW("Attempt to seek to past end of file: request = %d, "
+ "durationMs = %d",
+ msec,
+ durationMs);
+
+ msec = durationMs;
+ }
+
// cache duration
mCurrentPosition = msec;
if (mSeekPosition < 0) {
- getDuration_l(NULL);
mSeekPosition = msec;
return mPlayer->seekTo(msec);
}
}
}
-bool NuPlayer::GenericSource::isSeekable() {
- return true;
+uint32_t NuPlayer::GenericSource::flags() const {
+ return FLAG_SEEKABLE;
}
} // namespace android
virtual status_t getDuration(int64_t *durationUs);
virtual status_t seekTo(int64_t seekTimeUs);
- virtual bool isSeekable();
+
+ virtual uint32_t flags() const;
protected:
virtual ~GenericSource();
} else {
if (buffer[0] == 0x00) {
// XXX legacy
- sp<AMessage> extra;
+
+ uint8_t type = buffer[1];
+
+ sp<AMessage> extra = new AMessage;
+
+ if (type & 2) {
+ int64_t mediaTimeUs;
+ memcpy(&mediaTimeUs, &buffer[2], sizeof(mediaTimeUs));
+
+ extra->setInt64(IStreamListener::kKeyMediaTimeUs, mediaTimeUs);
+ }
+
mTSParser->signalDiscontinuity(
- buffer[1] == 0x00
+ ((type & 1) == 0)
? ATSParser::DISCONTINUITY_SEEK
: ATSParser::DISCONTINUITY_FORMATCHANGE,
extra);
return OK;
}
-bool NuPlayer::HTTPLiveSource::isSeekable() {
- return mLiveSession->isSeekable();
+uint32_t NuPlayer::HTTPLiveSource::flags() const {
+ uint32_t flags = 0;
+ if (mLiveSession->isSeekable()) {
+ flags |= FLAG_SEEKABLE;
+ }
+
+ if (mLiveSession->hasDynamicDuration()) {
+ flags |= FLAG_DYNAMIC_DURATION;
+ }
+
+ return flags;
}
} // namespace android
virtual status_t getDuration(int64_t *durationUs);
virtual status_t seekTo(int64_t seekTimeUs);
- virtual bool isSeekable();
+
+ virtual uint32_t flags() const;
protected:
virtual ~HTTPLiveSource();
mVideoEOS(false),
mScanSourcesPending(false),
mScanSourcesGeneration(0),
+ mPollDurationGeneration(0),
mTimeDiscontinuityPending(false),
mFlushingAudio(NONE),
mFlushingVideo(NONE),
break;
}
+ case kWhatPollDuration:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mPollDurationGeneration) {
+ // stale
+ break;
+ }
+
+ int64_t durationUs;
+ if (mDriver != NULL && mSource->getDuration(&durationUs) == OK) {
+ sp<NuPlayerDriver> driver = mDriver.promote();
+ if (driver != NULL) {
+ driver->notifyDuration(durationUs);
+ }
+ }
+
+ msg->post(1000000ll); // poll again in a second.
+ break;
+ }
+
case kWhatSetVideoNativeWindow:
{
ALOGV("kWhatSetVideoNativeWindow");
ALOGV("scanning sources haveAudio=%d, haveVideo=%d",
mAudioDecoder != NULL, mVideoDecoder != NULL);
+ bool mHadAnySourcesBefore =
+ (mAudioDecoder != NULL) || (mVideoDecoder != NULL);
+
if (mNativeWindow != NULL) {
instantiateDecoder(false, &mVideoDecoder);
}
instantiateDecoder(true, &mAudioDecoder);
}
+ if (!mHadAnySourcesBefore
+ && (mAudioDecoder != NULL || mVideoDecoder != NULL)) {
+ // This is the first time we've found anything playable.
+
+ uint32_t flags = mSource->flags();
+
+ if (flags & Source::FLAG_DYNAMIC_DURATION) {
+ schedulePollDuration();
+ }
+ }
+
status_t err;
if ((err = mSource->feedMoreTSData()) != OK) {
if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
{
ALOGV("kWhatReset");
+ cancelPollDuration();
+
if (mRenderer != NULL) {
// There's an edge case where the renderer owns all output
// buffers and is paused, therefore the decoder will not read
return OK;
}
+void NuPlayer::schedulePollDuration() {
+ sp<AMessage> msg = new AMessage(kWhatPollDuration, id());
+ msg->setInt32("generation", mPollDurationGeneration);
+ msg->post();
+}
+
+void NuPlayer::cancelPollDuration() {
+ ++mPollDurationGeneration;
+}
+
} // namespace android
kWhatSeek = 'seek',
kWhatPause = 'paus',
kWhatResume = 'rsme',
+ kWhatPollDuration = 'polD',
};
wp<NuPlayerDriver> mDriver;
bool mScanSourcesPending;
int32_t mScanSourcesGeneration;
+ int32_t mPollDurationGeneration;
+
enum FlushStatus {
NONE,
AWAITING_DISCONTINUITY,
void finishReset();
void postScanSources();
+ void schedulePollDuration();
+ void cancelPollDuration();
+
DISALLOW_EVIL_CONSTRUCTORS(NuPlayer);
};
struct ABuffer;
struct NuPlayer::Source : public RefBase {
+ enum Flags {
+ FLAG_SEEKABLE = 1,
+ FLAG_DYNAMIC_DURATION = 2,
+ };
+
Source() {}
virtual void start() = 0;
return INVALID_OPERATION;
}
- virtual bool isSeekable() {
- return false;
- }
+ virtual uint32_t flags() const = 0;
protected:
virtual ~Source() {}
mHandler->seek(seekTimeUs);
}
-bool NuPlayer::RTSPSource::isSeekable() {
- return true;
+uint32_t NuPlayer::RTSPSource::flags() const {
+ return FLAG_SEEKABLE;
}
void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) {
virtual status_t getDuration(int64_t *durationUs);
virtual status_t seekTo(int64_t seekTimeUs);
- virtual bool isSeekable();
+
+ virtual uint32_t flags() const;
void onMessageReceived(const sp<AMessage> &msg);
} else {
if (buffer[0] == 0x00) {
// XXX legacy
+
+ if (extra == NULL) {
+ extra = new AMessage;
+ }
+
+ uint8_t type = buffer[1];
+
+ if (type & 2) {
+ int64_t mediaTimeUs;
+ memcpy(&mediaTimeUs, &buffer[2], sizeof(mediaTimeUs));
+
+ extra->setInt64(IStreamListener::kKeyMediaTimeUs, mediaTimeUs);
+ }
+
mTSParser->signalDiscontinuity(
- buffer[1] == 0x00
+ ((type & 1) == 0)
? ATSParser::DISCONTINUITY_SEEK
: ATSParser::DISCONTINUITY_FORMATCHANGE,
extra);
return err;
}
+uint32_t NuPlayer::StreamingSource::flags() const {
+ return 0;
+}
+
} // namespace android
virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+ virtual uint32_t flags() const;
+
protected:
virtual ~StreamingSource();
return mParser->dequeueAccessUnit(audio, accessUnit);
}
+uint32_t MP4Source::flags() const {
+ return 0;
+}
+
} // namespace android
virtual status_t dequeueAccessUnit(
bool audio, sp<ABuffer> *accessUnit);
+ virtual uint32_t flags() const;
+
protected:
virtual ~MP4Source();
if (getUID(&uid)) {
mDelegate->setUID(uid);
}
+
+#if defined(LOG_NDEBUG) && !LOG_NDEBUG
LOG_PRI(ANDROID_LOG_VERBOSE, LOG_TAG, "connect on behalf of uid %d", uid);
+#endif
return connect_l(uri, headers, offset);
}
disconnect_l();
}
- LOG_PRI(ANDROID_LOG_INFO, LOG_TAG,
+#if defined(LOG_NDEBUG) && !LOG_NDEBUG
+ LOG_PRI(ANDROID_LOG_VERBOSE, LOG_TAG,
"connect to <URL suppressed> @%lld", offset);
+#endif
mURI = uri;
mContentType = String8("application/octet-stream");
mSeqNumber(-1),
mSeekTimeUs(-1),
mNumRetries(0),
+ mStartOfPlayback(true),
mDurationUs(-1),
+ mDurationFixed(false),
mSeekDone(false),
mDisconnectPending(false),
mMonitorQueueGeneration(0),
}
sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
+ ALOGV("fetchPlaylist '%s'", url);
+
*unchanged = false;
sp<ABuffer> buffer;
return playlist;
}
+int64_t LiveSession::getSegmentStartTimeUs(int32_t seqNumber) const {
+ CHECK(mPlaylist != NULL);
+
+ int32_t firstSeqNumberInPlaylist;
+ if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
+ "media-sequence", &firstSeqNumberInPlaylist)) {
+ firstSeqNumberInPlaylist = 0;
+ }
+
+ int32_t lastSeqNumberInPlaylist =
+ firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
+
+ CHECK_GE(seqNumber, firstSeqNumberInPlaylist);
+ CHECK_LE(seqNumber, lastSeqNumberInPlaylist);
+
+ int64_t segmentStartUs = 0ll;
+ for (int32_t index = 0;
+ index < seqNumber - firstSeqNumberInPlaylist; ++index) {
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(
+ index, NULL /* uri */, &itemMeta));
+
+ int64_t itemDurationUs;
+ CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+ segmentStartUs += itemDurationUs;
+ }
+
+ return segmentStartUs;
+}
+
static double uniformRand() {
return (double)rand() / RAND_MAX;
}
url = mMasterURL;
}
- bool firstTime = (mPlaylist == NULL);
-
if ((ssize_t)bandwidthIndex != mPrevBandwidthIndex) {
// If we switch bandwidths, do not pay any heed to whether
// playlists changed since the last time...
mPlaylist = playlist;
}
- if (firstTime) {
+ if (!mDurationFixed) {
Mutex::Autolock autoLock(mLock);
- if (!mPlaylist->isComplete()) {
+ if (!mPlaylist->isComplete() && !mPlaylist->isEvent()) {
mDurationUs = -1;
+ mDurationFixed = true;
} else {
mDurationUs = 0;
for (size_t i = 0; i < mPlaylist->size(); ++i) {
mDurationUs += itemDurationUs;
}
+
+ mDurationFixed = mPlaylist->isComplete();
}
}
bool bandwidthChanged = false;
if (mSeekTimeUs >= 0) {
- if (mPlaylist->isComplete()) {
+ if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
size_t index = 0;
int64_t segmentStartUs = 0;
while (index < mPlaylist->size()) {
mCondition.broadcast();
}
+ const int32_t lastSeqNumberInPlaylist =
+ firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
+
if (mSeqNumber < 0) {
- mSeqNumber = firstSeqNumberInPlaylist;
+ if (mPlaylist->isComplete()) {
+ mSeqNumber = firstSeqNumberInPlaylist;
+ } else {
+ // If this is a live session, start 3 segments from the end.
+ mSeqNumber = lastSeqNumberInPlaylist - 3;
+ if (mSeqNumber < firstSeqNumberInPlaylist) {
+ mSeqNumber = firstSeqNumberInPlaylist;
+ }
+ }
}
- int32_t lastSeqNumberInPlaylist =
- firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
-
if (mSeqNumber < firstSeqNumberInPlaylist
|| mSeqNumber > lastSeqNumberInPlaylist) {
if (mPrevBandwidthIndex != (ssize_t)bandwidthIndex) {
range_length = -1;
}
+ ALOGV("fetching segment %d from (%d .. %d)",
+ mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist);
+
sp<ABuffer> buffer;
status_t err = fetchFile(uri.c_str(), &buffer, range_offset, range_length);
if (err != OK) {
bandwidthChanged = false;
}
+ if (mStartOfPlayback) {
+ seekDiscontinuity = true;
+ mStartOfPlayback = false;
+ }
+
if (seekDiscontinuity || explicitDiscontinuity || bandwidthChanged) {
// Signal discontinuity.
memset(tmp->data(), 0, tmp->size());
// signal a 'hard' discontinuity for explicit or bandwidthChanged.
- tmp->data()[1] = (explicitDiscontinuity || bandwidthChanged) ? 1 : 0;
+ uint8_t type = (explicitDiscontinuity || bandwidthChanged) ? 1 : 0;
+
+ if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+ // If this was a live event this made no sense since
+ // we don't have access to all the segment before the current
+ // one.
+ int64_t segmentStartTimeUs = getSegmentStartTimeUs(mSeqNumber);
+ memcpy(tmp->data() + 2, &segmentStartTimeUs, sizeof(segmentStartTimeUs));
+
+ type |= 2;
+ }
+
+ tmp->data()[1] = type;
mDataSource->queueBuffer(tmp);
}
postMonitorQueue();
}
-status_t LiveSession::getDuration(int64_t *durationUs) {
+status_t LiveSession::getDuration(int64_t *durationUs) const {
Mutex::Autolock autoLock(mLock);
*durationUs = mDurationUs;
return OK;
}
-bool LiveSession::isSeekable() {
+bool LiveSession::isSeekable() const {
int64_t durationUs;
return getDuration(&durationUs) == OK && durationUs >= 0;
}
+bool LiveSession::hasDynamicDuration() const {
+ return !mDurationFixed;
+}
+
} // namespace android
mBaseURI(baseURI),
mIsExtM3U(false),
mIsVariantPlaylist(false),
- mIsComplete(false) {
+ mIsComplete(false),
+ mIsEvent(false) {
mInitCheck = parse(data, size);
}
return mIsComplete;
}
+bool M3UParser::isEvent() const {
+ return mIsEvent;
+}
+
sp<AMessage> M3UParser::meta() {
return mMeta;
}
err = parseCipherInfo(line, &itemMeta, mBaseURI);
} else if (line.startsWith("#EXT-X-ENDLIST")) {
mIsComplete = true;
+ } else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:EVENT")) {
+ mIsEvent = true;
} else if (line.startsWith("#EXTINF")) {
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
// Blocks until seek is complete.
void seekTo(int64_t timeUs);
- status_t getDuration(int64_t *durationUs);
- bool isSeekable();
+ status_t getDuration(int64_t *durationUs) const;
+
+ bool isSeekable() const;
+ bool hasDynamicDuration() const;
protected:
virtual ~LiveSession();
int32_t mSeqNumber;
int64_t mSeekTimeUs;
int32_t mNumRetries;
+ bool mStartOfPlayback;
- Mutex mLock;
+ mutable Mutex mLock;
Condition mCondition;
int64_t mDurationUs;
+ bool mDurationFixed; // Duration has been determined once and for all.
bool mSeekDone;
bool mDisconnectPending;
static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
+ // Returns the media time in us of the segment specified by seqNumber.
+ // This is computed by summing the durations of all segments before it.
+ int64_t getSegmentStartTimeUs(int32_t seqNumber) const;
+
DISALLOW_EVIL_CONSTRUCTORS(LiveSession);
};
bool isExtM3U() const;
bool isVariantPlaylist() const;
bool isComplete() const;
+ bool isEvent() const;
sp<AMessage> meta();
bool mIsExtM3U;
bool mIsVariantPlaylist;
bool mIsComplete;
+ bool mIsEvent;
sp<AMessage> mMeta;
Vector<Item> mItems;
void ATSParser::Program::signalDiscontinuity(
DiscontinuityType type, const sp<AMessage> &extra) {
+ int64_t mediaTimeUs;
+ if ((type & DISCONTINUITY_TIME)
+ && extra != NULL
+ && extra->findInt64(
+ IStreamListener::kKeyMediaTimeUs, &mediaTimeUs)) {
+ mFirstPTSValid = false;
+ }
+
for (size_t i = 0; i < mStreams.size(); ++i) {
mStreams.editValueAt(i)->signalDiscontinuity(type, extra);
}
void ATSParser::signalDiscontinuity(
DiscontinuityType type, const sp<AMessage> &extra) {
- if (type == DISCONTINUITY_ABSOLUTE_TIME) {
+ int64_t mediaTimeUs;
+ if ((type & DISCONTINUITY_TIME)
+ && extra != NULL
+ && extra->findInt64(
+ IStreamListener::kKeyMediaTimeUs, &mediaTimeUs)) {
+ mAbsoluteTimeAnchorUs = mediaTimeUs;
+ } else if (type == DISCONTINUITY_ABSOLUTE_TIME) {
int64_t timeUs;
CHECK(extra->findInt64("timeUs", &timeUs));