blob: 4abea4c013001d355d072dad37697982e05e80df [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define USE_LOG SLAndroidLogLevel_Verbose
#include "sles_allinclusive.h"
#include "android_GenericMediaPlayer.h"
#include <media/IMediaPlayerService.h>
#include <surfaceflinger/ISurfaceComposer.h>
#include <surfaceflinger/SurfaceComposerClient.h>
#include <media/stagefright/foundation/ADebug.h>
// default delay in Us used when reposting an event when the player is not ready to accept
// the command yet. This is for instance used when seeking on a MediaPlayer that's still preparing
#define DEFAULT_COMMAND_DELAY_FOR_REPOST_US (100*1000) // 100ms
// table of prefixes for known distant protocols; these are immediately dispatched to mediaserver
static const char* const kDistantProtocolPrefix[] = { "http://", "https://", "rtsp://"};
#define NB_DISTANT_PROTOCOLS (sizeof(kDistantProtocolPrefix)/sizeof(kDistantProtocolPrefix[0]))
// is the specified URI a known distant protocol?
bool isDistantProtocol(const char *uri)
{
for (unsigned int i = 0; i < NB_DISTANT_PROTOCOLS; i++) {
if (!strncasecmp(uri, kDistantProtocolPrefix[i], strlen(kDistantProtocolPrefix[i]))) {
return true;
}
}
return false;
}
namespace android {
//--------------------------------------------------------------------------------------------------
MediaPlayerNotificationClient::MediaPlayerNotificationClient(GenericMediaPlayer* gmp) :
mGenericMediaPlayer(gmp),
mPlayerPrepared(PREPARE_NOT_STARTED)
{
SL_LOGV("MediaPlayerNotificationClient::MediaPlayerNotificationClient()");
}
MediaPlayerNotificationClient::~MediaPlayerNotificationClient() {
SL_LOGV("MediaPlayerNotificationClient::~MediaPlayerNotificationClient()");
}
// Map a MEDIA_* enum to a string
static const char *media_to_string(int msg)
{
switch (msg) {
#define _(x) case MEDIA_##x: return "MEDIA_" #x;
_(PREPARED)
_(SET_VIDEO_SIZE)
_(SEEK_COMPLETE)
_(PLAYBACK_COMPLETE)
_(BUFFERING_UPDATE)
_(ERROR)
_(NOP)
_(TIMED_TEXT)
_(INFO)
#undef _
default:
return NULL;
}
}
//--------------------------------------------------
// IMediaPlayerClient implementation
void MediaPlayerNotificationClient::notify(int msg, int ext1, int ext2, const Parcel *obj) {
SL_LOGV("MediaPlayerNotificationClient::notify(msg=%s (%d), ext1=%d, ext2=%d)",
media_to_string(msg), msg, ext1, ext2);
sp<GenericMediaPlayer> genericMediaPlayer(mGenericMediaPlayer.promote());
if (genericMediaPlayer == NULL) {
SL_LOGW("MediaPlayerNotificationClient::notify after GenericMediaPlayer destroyed");
return;
}
switch (msg) {
case MEDIA_PREPARED:
{
Mutex::Autolock _l(mLock);
if (PREPARE_IN_PROGRESS == mPlayerPrepared) {
mPlayerPrepared = PREPARE_COMPLETED_SUCCESSFULLY;
mPlayerPreparedCondition.signal();
} else {
SL_LOGE("Unexpected MEDIA_PREPARED");
}
}
break;
case MEDIA_SET_VIDEO_SIZE:
// only send video size updates if the player was flagged as having video, to avoid
// sending video size updates of (0,0)
// We're running on a different thread than genericMediaPlayer's ALooper thread,
// so it would normally be racy to access fields within genericMediaPlayer.
// But in this case mHasVideo is const, so it is safe to access.
// Or alternatively, we could notify unconditionally and let it decide whether to handle.
if (genericMediaPlayer->mHasVideo) {
genericMediaPlayer->notify(PLAYEREVENT_VIDEO_SIZE_UPDATE,
(int32_t)ext1, (int32_t)ext2, true /*async*/);
}
break;
case MEDIA_SEEK_COMPLETE:
genericMediaPlayer->seekComplete();
break;
case MEDIA_PLAYBACK_COMPLETE:
genericMediaPlayer->notify(PLAYEREVENT_ENDOFSTREAM, 1, true /*async*/);
break;
case MEDIA_BUFFERING_UPDATE:
// values received from Android framework for buffer fill level use percent,
// while SL/XA use permille, so does GenericPlayer
genericMediaPlayer->bufferingUpdate(ext1 * 10 /*fillLevelPerMille*/);
break;
case MEDIA_ERROR:
{
Mutex::Autolock _l(mLock);
if (PREPARE_IN_PROGRESS == mPlayerPrepared) {
mPlayerPrepared = PREPARE_COMPLETED_UNSUCCESSFULLY;
mPlayerPreparedCondition.signal();
} else {
// inform client of errors after preparation
genericMediaPlayer->notify(PLAYEREVENT_ERRORAFTERPREPARE, ext1, true /*async*/);
}
}
break;
case MEDIA_NOP:
case MEDIA_TIMED_TEXT:
case MEDIA_INFO:
break;
default: { }
}
}
//--------------------------------------------------
void MediaPlayerNotificationClient::beforePrepare()
{
Mutex::Autolock _l(mLock);
assert(mPlayerPrepared == PREPARE_NOT_STARTED);
mPlayerPrepared = PREPARE_IN_PROGRESS;
}
//--------------------------------------------------
bool MediaPlayerNotificationClient::blockUntilPlayerPrepared() {
Mutex::Autolock _l(mLock);
assert(mPlayerPrepared != PREPARE_NOT_STARTED);
while (mPlayerPrepared == PREPARE_IN_PROGRESS) {
mPlayerPreparedCondition.wait(mLock);
}
assert(mPlayerPrepared == PREPARE_COMPLETED_SUCCESSFULLY ||
mPlayerPrepared == PREPARE_COMPLETED_UNSUCCESSFULLY);
return mPlayerPrepared == PREPARE_COMPLETED_SUCCESSFULLY;
}
//--------------------------------------------------------------------------------------------------
GenericMediaPlayer::GenericMediaPlayer(const AudioPlayback_Parameters* params, bool hasVideo) :
GenericPlayer(params),
mHasVideo(hasVideo),
mSeekTimeMsec(0),
mVideoSurfaceTexture(0),
mPlayer(0),
mPlayerClient(new MediaPlayerNotificationClient(this)),
mPlayerDeathNotifier(new MediaPlayerDeathNotifier(mPlayerClient))
{
SL_LOGD("GenericMediaPlayer::GenericMediaPlayer()");
}
GenericMediaPlayer::~GenericMediaPlayer() {
SL_LOGD("GenericMediaPlayer::~GenericMediaPlayer()");
}
void GenericMediaPlayer::preDestroy() {
// FIXME can't access mPlayer from outside the looper (no mutex!) so using mPreparedPlayer
sp<IMediaPlayer> player;
getPreparedPlayer(player);
if (player != NULL) {
player->stop();
// causes CHECK failure in Nuplayer, but commented out in the subclass preDestroy
// randomly causes a NPE in StagefrightPlayer, heap corruption, or app hang
//player->setDataSource(NULL);
player->setVideoSurfaceTexture(NULL);
player->disconnect();
// release all references to the IMediaPlayer
// FIXME illegal if not on looper
//mPlayer.clear();
{
Mutex::Autolock _l(mPreparedPlayerLock);
mPreparedPlayer.clear();
}
}
GenericPlayer::preDestroy();
}
//--------------------------------------------------
// overridden from GenericPlayer
// pre-condition:
// msec != NULL
// post-condition
// *msec ==
// ANDROID_UNKNOWN_TIME if position is unknown at time of query,
// or the current MediaPlayer position
void GenericMediaPlayer::getPositionMsec(int* msec) {
SL_LOGD("GenericMediaPlayer::getPositionMsec()");
sp<IMediaPlayer> player;
getPreparedPlayer(player);
// To avoid deadlock, directly call the MediaPlayer object
if (player == 0 || player->getCurrentPosition(msec) != NO_ERROR) {
*msec = ANDROID_UNKNOWN_TIME;
}
}
//--------------------------------------------------
void GenericMediaPlayer::setVideoSurfaceTexture(const sp<ISurfaceTexture> &surfaceTexture) {
SL_LOGV("GenericMediaPlayer::setVideoSurfaceTexture()");
// FIXME bug - race condition, should do in looper
if (mVideoSurfaceTexture.get() == surfaceTexture.get()) {
return;
}
if ((mStateFlags & kFlagPrepared) && (mPlayer != 0)) {
mPlayer->setVideoSurfaceTexture(surfaceTexture);
}
mVideoSurfaceTexture = surfaceTexture;
}
//--------------------------------------------------
// Event handlers
// blocks until mPlayer is prepared
void GenericMediaPlayer::onPrepare() {
SL_LOGD("GenericMediaPlayer::onPrepare()");
// Attempt to prepare at most once, and only if there is a MediaPlayer
if (!(mStateFlags & (kFlagPrepared | kFlagPreparedUnsuccessfully)) && (mPlayer != 0)) {
if (mHasVideo) {
if (mVideoSurfaceTexture != 0) {
mPlayer->setVideoSurfaceTexture(mVideoSurfaceTexture);
}
}
mPlayer->setAudioStreamType(mPlaybackParams.streamType);
mPlayerClient->beforePrepare();
mPlayer->prepareAsync();
if (mPlayerClient->blockUntilPlayerPrepared()) {
mStateFlags |= kFlagPrepared;
afterMediaPlayerPreparedSuccessfully();
} else {
mStateFlags |= kFlagPreparedUnsuccessfully;
}
}
GenericPlayer::onPrepare();
SL_LOGD("GenericMediaPlayer::onPrepare() done, mStateFlags=0x%x", mStateFlags);
}
void GenericMediaPlayer::onPlay() {
SL_LOGD("GenericMediaPlayer::onPlay()");
if (((mStateFlags & (kFlagPrepared | kFlagPlaying)) == kFlagPrepared) && (mPlayer != 0)) {
mPlayer->start();
}
GenericPlayer::onPlay();
}
void GenericMediaPlayer::onPause() {
SL_LOGD("GenericMediaPlayer::onPause()");
if (!(~mStateFlags & (kFlagPrepared | kFlagPlaying)) && (mPlayer != 0)) {
mPlayer->pause();
}
GenericPlayer::onPause();
}
void GenericMediaPlayer::onSeekComplete() {
SL_LOGV("GenericMediaPlayer::onSeekComplete()");
// did we initiate the seek?
if (!(mStateFlags & kFlagSeeking)) {
// no, are we looping?
if (mStateFlags & kFlagLooping) {
// yes, per OpenSL ES 1.0.1 and 1.1 do NOT report it to client
// notify(PLAYEREVENT_ENDOFSTREAM, 1, true /*async*/);
// no, well that's surprising, but it's probably just a benign race condition
} else {
SL_LOGW("Unexpected seek complete event ignored");
}
}
GenericPlayer::onSeekComplete();
}
/**
* pre-condition: WHATPARAM_SEEK_SEEKTIME_MS parameter value >= 0
*/
void GenericMediaPlayer::onSeek(const sp<AMessage> &msg) {
SL_LOGV("GenericMediaPlayer::onSeek");
int64_t timeMsec = ANDROID_UNKNOWN_TIME;
if (!msg->findInt64(WHATPARAM_SEEK_SEEKTIME_MS, &timeMsec)) {
// invalid command, drop it
return;
}
if ((mStateFlags & kFlagSeeking) && (timeMsec == mSeekTimeMsec) &&
(timeMsec != ANDROID_UNKNOWN_TIME)) {
// already seeking to the same non-unknown time, cancel this command
return;
} else if (mStateFlags & kFlagPreparedUnsuccessfully) {
// discard seeks after unsuccessful prepare
} else if (!(mStateFlags & kFlagPrepared)) {
// we are not ready to accept a seek command at this time, retry later
msg->post(DEFAULT_COMMAND_DELAY_FOR_REPOST_US);
} else {
if (mPlayer != 0) {
mStateFlags |= kFlagSeeking;
mSeekTimeMsec = (int32_t)timeMsec;
// seek to unknown time is used by StreamPlayer after discontinuity
if (timeMsec == ANDROID_UNKNOWN_TIME) {
// FIXME simulate a MEDIA_SEEK_COMPLETE event in 250 ms;
// this is a terrible hack to make up for mediaserver not sending one
(new AMessage(kWhatSeekComplete, id()))->post(250000);
} else if (OK != mPlayer->seekTo(timeMsec)) {
mStateFlags &= ~kFlagSeeking;
mSeekTimeMsec = ANDROID_UNKNOWN_TIME;
}
}
}
}
void GenericMediaPlayer::onLoop(const sp<AMessage> &msg) {
SL_LOGV("GenericMediaPlayer::onLoop");
int32_t loop = 0;
if (msg->findInt32(WHATPARAM_LOOP_LOOPING, &loop)) {
if (loop) {
mStateFlags |= kFlagLooping;
} else {
mStateFlags &= ~kFlagLooping;
}
// if we have a MediaPlayer then tell it now, otherwise we'll tell it after it's created
if (mPlayer != 0) {
(void) mPlayer->setLooping(loop);
}
}
}
void GenericMediaPlayer::onVolumeUpdate() {
SL_LOGD("GenericMediaPlayer::onVolumeUpdate()");
// use settings lock to read the volume settings
Mutex::Autolock _l(mSettingsLock);
if (mPlayer != 0) {
mPlayer->setVolume(mAndroidAudioLevels.mFinalVolume[0],
mAndroidAudioLevels.mFinalVolume[1]);
}
}
void GenericMediaPlayer::onAttachAuxEffect(const sp<AMessage> &msg) {
SL_LOGD("GenericMediaPlayer::onAttachAuxEffect()");
int32_t effectId = 0;
if (msg->findInt32(WHATPARAM_ATTACHAUXEFFECT, &effectId)) {
if (mPlayer != 0) {
status_t status;
status = mPlayer->attachAuxEffect(effectId);
// attachAuxEffect returns a status but we have no way to report it back to app
(void) status;
}
}
}
void GenericMediaPlayer::onSetAuxEffectSendLevel(const sp<AMessage> &msg) {
SL_LOGD("GenericMediaPlayer::onSetAuxEffectSendLevel()");
float level = 0.0f;
if (msg->findFloat(WHATPARAM_SETAUXEFFECTSENDLEVEL, &level)) {
if (mPlayer != 0) {
status_t status;
status = mPlayer->setAuxEffectSendLevel(level);
// setAuxEffectSendLevel returns a status but we have no way to report it back to app
(void) status;
}
}
}
void GenericMediaPlayer::onBufferingUpdate(const sp<AMessage> &msg) {
int32_t fillLevel = 0;
if (msg->findInt32(WHATPARAM_BUFFERING_UPDATE, &fillLevel)) {
SL_LOGD("GenericMediaPlayer::onBufferingUpdate(fillLevel=%d)", fillLevel);
Mutex::Autolock _l(mSettingsLock);
mCacheFill = fillLevel;
// handle cache fill update
if (mCacheFill - mLastNotifiedCacheFill >= mCacheFillNotifThreshold) {
notifyCacheFill();
}
// handle prefetch status update
// compute how much time ahead of position is buffered
int durationMsec, positionMsec = -1;
if ((mStateFlags & kFlagPrepared) && (mPlayer != 0)
&& (OK == mPlayer->getDuration(&durationMsec))
&& (OK == mPlayer->getCurrentPosition(&positionMsec))) {
if ((-1 != durationMsec) && (-1 != positionMsec)) {
// evaluate prefetch status based on buffer time thresholds
int64_t bufferedDurationMsec = (durationMsec * fillLevel / 100) - positionMsec;
CacheStatus_t newCacheStatus = mCacheStatus;
if (bufferedDurationMsec > DURATION_CACHED_HIGH_MS) {
newCacheStatus = kStatusHigh;
} else if (bufferedDurationMsec > DURATION_CACHED_MED_MS) {
newCacheStatus = kStatusEnough;
} else if (bufferedDurationMsec > DURATION_CACHED_LOW_MS) {
newCacheStatus = kStatusIntermediate;
} else if (bufferedDurationMsec == 0) {
newCacheStatus = kStatusEmpty;
} else {
newCacheStatus = kStatusLow;
}
if (newCacheStatus != mCacheStatus) {
mCacheStatus = newCacheStatus;
notifyStatus();
}
}
}
} else {
SL_LOGV("GenericMediaPlayer::onBufferingUpdate(fillLevel=unknown)");
}
}
//--------------------------------------------------
/**
* called from GenericMediaPlayer::onPrepare after the MediaPlayer mPlayer is prepared successfully
* pre-conditions:
* mPlayer != 0
* mPlayer is prepared successfully
*/
void GenericMediaPlayer::afterMediaPlayerPreparedSuccessfully() {
SL_LOGV("GenericMediaPlayer::afterMediaPlayerPrepared()");
assert(mPlayer != 0);
assert(mStateFlags & kFlagPrepared);
// Mark this player as prepared successfully, so safe to directly call getCurrentPosition
{
Mutex::Autolock _l(mPreparedPlayerLock);
assert(mPreparedPlayer == 0);
mPreparedPlayer = mPlayer;
}
// retrieve channel count
int32_t channelCount;
Parcel *reply = new Parcel();
status_t status = mPlayer->getParameter(KEY_PARAMETER_AUDIO_CHANNEL_COUNT, reply);
if (status == NO_ERROR) {
channelCount = reply->readInt32();
} else {
// FIXME MPEG-2 TS doesn't yet implement this key, so default to stereo
channelCount = 2;
}
if (UNKNOWN_NUMCHANNELS != channelCount) {
// now that we know the channel count, re-calculate the volumes
notify(PLAYEREVENT_CHANNEL_COUNT, channelCount, true /*async*/);
} else {
LOGW("channel count is still unknown after prepare");
}
delete reply;
// retrieve duration
{
int msec = 0;
if (OK == mPlayer->getDuration(&msec)) {
Mutex::Autolock _l(mSettingsLock);
mDurationMsec = msec;
}
}
// now that we have a MediaPlayer, set the looping flag
if (mStateFlags & kFlagLooping) {
(void) mPlayer->setLooping(1);
}
// when the MediaPlayer mPlayer is prepared, there is "sufficient data" in the playback buffers
// if the data source was local, and the buffers are considered full so we need to notify that
bool isLocalSource = true;
if (kDataLocatorUri == mDataLocatorType) {
isLocalSource = !isDistantProtocol(mDataLocator.uriRef);
}
if (isLocalSource) {
SL_LOGD("media player prepared on local source");
{
Mutex::Autolock _l(mSettingsLock);
mCacheStatus = kStatusHigh;
mCacheFill = 1000;
notifyStatus();
notifyCacheFill();
}
} else {
SL_LOGD("media player prepared on non-local source");
}
}
//--------------------------------------------------
// If player is prepared successfully, set output parameter to that reference, otherwise NULL
void GenericMediaPlayer::getPreparedPlayer(sp<IMediaPlayer> &preparedPlayer)
{
Mutex::Autolock _l(mPreparedPlayerLock);
preparedPlayer = mPreparedPlayer;
}
} // namespace android