summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/AwesomePlayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/AwesomePlayer.cpp')
-rw-r--r--media/libstagefright/AwesomePlayer.cpp307
1 files changed, 262 insertions, 45 deletions
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index d53f442..c912f75 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -47,6 +47,7 @@
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/Surface.h>
@@ -65,6 +66,11 @@ static int64_t kHighWaterMarkUs = 5000000ll; // 5secs
static const size_t kLowWaterMarkBytes = 40000;
static const size_t kHighWaterMarkBytes = 200000;
+// maximum time in paused state when offloading audio decompression. When elapsed, the AudioPlayer
+// is destroyed to allow the audio DSP to power down.
+static int64_t kOffloadPauseMaxUs = 60000000ll;
+
+
struct AwesomeEvent : public TimedEventQueue::Event {
AwesomeEvent(
AwesomePlayer *player,
@@ -185,6 +191,8 @@ AwesomePlayer::AwesomePlayer()
mTimeSource(NULL),
mVideoRenderingStarted(false),
mVideoRendererIsPreview(false),
+ mMediaRenderingStartGeneration(0),
+ mStartGeneration(0),
mAudioPlayer(NULL),
mDisplayWidth(0),
mDisplayHeight(0),
@@ -194,7 +202,9 @@ AwesomePlayer::AwesomePlayer()
mVideoBuffer(NULL),
mDecryptHandle(NULL),
mLastVideoTimeUs(-1),
- mTextDriver(NULL) {
+ mTextDriver(NULL),
+ mOffloadAudio(false),
+ mAudioTearDown(false) {
CHECK_EQ(mClient.connect(), (status_t)OK);
DataSource::RegisterDefaultSniffers();
@@ -206,13 +216,17 @@ AwesomePlayer::AwesomePlayer()
mBufferingEvent = new AwesomeEvent(this, &AwesomePlayer::onBufferingUpdate);
mBufferingEventPending = false;
mVideoLagEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoLagUpdate);
- mVideoEventPending = false;
+ mVideoLagEventPending = false;
mCheckAudioStatusEvent = new AwesomeEvent(
this, &AwesomePlayer::onCheckAudioStatus);
mAudioStatusEventPending = false;
+ mAudioTearDownEvent = new AwesomeEvent(this,
+ &AwesomePlayer::onAudioTearDownEvent);
+ mAudioTearDownEventPending = false;
+
reset();
}
@@ -232,6 +246,11 @@ void AwesomePlayer::cancelPlayerEvents(bool keepNotifications) {
mQueue.cancelEvent(mVideoLagEvent->eventID());
mVideoLagEventPending = false;
+ if (mOffloadAudio) {
+ mQueue.cancelEvent(mAudioTearDownEvent->eventID());
+ mAudioTearDownEventPending = false;
+ }
+
if (!keepNotifications) {
mQueue.cancelEvent(mStreamDoneEvent->eventID());
mStreamDoneEventPending = false;
@@ -240,6 +259,7 @@ void AwesomePlayer::cancelPlayerEvents(bool keepNotifications) {
mQueue.cancelEvent(mBufferingEvent->eventID());
mBufferingEventPending = false;
+ mAudioTearDown = false;
}
}
@@ -474,6 +494,8 @@ void AwesomePlayer::reset_l() {
mDisplayWidth = 0;
mDisplayHeight = 0;
+ notifyListener_l(MEDIA_STOPPED);
+
if (mDecryptHandle != NULL) {
mDrmManagerClient->setPlaybackStatus(mDecryptHandle,
Playback::STOP, 0);
@@ -518,7 +540,7 @@ void AwesomePlayer::reset_l() {
mVideoTrack.clear();
mExtractor.clear();
- // Shutdown audio first, so that the respone to the reset request
+ // Shutdown audio first, so that the response to the reset request
// appears to happen instantaneously as far as the user is concerned
// If we did this later, audio would continue playing while we
// shutdown the video-related resources and the player appear to
@@ -531,6 +553,7 @@ void AwesomePlayer::reset_l() {
mAudioSource->stop();
}
mAudioSource.clear();
+ mOmxSource.clear();
mTimeSource = NULL;
@@ -586,7 +609,7 @@ void AwesomePlayer::reset_l() {
}
void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) {
- if (mListener != NULL) {
+ if ((mListener != NULL) && !mAudioTearDown) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
@@ -597,7 +620,7 @@ void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) {
bool AwesomePlayer::getBitrate(int64_t *bitrate) {
off64_t size;
- if (mDurationUs >= 0 && mCachedSource != NULL
+ if (mDurationUs > 0 && mCachedSource != NULL
&& mCachedSource->getSize(&size) == OK) {
*bitrate = size * 8000000ll / mDurationUs; // in bits/sec
return true;
@@ -775,7 +798,9 @@ void AwesomePlayer::onBufferingUpdate() {
}
}
- postBufferingEvent_l();
+ if (mFlags & (PLAYING | PREPARING)) {
+ postBufferingEvent_l();
+ }
}
void AwesomePlayer::sendCacheStats() {
@@ -842,6 +867,13 @@ void AwesomePlayer::onStreamDone() {
pause_l(true /* at eos */);
+ // If audio hasn't completed MEDIA_SEEK_COMPLETE yet,
+ // notify MEDIA_SEEK_COMPLETE to observer immediately for state persistence.
+ if (mWatchForAudioSeekComplete) {
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ mWatchForAudioSeekComplete = false;
+ }
+
modifyFlags(AT_EOS, SET);
}
}
@@ -883,41 +915,49 @@ status_t AwesomePlayer::play_l() {
if (mAudioSource != NULL) {
if (mAudioPlayer == NULL) {
- if (mAudioSink != NULL) {
- bool allowDeepBuffering;
- int64_t cachedDurationUs;
- bool eos;
- if (mVideoSource == NULL
- && (mDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US ||
- (getCachedDuration_l(&cachedDurationUs, &eos) &&
- cachedDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US))) {
- allowDeepBuffering = true;
- } else {
- allowDeepBuffering = false;
- }
-
- mAudioPlayer = new AudioPlayer(mAudioSink, allowDeepBuffering, this);
- mAudioPlayer->setSource(mAudioSource);
-
- mTimeSource = mAudioPlayer;
-
- // If there was a seek request before we ever started,
- // honor the request now.
- // Make sure to do this before starting the audio player
- // to avoid a race condition.
- seekAudioIfNecessary_l();
- }
+ createAudioPlayer_l();
}
CHECK(!(mFlags & AUDIO_RUNNING));
if (mVideoSource == NULL) {
+
// We don't want to post an error notification at this point,
// the error returned from MediaPlayer::start() will suffice.
status_t err = startAudioPlayer_l(
false /* sendErrorNotification */);
+ if ((err != OK) && mOffloadAudio) {
+ ALOGI("play_l() cannot create offload output, fallback to sw decode");
+ int64_t curTimeUs;
+ getPosition(&curTimeUs);
+
+ delete mAudioPlayer;
+ mAudioPlayer = NULL;
+ // if the player was started it will take care of stopping the source when destroyed
+ if (!(mFlags & AUDIOPLAYER_STARTED)) {
+ mAudioSource->stop();
+ }
+ modifyFlags((AUDIO_RUNNING | AUDIOPLAYER_STARTED), CLEAR);
+ mOffloadAudio = false;
+ mAudioSource = mOmxSource;
+ if (mAudioSource != NULL) {
+ err = mAudioSource->start();
+
+ if (err != OK) {
+ mAudioSource.clear();
+ } else {
+ mSeekNotificationSent = true;
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ seekTo_l(curTimeUs);
+ }
+ createAudioPlayer_l();
+ err = startAudioPlayer_l(false);
+ }
+ }
+ }
+
if (err != OK) {
delete mAudioPlayer;
mAudioPlayer = NULL;
@@ -963,22 +1003,72 @@ status_t AwesomePlayer::play_l() {
}
addBatteryData(params);
+ if (isStreamingHTTP()) {
+ postBufferingEvent_l();
+ }
+
return OK;
}
+void AwesomePlayer::createAudioPlayer_l()
+{
+ uint32_t flags = 0;
+ int64_t cachedDurationUs;
+ bool eos;
+
+ if (mOffloadAudio) {
+ flags |= AudioPlayer::USE_OFFLOAD;
+ } else if (mVideoSource == NULL
+ && (mDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US ||
+ (getCachedDuration_l(&cachedDurationUs, &eos) &&
+ cachedDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US))) {
+ flags |= AudioPlayer::ALLOW_DEEP_BUFFERING;
+ }
+ if (isStreamingHTTP()) {
+ flags |= AudioPlayer::IS_STREAMING;
+ }
+ if (mVideoSource != NULL) {
+ flags |= AudioPlayer::HAS_VIDEO;
+ }
+
+ mAudioPlayer = new AudioPlayer(mAudioSink, flags, this);
+ mAudioPlayer->setSource(mAudioSource);
+
+ mTimeSource = mAudioPlayer;
+
+ // If there was a seek request before we ever started,
+ // honor the request now.
+ // Make sure to do this before starting the audio player
+ // to avoid a race condition.
+ seekAudioIfNecessary_l();
+}
+
+void AwesomePlayer::notifyIfMediaStarted_l() {
+ if (mMediaRenderingStartGeneration == mStartGeneration) {
+ mMediaRenderingStartGeneration = -1;
+ notifyListener_l(MEDIA_STARTED);
+ }
+}
+
status_t AwesomePlayer::startAudioPlayer_l(bool sendErrorNotification) {
CHECK(!(mFlags & AUDIO_RUNNING));
+ status_t err = OK;
if (mAudioSource == NULL || mAudioPlayer == NULL) {
return OK;
}
+ if (mOffloadAudio) {
+ mQueue.cancelEvent(mAudioTearDownEvent->eventID());
+ mAudioTearDownEventPending = false;
+ }
+
if (!(mFlags & AUDIOPLAYER_STARTED)) {
bool wasSeeking = mAudioPlayer->isSeeking();
// We've already started the MediaSource in order to enable
// the prefetcher to read its data.
- status_t err = mAudioPlayer->start(
+ err = mAudioPlayer->start(
true /* sourceAlreadyStarted */);
if (err != OK) {
@@ -996,16 +1086,20 @@ status_t AwesomePlayer::startAudioPlayer_l(bool sendErrorNotification) {
// We will have finished the seek while starting the audio player.
postAudioSeekComplete();
+ } else {
+ notifyIfMediaStarted_l();
}
} else {
- mAudioPlayer->resume();
+ err = mAudioPlayer->resume();
}
- modifyFlags(AUDIO_RUNNING, SET);
+ if (err == OK) {
+ modifyFlags(AUDIO_RUNNING, SET);
- mWatchForAudioEOS = true;
+ mWatchForAudioEOS = true;
+ }
- return OK;
+ return err;
}
void AwesomePlayer::notifyVideoSize_l() {
@@ -1131,21 +1225,29 @@ status_t AwesomePlayer::pause() {
status_t AwesomePlayer::pause_l(bool at_eos) {
if (!(mFlags & PLAYING)) {
+ if (mAudioTearDown && mAudioTearDownWasPlaying) {
+ ALOGV("pause_l() during teardown and finishSetDataSource_l() mFlags %x" , mFlags);
+ mAudioTearDownWasPlaying = false;
+ notifyListener_l(MEDIA_PAUSED);
+ mMediaRenderingStartGeneration = ++mStartGeneration;
+ }
return OK;
}
+ notifyListener_l(MEDIA_PAUSED);
+ mMediaRenderingStartGeneration = ++mStartGeneration;
+
cancelPlayerEvents(true /* keepNotifications */);
if (mAudioPlayer != NULL && (mFlags & AUDIO_RUNNING)) {
- if (at_eos) {
- // If we played the audio stream to completion we
- // want to make sure that all samples remaining in the audio
- // track's queue are played out.
- mAudioPlayer->pause(true /* playPendingSamples */);
- } else {
- mAudioPlayer->pause();
+ // If we played the audio stream to completion we
+ // want to make sure that all samples remaining in the audio
+ // track's queue are played out.
+ mAudioPlayer->pause(at_eos /* playPendingSamples */);
+ // send us a reminder to tear down the AudioPlayer if paused for too long.
+ if (mOffloadAudio) {
+ postAudioTearDownEvent(kOffloadPauseMaxUs);
}
-
modifyFlags(AUDIO_RUNNING, CLEAR);
}
@@ -1290,7 +1392,6 @@ status_t AwesomePlayer::getPosition(int64_t *positionUs) {
} else {
*positionUs = 0;
}
-
return OK;
}
@@ -1324,6 +1425,11 @@ status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
mSeekTimeUs = timeUs;
modifyFlags((AT_EOS | AUDIO_AT_EOS | VIDEO_AT_EOS), CLEAR);
+ if (mFlags & PLAYING) {
+ notifyListener_l(MEDIA_PAUSED);
+ mMediaRenderingStartGeneration = ++mStartGeneration;
+ }
+
seekAudioIfNecessary_l();
if (mFlags & TEXTPLAYER_INITIALIZED) {
@@ -1385,14 +1491,29 @@ status_t AwesomePlayer::initAudioDecoder() {
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
+ // Check whether there is a hardware codec for this stream
+ // This doesn't guarantee that the hardware has a free stream
+ // but it avoids us attempting to open (and re-open) an offload
+ // stream to hardware that doesn't have the necessary codec
+ mOffloadAudio = canOffloadStream(meta, (mVideoSource != NULL), isStreamingHTTP());
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) {
+ ALOGV("createAudioPlayer: bypass OMX (raw)");
mAudioSource = mAudioTrack;
} else {
- mAudioSource = OMXCodec::Create(
+ // If offloading we still create a OMX decoder as a fall-back
+ // but we don't start it
+ mOmxSource = OMXCodec::Create(
mClient.interface(), mAudioTrack->getFormat(),
false, // createEncoder
mAudioTrack);
+
+ if (mOffloadAudio) {
+ ALOGV("createAudioPlayer: bypass OMX (offload)");
+ mAudioSource = mAudioTrack;
+ } else {
+ mAudioSource = mOmxSource;
+ }
}
if (mAudioSource != NULL) {
@@ -1408,6 +1529,7 @@ status_t AwesomePlayer::initAudioDecoder() {
if (err != OK) {
mAudioSource.clear();
+ mOmxSource.clear();
return err;
}
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_QCELP)) {
@@ -1551,6 +1673,16 @@ void AwesomePlayer::finishSeekIfNecessary(int64_t videoTimeUs) {
return;
}
+ // If we paused, then seeked, then resumed, it is possible that we have
+ // signaled SEEK_COMPLETE at a copmletely different media time than where
+ // we are now resuming. Signal new position to media time provider.
+ // Cannot signal another SEEK_COMPLETE, as existing clients may not expect
+ // multiple SEEK_COMPLETE responses to a single seek() request.
+ if (mSeekNotificationSent && abs(mSeekTimeUs - videoTimeUs) > 10000) {
+ // notify if we are resuming more than 10ms away from desired seek time
+ notifyListener_l(MEDIA_SKIPPED);
+ }
+
if (mAudioPlayer != NULL) {
ALOGV("seeking audio to %lld us (%.2f secs).", videoTimeUs, videoTimeUs / 1E6);
@@ -1822,6 +1954,9 @@ void AwesomePlayer::onVideoEvent() {
notifyListener_l(MEDIA_INFO, MEDIA_INFO_RENDERING_START);
}
+ if (mFlags & PLAYING) {
+ notifyIfMediaStarted_l();
+ }
}
mVideoBuffer->release();
@@ -1885,6 +2020,15 @@ void AwesomePlayer::postCheckAudioStatusEvent(int64_t delayUs) {
mQueue.postEventWithDelay(mCheckAudioStatusEvent, delayUs);
}
+void AwesomePlayer::postAudioTearDownEvent(int64_t delayUs) {
+ Mutex::Autolock autoLock(mAudioLock);
+ if (mAudioTearDownEventPending) {
+ return;
+ }
+ mAudioTearDownEventPending = true;
+ mQueue.postEventWithDelay(mAudioTearDownEvent, delayUs);
+}
+
void AwesomePlayer::onCheckAudioStatus() {
{
Mutex::Autolock autoLock(mAudioLock);
@@ -1908,6 +2052,8 @@ void AwesomePlayer::onCheckAudioStatus() {
}
mSeeking = NO_SEEK;
+
+ notifyIfMediaStarted_l();
}
status_t finalStatus;
@@ -2189,6 +2335,7 @@ void AwesomePlayer::abortPrepare(status_t err) {
modifyFlags((PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED), CLEAR);
mAsyncPrepareEvent = NULL;
mPreparedCondition.broadcast();
+ mAudioTearDown = false;
}
// static
@@ -2200,7 +2347,10 @@ bool AwesomePlayer::ContinuePreparation(void *cookie) {
void AwesomePlayer::onPrepareAsyncEvent() {
Mutex::Autolock autoLock(mLock);
+ beginPrepareAsync_l();
+}
+void AwesomePlayer::beginPrepareAsync_l() {
if (mFlags & PREPARE_CANCELLED) {
ALOGI("prepare was cancelled before doing anything");
abortPrepare(UNKNOWN_ERROR);
@@ -2259,6 +2409,20 @@ void AwesomePlayer::finishAsyncPrepare_l() {
modifyFlags(PREPARED, SET);
mAsyncPrepareEvent = NULL;
mPreparedCondition.broadcast();
+
+ if (mAudioTearDown) {
+ if (mPrepareResult == OK) {
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ seekTo_l(mAudioTearDownPosition);
+ }
+
+ if (mAudioTearDownWasPlaying) {
+ modifyFlags(CACHE_UNDERRUN, CLEAR);
+ play_l();
+ }
+ }
+ mAudioTearDown = false;
+ }
}
uint32_t AwesomePlayer::flags() const {
@@ -2273,6 +2437,10 @@ void AwesomePlayer::postAudioSeekComplete() {
postCheckAudioStatusEvent(0);
}
+void AwesomePlayer::postAudioTearDown() {
+ postAudioTearDownEvent(0);
+}
+
status_t AwesomePlayer::setParameter(int key, const Parcel &request) {
switch (key) {
case KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS:
@@ -2404,6 +2572,7 @@ status_t AwesomePlayer::selectAudioTrack_l(
mAudioSource->stop();
}
mAudioSource.clear();
+ mOmxSource.clear();
mTimeSource = NULL;
@@ -2660,4 +2829,52 @@ void AwesomePlayer::modifyFlags(unsigned value, FlagMode mode) {
}
}
+void AwesomePlayer::onAudioTearDownEvent() {
+
+ Mutex::Autolock autoLock(mLock);
+ if (!mAudioTearDownEventPending) {
+ return;
+ }
+ mAudioTearDownEventPending = false;
+
+ ALOGV("onAudioTearDownEvent");
+
+ // stream info is cleared by reset_l() so copy what we need
+ mAudioTearDownWasPlaying = (mFlags & PLAYING);
+ KeyedVector<String8, String8> uriHeaders(mUriHeaders);
+ sp<DataSource> fileSource(mFileSource);
+
+ mStatsLock.lock();
+ String8 uri(mStats.mURI);
+ mStatsLock.unlock();
+
+ // get current position so we can start recreated stream from here
+ getPosition(&mAudioTearDownPosition);
+
+ // Reset and recreate
+ reset_l();
+
+ status_t err;
+
+ if (fileSource != NULL) {
+ mFileSource = fileSource;
+ err = setDataSource_l(fileSource);
+ } else {
+ err = setDataSource_l(uri, &uriHeaders);
+ }
+
+ mFlags |= PREPARING;
+ if ( err != OK ) {
+ // This will force beingPrepareAsync_l() to notify
+ // a MEDIA_ERROR to the client and abort the prepare
+ mFlags |= PREPARE_CANCELLED;
+ }
+
+ mAudioTearDown = true;
+ mIsAsyncPrepare = true;
+
+ // Call prepare for the host decoding
+ beginPrepareAsync_l();
+}
+
} // namespace android