MediaPlayer: overhaul buffering monitor scheme.
GenericSource: buffering monitor runs on a separate looper since readBuffer()
call can be blocked for long time.
When paused, dequeueAccess() returns -EWOULDBLOCK.
For audio offload, take into account cached data in downstream components.
NuPlayerDecoderPassThrough: flush out aggregate buffer when source doesn't have
data available.
Bug: 24295007
Change-Id: I535a438d96ee902c9b4baa7c84ed7e5063a23964
(cherry picked from commit 32ce83cf93e4eb14c9937e4b850e044f9f7fdf2c)
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index 9a0db30..f4dbdeb 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -42,6 +42,7 @@
static int64_t kLowWaterMarkUs = 2000000ll; // 2secs
static int64_t kHighWaterMarkUs = 5000000ll; // 5secs
+static int64_t kHighWaterMarkRebufferUs = 15000000ll; // 15secs
static const ssize_t kLowWaterMarkBytes = 40000;
static const ssize_t kHighWaterMarkBytes = 200000;
@@ -66,11 +67,8 @@
mFd(-1),
mDrmManagerClient(NULL),
mBitrate(-1ll),
- mPollBufferingGeneration(0),
- mPendingReadBufferTypes(0),
- mBuffering(false),
- mPrepareBuffering(false),
- mPrevBufferPercentage(-1) {
+ mPendingReadBufferTypes(0) {
+ mBufferingMonitor = new BufferingMonitor(notify);
resetDataSource();
DataSource::RegisterDefaultSniffers();
}
@@ -91,6 +89,13 @@
mDrmManagerClient = NULL;
mStarted = false;
mStopRead = true;
+
+ if (mBufferingMonitorLooper != NULL) {
+ mBufferingMonitorLooper->unregisterHandler(mBufferingMonitor->id());
+ mBufferingMonitorLooper->stop();
+ mBufferingMonitorLooper = NULL;
+ }
+ mBufferingMonitor->stop();
}
status_t NuPlayer::GenericSource::setDataSource(
@@ -338,6 +343,10 @@
return mIsStreaming;
}
+void NuPlayer::GenericSource::setOffloadAudio(bool offload) {
+ mBufferingMonitor->setOffloadAudio(offload);
+}
+
NuPlayer::GenericSource::~GenericSource() {
if (mLooper != NULL) {
mLooper->unregisterHandler(id());
@@ -466,10 +475,18 @@
}
if (mIsStreaming) {
- mPrepareBuffering = true;
+ if (mBufferingMonitorLooper == NULL) {
+ mBufferingMonitor->prepare(mCachedSource, mWVMExtractor, mDurationUs, mBitrate,
+ mIsStreaming);
- ensureCacheIsFetching();
- restartPollBuffering();
+ mBufferingMonitorLooper = new ALooper;
+ mBufferingMonitorLooper->setName("GSBMonitor");
+ mBufferingMonitorLooper->start();
+ mBufferingMonitorLooper->registerHandler(mBufferingMonitor);
+ }
+
+ mBufferingMonitor->ensureCacheIsFetching();
+ mBufferingMonitor->restartPollBuffering();
} else {
notifyPrepared();
}
@@ -492,7 +509,7 @@
}
mBitrate = -1;
- cancelPollBuffering();
+ mBufferingMonitor->cancelPollBuffering();
}
notifyPrepared(err);
}
@@ -571,182 +588,6 @@
return OK;
}
-void NuPlayer::GenericSource::schedulePollBuffering() {
- sp<AMessage> msg = new AMessage(kWhatPollBuffering, this);
- msg->setInt32("generation", mPollBufferingGeneration);
- msg->post(1000000ll);
-}
-
-void NuPlayer::GenericSource::cancelPollBuffering() {
- mBuffering = false;
- ++mPollBufferingGeneration;
- mPrevBufferPercentage = -1;
-}
-
-void NuPlayer::GenericSource::restartPollBuffering() {
- if (mIsStreaming) {
- cancelPollBuffering();
- onPollBuffering();
- }
-}
-
-void NuPlayer::GenericSource::notifyBufferingUpdate(int32_t percentage) {
- // Buffering percent could go backward as it's estimated from remaining
- // data and last access time. This could cause the buffering position
- // drawn on media control to jitter slightly. Remember previously reported
- // percentage and don't allow it to go backward.
- if (percentage < mPrevBufferPercentage) {
- percentage = mPrevBufferPercentage;
- } else if (percentage > 100) {
- percentage = 100;
- }
-
- mPrevBufferPercentage = percentage;
-
- ALOGV("notifyBufferingUpdate: buffering %d%%", percentage);
-
- sp<AMessage> 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<AMessage> 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<AMessage> notify = dupNotify();
- notify->setInt32("what", kWhatResumeOnBufferingEnd);
- notify->post();
- }
-}
-
-void NuPlayer::GenericSource::sendCacheStats() {
- int32_t kbps = 0;
- status_t err = UNKNOWN_ERROR;
-
- if (mWVMExtractor != NULL) {
- err = mWVMExtractor->getEstimatedBandwidthKbps(&kbps);
- } else if (mCachedSource != NULL) {
- err = mCachedSource->getEstimatedBandwidthKbps(&kbps);
- }
-
- if (err == OK) {
- sp<AMessage> 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 = -1ll;
- ssize_t cachedDataRemaining = -1;
-
- ALOGW_IF(mWVMExtractor != NULL && mCachedSource != NULL,
- "WVMExtractor and NuCachedSource both present");
-
- if (mWVMExtractor != NULL) {
- cachedDurationUs =
- mWVMExtractor->getCachedDurationUs(&finalStatus);
- } else if (mCachedSource != NULL) {
- cachedDataRemaining =
- mCachedSource->approxDataRemaining(&finalStatus);
-
- if (finalStatus == OK) {
- off64_t size;
- int64_t bitrate = 0ll;
- if (mDurationUs > 0 && mCachedSource->getSize(&size) == OK) {
- bitrate = size * 8000000ll / mDurationUs;
- } else if (mBitrate > 0) {
- bitrate = mBitrate;
- }
- if (bitrate > 0) {
- cachedDurationUs = cachedDataRemaining * 8000000ll / bitrate;
- }
- }
- }
-
- if (finalStatus != OK) {
- ALOGV("onPollBuffering: EOS (finalStatus = %d)", finalStatus);
-
- if (finalStatus == ERROR_END_OF_STREAM) {
- notifyBufferingUpdate(100);
- }
-
- stopBufferingIfNecessary();
- return;
- } 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);
- }
-
- ALOGV("onPollBuffering: cachedDurationUs %.1f sec",
- cachedDurationUs / 1000000.0f);
-
- if (cachedDurationUs < kLowWaterMarkUs) {
- startBufferingIfNecessary();
- } else if (cachedDurationUs > kHighWaterMarkUs) {
- stopBufferingIfNecessary();
- }
- } else if (cachedDataRemaining >= 0) {
- ALOGV("onPollBuffering: cachedDataRemaining %zd bytes",
- cachedDataRemaining);
-
- if (cachedDataRemaining < kLowWaterMarkBytes) {
- startBufferingIfNecessary();
- } else if (cachedDataRemaining > kHighWaterMarkBytes) {
- stopBufferingIfNecessary();
- }
- }
-
- schedulePollBuffering();
-}
-
void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatPrepareAsync:
@@ -834,17 +675,7 @@
case kWhatStart:
case kWhatResume:
{
- restartPollBuffering();
- break;
- }
-
- case kWhatPollBuffering:
- {
- int32_t generation;
- CHECK(msg->findInt32("generation", &generation));
- if (generation == mPollBufferingGeneration) {
- onPollBuffering();
- }
+ mBufferingMonitor->restartPollBuffering();
break;
}
@@ -1045,6 +876,10 @@
status_t NuPlayer::GenericSource::dequeueAccessUnit(
bool audio, sp<ABuffer> *accessUnit) {
+ if (!mStarted) {
+ return -EWOULDBLOCK;
+ }
+
Track *track = audio ? &mAudioTrack : &mVideoTrack;
if (track->mSource == NULL) {
@@ -1091,6 +926,7 @@
CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs));
if (audio) {
mAudioLastDequeueTimeUs = timeUs;
+ mBufferingMonitor->updateDequeuedBufferTime(timeUs);
} else {
mVideoLastDequeueTimeUs = timeUs;
}
@@ -1377,6 +1213,8 @@
}
status_t NuPlayer::GenericSource::doSeek(int64_t seekTimeUs) {
+ mBufferingMonitor->updateDequeuedBufferTime(-1ll);
+
// If the Widevine source is stopped, do not attempt to read any
// more buffers.
if (mStopRead) {
@@ -1403,8 +1241,8 @@
// 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();
+ mBufferingMonitor->stopBufferingIfNecessary();
+ mBufferingMonitor->restartPollBuffering();
return OK;
}
@@ -1585,8 +1423,10 @@
CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs));
if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
mAudioTimeUs = timeUs;
+ mBufferingMonitor->updateQueuedTime(true /* isAudio */, timeUs);
} else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
mVideoTimeUs = timeUs;
+ mBufferingMonitor->updateQueuedTime(false /* isAudio */, timeUs);
}
queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
@@ -1630,4 +1470,330 @@
}
}
+NuPlayer::GenericSource::BufferingMonitor::BufferingMonitor(const sp<AMessage> ¬ify)
+ : mNotify(notify),
+ mDurationUs(-1ll),
+ mBitrate(-1ll),
+ mIsStreaming(false),
+ mAudioTimeUs(0),
+ mVideoTimeUs(0),
+ mPollBufferingGeneration(0),
+ mPrepareBuffering(false),
+ mBuffering(false),
+ mPrevBufferPercentage(-1),
+ mOffloadAudio(false),
+ mFirstDequeuedBufferRealUs(-1ll),
+ mFirstDequeuedBufferMediaUs(-1ll),
+ mlastDequeuedBufferMediaUs(-1ll) {
+}
+
+NuPlayer::GenericSource::BufferingMonitor::~BufferingMonitor() {
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::prepare(
+ const sp<NuCachedSource2> &cachedSource,
+ const sp<WVMExtractor> &wvmExtractor,
+ int64_t durationUs,
+ int64_t bitrate,
+ bool isStreaming) {
+ Mutex::Autolock _l(mLock);
+ prepare_l(cachedSource, wvmExtractor, durationUs, bitrate, isStreaming);
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::stop() {
+ Mutex::Autolock _l(mLock);
+ prepare_l(NULL /* cachedSource */, NULL /* wvmExtractor */, -1 /* durationUs */,
+ -1 /* bitrate */, false /* isStreaming */);
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::cancelPollBuffering() {
+ Mutex::Autolock _l(mLock);
+ cancelPollBuffering_l();
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::restartPollBuffering() {
+ Mutex::Autolock _l(mLock);
+ if (mIsStreaming) {
+ cancelPollBuffering_l();
+ onPollBuffering_l();
+ }
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::stopBufferingIfNecessary() {
+ Mutex::Autolock _l(mLock);
+ stopBufferingIfNecessary_l();
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::ensureCacheIsFetching() {
+ Mutex::Autolock _l(mLock);
+ ensureCacheIsFetching_l();
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::updateQueuedTime(bool isAudio, int64_t timeUs) {
+ Mutex::Autolock _l(mLock);
+ if (isAudio) {
+ mAudioTimeUs = timeUs;
+ } else {
+ mVideoTimeUs = timeUs;
+ }
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::setOffloadAudio(bool offload) {
+ Mutex::Autolock _l(mLock);
+ mOffloadAudio = offload;
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::updateDequeuedBufferTime(int64_t mediaUs) {
+ Mutex::Autolock _l(mLock);
+ if (mediaUs < 0) {
+ mFirstDequeuedBufferRealUs = -1ll;
+ mFirstDequeuedBufferMediaUs = -1ll;
+ } else if (mFirstDequeuedBufferRealUs < 0) {
+ mFirstDequeuedBufferRealUs = ALooper::GetNowUs();
+ mFirstDequeuedBufferMediaUs = mediaUs;
+ }
+ mlastDequeuedBufferMediaUs = mediaUs;
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::prepare_l(
+ const sp<NuCachedSource2> &cachedSource,
+ const sp<WVMExtractor> &wvmExtractor,
+ int64_t durationUs,
+ int64_t bitrate,
+ bool isStreaming) {
+ ALOGW_IF(wvmExtractor != NULL && cachedSource != NULL,
+ "WVMExtractor and NuCachedSource are both present when "
+ "BufferingMonitor::prepare_l is called, ignore NuCachedSource");
+
+ mCachedSource = cachedSource;
+ mWVMExtractor = wvmExtractor;
+ mDurationUs = durationUs;
+ mBitrate = bitrate;
+ mIsStreaming = isStreaming;
+ mAudioTimeUs = 0;
+ mVideoTimeUs = 0;
+ mPrepareBuffering = true;
+ cancelPollBuffering_l();
+ mOffloadAudio = false;
+ mFirstDequeuedBufferRealUs = -1ll;
+ mFirstDequeuedBufferMediaUs = -1ll;
+ mlastDequeuedBufferMediaUs = -1ll;
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::cancelPollBuffering_l() {
+ mBuffering = false;
+ ++mPollBufferingGeneration;
+ mPrevBufferPercentage = -1;
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::notifyBufferingUpdate_l(int32_t percentage) {
+ // Buffering percent could go backward as it's estimated from remaining
+ // data and last access time. This could cause the buffering position
+ // drawn on media control to jitter slightly. Remember previously reported
+ // percentage and don't allow it to go backward.
+ if (percentage < mPrevBufferPercentage) {
+ percentage = mPrevBufferPercentage;
+ } else if (percentage > 100) {
+ percentage = 100;
+ }
+
+ mPrevBufferPercentage = percentage;
+
+ ALOGV("notifyBufferingUpdate_l: buffering %d%%", percentage);
+
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatBufferingUpdate);
+ msg->setInt32("percentage", percentage);
+ msg->post();
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::startBufferingIfNecessary_l() {
+ ALOGD("startBufferingIfNecessary_l: mPrepareBuffering=%d, mBuffering=%d",
+ mPrepareBuffering, mBuffering);
+
+ if (mPrepareBuffering) {
+ return;
+ }
+
+ if (!mBuffering) {
+ mBuffering = true;
+
+ ensureCacheIsFetching_l();
+ sendCacheStats_l();
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatPauseOnBufferingStart);
+ notify->post();
+ }
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::stopBufferingIfNecessary_l() {
+ ALOGD("stopBufferingIfNecessary_l: mPrepareBuffering=%d, mBuffering=%d",
+ mPrepareBuffering, mBuffering);
+
+ if (mPrepareBuffering) {
+ mPrepareBuffering = false;
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatPrepared);
+ notify->setInt32("err", OK);
+ notify->post();
+
+ return;
+ }
+
+ if (mBuffering) {
+ mBuffering = false;
+
+ sendCacheStats_l();
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatResumeOnBufferingEnd);
+ notify->post();
+ }
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::sendCacheStats_l() {
+ int32_t kbps = 0;
+ status_t err = UNKNOWN_ERROR;
+
+ if (mWVMExtractor != NULL) {
+ err = mWVMExtractor->getEstimatedBandwidthKbps(&kbps);
+ } else if (mCachedSource != NULL) {
+ err = mCachedSource->getEstimatedBandwidthKbps(&kbps);
+ }
+
+ if (err == OK) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatCacheStats);
+ notify->setInt32("bandwidth", kbps);
+ notify->post();
+ }
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::ensureCacheIsFetching_l() {
+ if (mCachedSource != NULL) {
+ mCachedSource->resumeFetchingIfNecessary();
+ }
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::schedulePollBuffering_l() {
+ sp<AMessage> msg = new AMessage(kWhatPollBuffering, this);
+ msg->setInt32("generation", mPollBufferingGeneration);
+ // Enquires buffering status every second.
+ msg->post(1000000ll);
+}
+
+int64_t NuPlayer::GenericSource::BufferingMonitor::getLastReadPosition_l() {
+ if (mAudioTimeUs > 0) {
+ return mAudioTimeUs;
+ } else if (mVideoTimeUs > 0) {
+ return mVideoTimeUs;
+ } else {
+ return 0;
+ }
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::onPollBuffering_l() {
+ status_t finalStatus = UNKNOWN_ERROR;
+ int64_t cachedDurationUs = -1ll;
+ ssize_t cachedDataRemaining = -1;
+
+ if (mWVMExtractor != NULL) {
+ cachedDurationUs =
+ mWVMExtractor->getCachedDurationUs(&finalStatus);
+ } else if (mCachedSource != NULL) {
+ cachedDataRemaining =
+ mCachedSource->approxDataRemaining(&finalStatus);
+
+ if (finalStatus == OK) {
+ off64_t size;
+ int64_t bitrate = 0ll;
+ if (mDurationUs > 0 && mCachedSource->getSize(&size) == OK) {
+ // |bitrate| uses bits/second unit, while size is number of bytes.
+ bitrate = size * 8000000ll / mDurationUs;
+ } else if (mBitrate > 0) {
+ bitrate = mBitrate;
+ }
+ if (bitrate > 0) {
+ cachedDurationUs = cachedDataRemaining * 8000000ll / bitrate;
+ }
+ }
+ }
+
+ if (finalStatus != OK) {
+ ALOGV("onPollBuffering_l: EOS (finalStatus = %d)", finalStatus);
+
+ if (finalStatus == ERROR_END_OF_STREAM) {
+ notifyBufferingUpdate_l(100);
+ }
+
+ stopBufferingIfNecessary_l();
+ return;
+ } else if (cachedDurationUs >= 0ll) {
+ if (mDurationUs > 0ll) {
+ int64_t cachedPosUs = getLastReadPosition_l() + cachedDurationUs;
+ int percentage = 100.0 * cachedPosUs / mDurationUs;
+ if (percentage > 100) {
+ percentage = 100;
+ }
+
+ notifyBufferingUpdate_l(percentage);
+ }
+
+ ALOGV("onPollBuffering_l: cachedDurationUs %.1f sec",
+ cachedDurationUs / 1000000.0f);
+
+ if (cachedDurationUs < kLowWaterMarkUs) {
+ // Take into account the data cached in downstream components to try to avoid
+ // unnecessary pause.
+ if (mOffloadAudio && mFirstDequeuedBufferRealUs >= 0) {
+ int64_t downStreamCacheUs = mlastDequeuedBufferMediaUs - mFirstDequeuedBufferMediaUs
+ - (ALooper::GetNowUs() - mFirstDequeuedBufferRealUs);
+ if (downStreamCacheUs > 0) {
+ cachedDurationUs += downStreamCacheUs;
+ }
+ }
+
+ if (cachedDurationUs < kLowWaterMarkUs) {
+ startBufferingIfNecessary_l();
+ }
+ } else {
+ int64_t highWaterMark = mPrepareBuffering ? kHighWaterMarkUs : kHighWaterMarkRebufferUs;
+ if (cachedDurationUs > highWaterMark) {
+ stopBufferingIfNecessary_l();
+ }
+ }
+ } else if (cachedDataRemaining >= 0) {
+ ALOGV("onPollBuffering_l: cachedDataRemaining %zd bytes",
+ cachedDataRemaining);
+
+ if (cachedDataRemaining < kLowWaterMarkBytes) {
+ startBufferingIfNecessary_l();
+ } else if (cachedDataRemaining > kHighWaterMarkBytes) {
+ stopBufferingIfNecessary_l();
+ }
+ }
+
+ schedulePollBuffering_l();
+}
+
+void NuPlayer::GenericSource::BufferingMonitor::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatPollBuffering:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+ Mutex::Autolock _l(mLock);
+ if (generation == mPollBufferingGeneration) {
+ onPollBuffering_l();
+ }
+ break;
+ }
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
} // namespace android