From efbb61950db36a5eb789be83f077246172507c67 Mon Sep 17 00:00:00 2001 From: Chong Zhang Date: Fri, 30 Jan 2015 17:13:27 -0800 Subject: NuPlayer: pause playback when buffering is low also fix buffering percentage report (should be the buffered position) bug: 18730095 Change-Id: I11e7ca4ba9e772a1ae76861ca1ff1725b62f65ae --- .../nuplayer/GenericSource.cpp | 189 ++++++++++++++++++--- .../libmediaplayerservice/nuplayer/GenericSource.h | 13 +- media/libmediaplayerservice/nuplayer/NuPlayer.cpp | 69 ++++++-- media/libmediaplayerservice/nuplayer/NuPlayer.h | 9 + .../nuplayer/NuPlayerSource.h | 3 + media/libstagefright/NuCachedSource2.cpp | 2 +- 6 files changed, 251 insertions(+), 34 deletions(-) diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp index 76b80bb..9b446b8 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp +++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp @@ -40,6 +40,11 @@ namespace android { +static int64_t kLowWaterMarkUs = 2000000ll; // 2secs +static int64_t kHighWaterMarkUs = 5000000ll; // 5secs +static const ssize_t kLowWaterMarkBytes = 40000; +static const ssize_t kHighWaterMarkBytes = 200000; + NuPlayer::GenericSource::GenericSource( const sp ¬ify, bool uidValid, @@ -55,6 +60,7 @@ NuPlayer::GenericSource::GenericSource( mAudioIsVorbis(false), mIsWidevine(false), mIsSecure(false), + mIsStreaming(false), mUIDValid(uidValid), mUID(uid), mFd(-1), @@ -62,7 +68,9 @@ NuPlayer::GenericSource::GenericSource( mMetaDataSize(-1ll), mBitrate(-1ll), mPollBufferingGeneration(0), - mPendingReadBufferTypes(0) { + mPendingReadBufferTypes(0), + mBuffering(false), + mPrepareBuffering(false) { resetDataSource(); DataSource::RegisterDefaultSniffers(); } @@ -254,6 +262,20 @@ status_t NuPlayer::GenericSource::initFromDataSource() { } } + // Start the selected A/V tracks now before we start buffering. + // Widevine sources might re-initialize crypto when starting, if we delay + // this to start(), all data buffered during prepare would be wasted. + // (We don't actually start reading until start().) + if (mAudioTrack.mSource != NULL && mAudioTrack.mSource->start() != OK) { + ALOGE("failed to start audio track!"); + return UNKNOWN_ERROR; + } + + if (mVideoTrack.mSource != NULL && mVideoTrack.mSource->start() != OK) { + ALOGE("failed to start video track!"); + return UNKNOWN_ERROR; + } + mBitrate = totalBitrate; return OK; @@ -352,9 +374,13 @@ void NuPlayer::GenericSource::onPrepareAsync() { mCachedSource = static_cast(mDataSource.get()); } - if (mIsWidevine || mCachedSource != NULL) { - schedulePollBuffering(); - } + // For widevine or other cached streaming cases, we need to wait for + // enough buffering before reporting prepared. + // Note that even when URL doesn't start with widevine://, mIsWidevine + // could still be set to true later, if the streaming or file source + // is sniffed to be widevine. We don't want to buffer for file source + // in that case, so must check the flag now. + mIsStreaming = (mIsWidevine || mCachedSource != NULL); } // check initial caching status @@ -397,7 +423,14 @@ void NuPlayer::GenericSource::onPrepareAsync() { | FLAG_CAN_SEEK_FORWARD | FLAG_CAN_SEEK); - notifyPrepared(); + if (mIsStreaming) { + mPrepareBuffering = true; + + ensureCacheIsFetching(); + restartPollBuffering(); + } else { + notifyPrepared(); + } } void NuPlayer::GenericSource::notifyPreparedAndCleanup(status_t err) { @@ -489,19 +522,17 @@ void NuPlayer::GenericSource::start() { mStopRead = false; if (mAudioTrack.mSource != NULL) { - CHECK_EQ(mAudioTrack.mSource->start(), (status_t)OK); - postReadBuffer(MEDIA_TRACK_TYPE_AUDIO); } if (mVideoTrack.mSource != NULL) { - CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK); - postReadBuffer(MEDIA_TRACK_TYPE_VIDEO); } setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000); mStarted = true; + + (new AMessage(kWhatStart, id()))->post(); } void NuPlayer::GenericSource::stop() { @@ -526,6 +557,8 @@ void NuPlayer::GenericSource::resume() { // nothing to do, just account for DRM playback status setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000); mStarted = true; + + (new AMessage(kWhatResume, id()))->post(); } void NuPlayer::GenericSource::disconnect() { @@ -558,22 +591,98 @@ void NuPlayer::GenericSource::schedulePollBuffering() { } void NuPlayer::GenericSource::cancelPollBuffering() { + mBuffering = false; ++mPollBufferingGeneration; } +void NuPlayer::GenericSource::restartPollBuffering() { + if (mIsStreaming) { + cancelPollBuffering(); + onPollBuffering(); + } +} + void NuPlayer::GenericSource::notifyBufferingUpdate(int percentage) { + ALOGV("notifyBufferingUpdate: buffering %d%%", percentage); + sp msg = dupNotify(); msg->setInt32("what", kWhatBufferingUpdate); msg->setInt32("percentage", percentage); msg->post(); } +void NuPlayer::GenericSource::startBufferingIfNecessary() { + ALOGV("startBufferingIfNecessary: mPrepareBuffering=%d, mBuffering=%d", + mPrepareBuffering, mBuffering); + + if (mPrepareBuffering) { + return; + } + + if (!mBuffering) { + mBuffering = true; + + ensureCacheIsFetching(); + sendCacheStats(); + + sp notify = dupNotify(); + notify->setInt32("what", kWhatPauseOnBufferingStart); + notify->post(); + } +} + +void NuPlayer::GenericSource::stopBufferingIfNecessary() { + ALOGV("stopBufferingIfNecessary: mPrepareBuffering=%d, mBuffering=%d", + mPrepareBuffering, mBuffering); + + if (mPrepareBuffering) { + mPrepareBuffering = false; + notifyPrepared(); + return; + } + + if (mBuffering) { + mBuffering = false; + + sendCacheStats(); + + sp notify = dupNotify(); + notify->setInt32("what", kWhatResumeOnBufferingEnd); + notify->post(); + } +} + +void NuPlayer::GenericSource::sendCacheStats() { + int32_t kbps = 0; + status_t err = UNKNOWN_ERROR; + + if (mCachedSource != NULL) { + err = mCachedSource->getEstimatedBandwidthKbps(&kbps); + } else if (mWVMExtractor != NULL) { + err = mWVMExtractor->getEstimatedBandwidthKbps(&kbps); + } + + if (err == OK) { + sp notify = dupNotify(); + notify->setInt32("what", kWhatCacheStats); + notify->setInt32("bandwidth", kbps); + notify->post(); + } +} + +void NuPlayer::GenericSource::ensureCacheIsFetching() { + if (mCachedSource != NULL) { + mCachedSource->resumeFetchingIfNecessary(); + } +} + void NuPlayer::GenericSource::onPollBuffering() { status_t finalStatus = UNKNOWN_ERROR; - int64_t cachedDurationUs = 0ll; + int64_t cachedDurationUs = -1ll; + ssize_t cachedDataRemaining = -1; if (mCachedSource != NULL) { - size_t cachedDataRemaining = + cachedDataRemaining = mCachedSource->approxDataRemaining(&finalStatus); if (finalStatus == OK) { @@ -593,23 +702,48 @@ void NuPlayer::GenericSource::onPollBuffering() { = mWVMExtractor->getCachedDurationUs(&finalStatus); } - if (finalStatus == ERROR_END_OF_STREAM) { - notifyBufferingUpdate(100); - cancelPollBuffering(); + if (finalStatus != OK) { + ALOGV("onPollBuffering: EOS (finalStatus = %d)", finalStatus); + + if (finalStatus == ERROR_END_OF_STREAM) { + notifyBufferingUpdate(100); + } + + stopBufferingIfNecessary(); return; - } else if (cachedDurationUs > 0ll && mDurationUs > 0ll) { - int percentage = 100.0 * cachedDurationUs / mDurationUs; - if (percentage > 100) { - percentage = 100; + } else if (cachedDurationUs >= 0ll) { + if (mDurationUs > 0ll) { + int64_t cachedPosUs = getLastReadPosition() + cachedDurationUs; + int percentage = 100.0 * cachedPosUs / mDurationUs; + if (percentage > 100) { + percentage = 100; + } + + notifyBufferingUpdate(percentage); } - notifyBufferingUpdate(percentage); + ALOGV("onPollBuffering: cachedDurationUs %.1f sec", + cachedDurationUs / 1000000.0f); + + if (cachedDurationUs < kLowWaterMarkUs) { + startBufferingIfNecessary(); + } else if (cachedDurationUs > kHighWaterMarkUs) { + stopBufferingIfNecessary(); + } + } else if (cachedDataRemaining >= 0) { + ALOGV("onPollBuffering: cachedDataRemaining %d bytes", + cachedDataRemaining); + + if (cachedDataRemaining < kLowWaterMarkBytes) { + startBufferingIfNecessary(); + } else if (cachedDataRemaining > kHighWaterMarkBytes) { + stopBufferingIfNecessary(); + } } schedulePollBuffering(); } - void NuPlayer::GenericSource::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatPrepareAsync: @@ -688,6 +822,14 @@ void NuPlayer::GenericSource::onMessageReceived(const sp &msg) { break; } + + case kWhatStart: + case kWhatResume: + { + restartPollBuffering(); + break; + } + case kWhatPollBuffering: { int32_t generation; @@ -1201,6 +1343,13 @@ status_t NuPlayer::GenericSource::doSeek(int64_t seekTimeUs) { if (!mStarted) { setDrmPlaybackStatusIfNeeded(Playback::PAUSE, 0); } + + // If currently buffering, post kWhatBufferingEnd first, so that + // NuPlayer resumes. Otherwise, if cache hits high watermark + // before new polling happens, no one will resume the playback. + stopBufferingIfNecessary(); + restartPollBuffering(); + return OK; } diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h index 1b63a1f..385d73a 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.h +++ b/media/libmediaplayerservice/nuplayer/GenericSource.h @@ -94,16 +94,17 @@ private: kWhatSeek, kWhatReadBuffer, kWhatStopWidevine, + kWhatStart, + kWhatResume, }; - Vector > mSources; - struct Track { size_t mIndex; sp mSource; sp mPackets; }; + Vector > mSources; Track mAudioTrack; int64_t mAudioTimeUs; int64_t mAudioLastDequeueTimeUs; @@ -119,6 +120,7 @@ private: bool mAudioIsVorbis; bool mIsWidevine; bool mIsSecure; + bool mIsStreaming; bool mUIDValid; uid_t mUID; sp mHTTPService; @@ -143,6 +145,8 @@ private: int64_t mBitrate; int32_t mPollBufferingGeneration; uint32_t mPendingReadBufferTypes; + bool mBuffering; + bool mPrepareBuffering; mutable Mutex mReadBufferLock; sp mLooper; @@ -194,8 +198,13 @@ private: void schedulePollBuffering(); void cancelPollBuffering(); + void restartPollBuffering(); void onPollBuffering(); void notifyBufferingUpdate(int percentage); + void startBufferingIfNecessary(); + void stopBufferingIfNecessary(); + void sendCacheStats(); + void ensureCacheIsFetching(); DISALLOW_EVIL_CONSTRUCTORS(GenericSource); }; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index e02a2d5..1f55706 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -180,7 +180,9 @@ NuPlayer::NuPlayer() mFlushingVideo(NONE), mResumePending(false), mVideoScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW), - mStarted(false) { + mStarted(false), + mPaused(false), + mPausedByClient(false) { clearFlushComplete(); } @@ -598,6 +600,7 @@ void NuPlayer::onMessageReceived(const sp &msg) { } else { onStart(); } + mPausedByClient = false; break; } @@ -956,16 +959,8 @@ void NuPlayer::onMessageReceived(const sp &msg) { case kWhatPause: { - if (mSource != NULL) { - mSource->pause(); - } else { - ALOGW("pause called when source is gone or not set"); - } - if (mRenderer != NULL) { - mRenderer->pause(); - } else { - ALOGW("pause called when renderer is gone or not set"); - } + onPause(); + mPausedByClient = true; break; } @@ -988,6 +983,10 @@ void NuPlayer::onMessageReceived(const sp &msg) { } void NuPlayer::onResume() { + if (!mPaused) { + return; + } + mPaused = false; if (mSource != NULL) { mSource->resume(); } else { @@ -1072,6 +1071,23 @@ void NuPlayer::onStart() { postScanSources(); } +void NuPlayer::onPause() { + if (mPaused) { + return; + } + mPaused = true; + if (mSource != NULL) { + mSource->pause(); + } else { + ALOGW("pause called when source is gone or not set"); + } + if (mRenderer != NULL) { + mRenderer->pause(); + } else { + ALOGW("pause called when renderer is gone or not set"); + } +} + bool NuPlayer::audioDecoderStillNeeded() { // Audio decoder is no longer needed if it's in shut/shutting down status. return ((mFlushingAudio != SHUT_DOWN) && (mFlushingAudio != SHUTTING_DOWN_DECODER)); @@ -1709,18 +1725,49 @@ void NuPlayer::onSourceNotify(const sp &msg) { break; } + case Source::kWhatPauseOnBufferingStart: + { + // ignore if not playing + if (mStarted && !mPausedByClient) { + ALOGI("buffer low, pausing..."); + + onPause(); + } + // fall-thru + } + case Source::kWhatBufferingStart: { notifyListener(MEDIA_INFO, MEDIA_INFO_BUFFERING_START, 0); break; } + case Source::kWhatResumeOnBufferingEnd: + { + // ignore if not playing + if (mStarted && !mPausedByClient) { + ALOGI("buffer ready, resuming..."); + + onResume(); + } + // fall-thru + } + case Source::kWhatBufferingEnd: { notifyListener(MEDIA_INFO, MEDIA_INFO_BUFFERING_END, 0); break; } + case Source::kWhatCacheStats: + { + int32_t kbps; + CHECK(msg->findInt32("bandwidth", &kbps)); + + notifyListener(MEDIA_INFO, MEDIA_INFO_NETWORK_BANDWIDTH, kbps); + break; + } + case Source::kWhatSubtitleData: { sp buffer; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h index 1569816..edc2bd3 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -177,6 +177,14 @@ private: bool mStarted; + // Actual pause state, either as requested by client or due to buffering. + bool mPaused; + + // Pause state as requested by client. Note that if mPausedByClient is + // true, mPaused is always true; if mPausedByClient is false, mPaused could + // still become true, when we pause internally due to buffering. + bool mPausedByClient; + inline const sp &getDecoder(bool audio) { return audio ? mAudioDecoder : mVideoDecoder; } @@ -204,6 +212,7 @@ private: void onStart(); void onResume(); + void onPause(); bool audioDecoderStillNeeded(); diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h index 5bf9187..8f18464 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h @@ -49,6 +49,9 @@ struct NuPlayer::Source : public AHandler { kWhatBufferingUpdate, kWhatBufferingStart, kWhatBufferingEnd, + kWhatPauseOnBufferingStart, + kWhatResumeOnBufferingEnd, + kWhatCacheStats, kWhatSubtitleData, kWhatTimedTextData, kWhatQueueDecoderShutdown, diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp index bd0a41d..7d7d631 100644 --- a/media/libstagefright/NuCachedSource2.cpp +++ b/media/libstagefright/NuCachedSource2.cpp @@ -354,7 +354,7 @@ void NuCachedSource2::fetchInternal() { Mutex::Autolock autoLock(mLock); if (n == 0 || mDisconnecting) { - ALOGI("ERROR_END_OF_STREAM"); + ALOGI("caching reached eos."); mNumRetriesLeft = 0; mFinalStatus = ERROR_END_OF_STREAM; -- cgit v1.1