Gapless playback, step 1.
Currently able to play Ogg Vorbis, PCM WAV and other lossless files seamlessly by reusing the initial AudioTrack for subsequent players. Change-Id: Ie7cf6b9076bdf4f9211574456d192c02c04fecc7
This commit is contained in:
@@ -64,6 +64,7 @@ public:
|
||||
virtual status_t setParameter(int key, const Parcel& request) = 0;
|
||||
virtual status_t getParameter(int key, Parcel* reply) = 0;
|
||||
virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint) = 0;
|
||||
virtual status_t setNextPlayer(const sp<IMediaPlayer>& next) = 0;
|
||||
|
||||
// Invoke a generic method on the player by using opaque parcels
|
||||
// for the request and reply.
|
||||
|
||||
@@ -107,6 +107,7 @@ public:
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual status_t setPlaybackRatePermille(int32_t rate) { return INVALID_OPERATION; }
|
||||
virtual bool needsTrailingPadding() { return true; }
|
||||
};
|
||||
|
||||
MediaPlayerBase() : mCookie(0), mNotify(0) {}
|
||||
|
||||
@@ -96,6 +96,9 @@ enum media_error_type {
|
||||
enum media_info_type {
|
||||
// 0xx
|
||||
MEDIA_INFO_UNKNOWN = 1,
|
||||
// The player was started because it was used as the next player for another
|
||||
// player, which just completed playback
|
||||
MEDIA_INFO_STARTED_AS_NEXT = 2,
|
||||
// 7xx
|
||||
// The video is too complex for the decoder: it can't decode frames fast
|
||||
// enough. Possibly only the audio plays fine at this stage.
|
||||
@@ -207,6 +210,7 @@ public:
|
||||
status_t setParameter(int key, const Parcel& request);
|
||||
status_t getParameter(int key, Parcel* reply);
|
||||
status_t setRetransmitEndpoint(const char* addrString, uint16_t port);
|
||||
status_t setNextMediaPlayer(const sp<MediaPlayer>& player);
|
||||
|
||||
private:
|
||||
void clear_l();
|
||||
|
||||
@@ -1147,6 +1147,26 @@ public class MediaPlayer
|
||||
return native_setMetadataFilter(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the MediaPlayer to start when this MediaPlayer finishes playback
|
||||
* (i.e. reaches the end of the stream).
|
||||
* The media framework will attempt to transition from this player to
|
||||
* the next as seamlessly as possible. The next player can be set at
|
||||
* any time before completion. The next player must be prepared by the
|
||||
* app, and the application should not call start() on it.
|
||||
* The next MediaPlayer must be different from 'this'. An exception
|
||||
* will be thrown if next == this.
|
||||
* The application may call setNextMediaPlayer(null) to indicate no
|
||||
* next player should be started at the end of playback.
|
||||
* If the current player is looping, it will keep looping and the next
|
||||
* player will not be started.
|
||||
*
|
||||
* @param next the player to start after this one completes playback.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public native void setNextMediaPlayer(MediaPlayer next);
|
||||
|
||||
/**
|
||||
* Releases resources associated with this MediaPlayer object.
|
||||
* It is considered good practice to call this method when you're
|
||||
@@ -1652,6 +1672,10 @@ public class MediaPlayer
|
||||
return;
|
||||
}
|
||||
|
||||
if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
|
||||
// this acquires the wakelock if needed, and sets the client side state
|
||||
mp.start();
|
||||
}
|
||||
if (mp.mEventHandler != null) {
|
||||
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
|
||||
mp.mEventHandler.sendMessage(m);
|
||||
@@ -1906,6 +1930,13 @@ public class MediaPlayer
|
||||
*/
|
||||
public static final int MEDIA_INFO_UNKNOWN = 1;
|
||||
|
||||
/** The player was started because it was used as the next player for another
|
||||
* player, which just completed playback.
|
||||
* @see android.media.MediaPlayer.OnInfoListener
|
||||
* @hide
|
||||
*/
|
||||
public static final int MEDIA_INFO_STARTED_AS_NEXT = 2;
|
||||
|
||||
/** The video is too complex for the decoder: it can't decode frames fast
|
||||
* enough. Possibly only the audio plays fine at this stage.
|
||||
* @see android.media.MediaPlayer.OnInfoListener
|
||||
|
||||
@@ -795,6 +795,33 @@ android_media_MediaPlayer_getParameter(JNIEnv *env, jobject thiz, jint key, jobj
|
||||
process_media_player_call(env, thiz, mp->getParameter(key, reply), NULL, NULL );
|
||||
}
|
||||
|
||||
static void
|
||||
android_media_MediaPlayer_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject java_player)
|
||||
{
|
||||
ALOGV("setNextMediaPlayer");
|
||||
sp<MediaPlayer> thisplayer = getMediaPlayer(env, thiz);
|
||||
if (thisplayer == NULL) {
|
||||
jniThrowException(env, "java/lang/IllegalStateException", "This player not initialized");
|
||||
return;
|
||||
}
|
||||
sp<MediaPlayer> nextplayer = (java_player == NULL) ? NULL : getMediaPlayer(env, java_player);
|
||||
if (nextplayer == NULL && java_player != NULL) {
|
||||
jniThrowException(env, "java/lang/IllegalStateException", "That player not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextplayer == thisplayer) {
|
||||
jniThrowException(env, "java/lang/IllegalArgumentException", "Next player can't be self");
|
||||
return;
|
||||
}
|
||||
// tie the two players together
|
||||
process_media_player_call(
|
||||
env, thiz, thisplayer->setNextMediaPlayer(nextplayer),
|
||||
"java/lang/IllegalArgumentException",
|
||||
"setNextMediaPlayer failed." );
|
||||
;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
@@ -840,6 +867,7 @@ static JNINativeMethod gMethods[] = {
|
||||
{"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_setParameter},
|
||||
{"getParameter", "(ILandroid/os/Parcel;)V", (void *)android_media_MediaPlayer_getParameter},
|
||||
{"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint},
|
||||
{"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer},
|
||||
};
|
||||
|
||||
static const char* const kClassPathName = "android/media/MediaPlayer";
|
||||
|
||||
@@ -55,6 +55,7 @@ enum {
|
||||
SET_PARAMETER,
|
||||
GET_PARAMETER,
|
||||
SET_RETRANSMIT_ENDPOINT,
|
||||
SET_NEXT_PLAYER,
|
||||
};
|
||||
|
||||
class BpMediaPlayer: public BpInterface<IMediaPlayer>
|
||||
@@ -307,7 +308,15 @@ public:
|
||||
if (OK != err) {
|
||||
return err;
|
||||
}
|
||||
return reply.readInt32();
|
||||
}
|
||||
|
||||
status_t setNextPlayer(const sp<IMediaPlayer>& player) {
|
||||
Parcel data, reply;
|
||||
data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
|
||||
sp<IBinder> b(player->asBinder());
|
||||
data.writeStrongBinder(b);
|
||||
remote()->transact(SET_NEXT_PLAYER, data, &reply);
|
||||
return reply.readInt32();
|
||||
}
|
||||
};
|
||||
@@ -489,7 +498,11 @@ status_t BnMediaPlayer::onTransact(
|
||||
} else {
|
||||
reply->writeInt32(setRetransmitEndpoint(NULL));
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
} break;
|
||||
case SET_NEXT_PLAYER: {
|
||||
CHECK_INTERFACE(IMediaPlayer, data, reply);
|
||||
reply->writeInt32(setNextPlayer(interface_cast<IMediaPlayer>(data.readStrongBinder())));
|
||||
return NO_ERROR;
|
||||
} break;
|
||||
default:
|
||||
|
||||
@@ -788,4 +788,11 @@ void MediaPlayer::died()
|
||||
|
||||
}
|
||||
|
||||
status_t MediaPlayer::setNextMediaPlayer(const sp<MediaPlayer>& next) {
|
||||
if (mPlayer == NULL) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mPlayer->setNextPlayer(next == NULL ? NULL : next->mPlayer);
|
||||
}
|
||||
|
||||
}; // namespace android
|
||||
|
||||
@@ -1068,6 +1068,20 @@ status_t MediaPlayerService::Client::getDuration(int *msec)
|
||||
return ret;
|
||||
}
|
||||
|
||||
status_t MediaPlayerService::Client::setNextPlayer(const sp<IMediaPlayer>& player) {
|
||||
ALOGV("setNextPlayer");
|
||||
Mutex::Autolock l(mLock);
|
||||
sp<Client> c = static_cast<Client*>(player.get());
|
||||
mNextClient = c;
|
||||
if (mAudioOutput != NULL && c != NULL) {
|
||||
mAudioOutput->setNextOutput(c->mAudioOutput);
|
||||
} else {
|
||||
ALOGE("no current audio output");
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
status_t MediaPlayerService::Client::seekTo(int msec)
|
||||
{
|
||||
ALOGV("[%d] seekTo(%d)", mConnId, msec);
|
||||
@@ -1189,6 +1203,15 @@ void MediaPlayerService::Client::notify(
|
||||
{
|
||||
Client* client = static_cast<Client*>(cookie);
|
||||
|
||||
{
|
||||
Mutex::Autolock l(client->mLock);
|
||||
if (msg == MEDIA_PLAYBACK_COMPLETE && client->mNextClient != NULL) {
|
||||
client->mAudioOutput->switchToNextOutput();
|
||||
client->mNextClient->start();
|
||||
client->mNextClient->mClient->notify(MEDIA_INFO, MEDIA_INFO_STARTED_AS_NEXT, 0, obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (MEDIA_INFO == msg &&
|
||||
MEDIA_INFO_METADATA_UPDATE == ext1) {
|
||||
const media::Metadata::Type metadata_type = ext2;
|
||||
@@ -1376,9 +1399,11 @@ Exit:
|
||||
MediaPlayerService::AudioOutput::AudioOutput(int sessionId)
|
||||
: mCallback(NULL),
|
||||
mCallbackCookie(NULL),
|
||||
mCallbackData(NULL),
|
||||
mSessionId(sessionId) {
|
||||
ALOGV("AudioOutput(%d)", sessionId);
|
||||
mTrack = 0;
|
||||
mRecycledTrack = 0;
|
||||
mStreamType = AUDIO_STREAM_MUSIC;
|
||||
mLeftVolume = 1.0;
|
||||
mRightVolume = 1.0;
|
||||
@@ -1393,6 +1418,8 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId)
|
||||
MediaPlayerService::AudioOutput::~AudioOutput()
|
||||
{
|
||||
close();
|
||||
delete mRecycledTrack;
|
||||
delete mCallbackData;
|
||||
}
|
||||
|
||||
void MediaPlayerService::AudioOutput::setMinBufferCount()
|
||||
@@ -1473,7 +1500,6 @@ status_t MediaPlayerService::AudioOutput::open(
|
||||
}
|
||||
ALOGV("open(%u, %d, 0x%x, %d, %d, %d)", sampleRate, channelCount, channelMask,
|
||||
format, bufferCount, mSessionId);
|
||||
if (mTrack) close();
|
||||
int afSampleRate;
|
||||
int afFrameCount;
|
||||
int frameCount;
|
||||
@@ -1494,9 +1520,48 @@ status_t MediaPlayerService::AudioOutput::open(
|
||||
return NO_INIT;
|
||||
}
|
||||
}
|
||||
if (mRecycledTrack) {
|
||||
// check if the existing track can be reused as-is, or if a new track needs to be created.
|
||||
|
||||
bool reuse = true;
|
||||
if ((mCallbackData == NULL && mCallback != NULL) ||
|
||||
(mCallbackData != NULL && mCallback == NULL)) {
|
||||
// recycled track uses callbacks but the caller wants to use writes, or vice versa
|
||||
ALOGV("can't chain callback and write");
|
||||
reuse = false;
|
||||
} else if ((mRecycledTrack->getSampleRate() != sampleRate) ||
|
||||
(mRecycledTrack->channelCount() != channelCount) ||
|
||||
(mRecycledTrack->frameCount() != frameCount)) {
|
||||
ALOGV("samplerate, channelcount or framecount differ");
|
||||
reuse = false;
|
||||
}
|
||||
if (reuse) {
|
||||
ALOGV("chaining to next output");
|
||||
close();
|
||||
mTrack = mRecycledTrack;
|
||||
mRecycledTrack = NULL;
|
||||
if (mCallbackData != NULL) {
|
||||
mCallbackData->setOutput(this);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
// if we're not going to reuse the track, unblock and flush it
|
||||
if (mCallbackData != NULL) {
|
||||
mCallbackData->setOutput(NULL);
|
||||
mCallbackData->endTrackSwitch();
|
||||
}
|
||||
mRecycledTrack->flush();
|
||||
delete mRecycledTrack;
|
||||
mRecycledTrack = NULL;
|
||||
delete mCallbackData;
|
||||
mCallbackData = NULL;
|
||||
close();
|
||||
}
|
||||
|
||||
AudioTrack *t;
|
||||
if (mCallback != NULL) {
|
||||
mCallbackData = new CallbackData(this);
|
||||
t = new AudioTrack(
|
||||
mStreamType,
|
||||
sampleRate,
|
||||
@@ -1505,7 +1570,7 @@ status_t MediaPlayerService::AudioOutput::open(
|
||||
frameCount,
|
||||
0 /* flags */,
|
||||
CallbackWrapper,
|
||||
this,
|
||||
mCallbackData,
|
||||
0,
|
||||
mSessionId);
|
||||
} else {
|
||||
@@ -1546,6 +1611,9 @@ status_t MediaPlayerService::AudioOutput::open(
|
||||
void MediaPlayerService::AudioOutput::start()
|
||||
{
|
||||
ALOGV("start");
|
||||
if (mCallbackData != NULL) {
|
||||
mCallbackData->endTrackSwitch();
|
||||
}
|
||||
if (mTrack) {
|
||||
mTrack->setVolume(mLeftVolume, mRightVolume);
|
||||
mTrack->setAuxEffectSendLevel(mSendLevel);
|
||||
@@ -1553,8 +1621,27 @@ void MediaPlayerService::AudioOutput::start()
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayerService::AudioOutput::setNextOutput(const sp<AudioOutput>& nextOutput) {
|
||||
mNextOutput = nextOutput;
|
||||
}
|
||||
|
||||
|
||||
void MediaPlayerService::AudioOutput::switchToNextOutput() {
|
||||
ALOGV("switchToNextOutput");
|
||||
if (mNextOutput != NULL) {
|
||||
if (mCallbackData != NULL) {
|
||||
mCallbackData->beginTrackSwitch();
|
||||
}
|
||||
delete mNextOutput->mCallbackData;
|
||||
mNextOutput->mCallbackData = mCallbackData;
|
||||
mCallbackData = NULL;
|
||||
mNextOutput->mRecycledTrack = mTrack;
|
||||
mTrack = NULL;
|
||||
mNextOutput->mSampleRateHz = mSampleRateHz;
|
||||
mNextOutput->mMsecsPerFrame = mMsecsPerFrame;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size)
|
||||
{
|
||||
LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback.");
|
||||
@@ -1646,13 +1733,22 @@ void MediaPlayerService::AudioOutput::CallbackWrapper(
|
||||
return;
|
||||
}
|
||||
|
||||
AudioOutput *me = (AudioOutput *)cookie;
|
||||
CallbackData *data = (CallbackData*)cookie;
|
||||
data->lock();
|
||||
AudioOutput *me = data->getOutput();
|
||||
AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info;
|
||||
if (me == NULL) {
|
||||
// no output set, likely because the track was scheduled to be reused
|
||||
// by another player, but the format turned out to be incompatible.
|
||||
data->unlock();
|
||||
buffer->size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t actualSize = (*me->mCallback)(
|
||||
me, buffer->raw, buffer->size, me->mCallbackCookie);
|
||||
|
||||
if (actualSize == 0 && buffer->size > 0) {
|
||||
if (actualSize == 0 && buffer->size > 0 && me->mNextOutput == NULL) {
|
||||
// We've reached EOS but the audio track is not stopped yet,
|
||||
// keep playing silence.
|
||||
|
||||
@@ -1661,6 +1757,7 @@ void MediaPlayerService::AudioOutput::CallbackWrapper(
|
||||
}
|
||||
|
||||
buffer->size = actualSize;
|
||||
data->unlock();
|
||||
}
|
||||
|
||||
int MediaPlayerService::AudioOutput::getSessionId()
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <media/IMediaPlayerService.h>
|
||||
#include <media/MediaPlayerInterface.h>
|
||||
#include <media/Metadata.h>
|
||||
#include <media/stagefright/foundation/ABase.h>
|
||||
|
||||
#include <system/audio.h>
|
||||
|
||||
@@ -69,7 +70,9 @@ class MediaPlayerService : public BnMediaPlayerService
|
||||
|
||||
class AudioOutput : public MediaPlayerBase::AudioSink
|
||||
{
|
||||
public:
|
||||
class CallbackData;
|
||||
|
||||
public:
|
||||
AudioOutput(int sessionId);
|
||||
virtual ~AudioOutput();
|
||||
|
||||
@@ -104,14 +107,21 @@ class MediaPlayerService : public BnMediaPlayerService
|
||||
|
||||
static bool isOnEmulator();
|
||||
static int getMinBufferCount();
|
||||
void setNextOutput(const sp<AudioOutput>& nextOutput);
|
||||
void switchToNextOutput();
|
||||
virtual bool needsTrailingPadding() { return mNextOutput == NULL; }
|
||||
|
||||
private:
|
||||
static void setMinBufferCount();
|
||||
static void CallbackWrapper(
|
||||
int event, void *me, void *info);
|
||||
|
||||
AudioTrack* mTrack;
|
||||
AudioTrack* mRecycledTrack;
|
||||
sp<AudioOutput> mNextOutput;
|
||||
AudioCallback mCallback;
|
||||
void * mCallbackCookie;
|
||||
CallbackData * mCallbackData;
|
||||
audio_stream_type_t mStreamType;
|
||||
float mLeftVolume;
|
||||
float mRightVolume;
|
||||
@@ -124,7 +134,38 @@ class MediaPlayerService : public BnMediaPlayerService
|
||||
static bool mIsOnEmulator;
|
||||
static int mMinBufferCount; // 12 for emulator; otherwise 4
|
||||
|
||||
};
|
||||
// CallbackData is what is passed to the AudioTrack as the "user" data.
|
||||
// We need to be able to target this to a different Output on the fly,
|
||||
// so we can't use the Output itself for this.
|
||||
class CallbackData {
|
||||
public:
|
||||
CallbackData(AudioOutput *cookie) {
|
||||
mData = cookie;
|
||||
mSwitching = false;
|
||||
}
|
||||
AudioOutput * getOutput() { return mData;}
|
||||
void setOutput(AudioOutput* newcookie) { mData = newcookie; }
|
||||
// lock/unlock are used by the callback before accessing the payload of this object
|
||||
void lock() { mLock.lock(); }
|
||||
void unlock() { mLock.unlock(); }
|
||||
// beginTrackSwitch/endTrackSwitch are used when this object is being handed over
|
||||
// to the next sink.
|
||||
void beginTrackSwitch() { mLock.lock(); mSwitching = true; }
|
||||
void endTrackSwitch() {
|
||||
if (mSwitching) {
|
||||
mLock.unlock();
|
||||
}
|
||||
mSwitching = false;
|
||||
}
|
||||
private:
|
||||
AudioOutput * mData;
|
||||
mutable Mutex mLock;
|
||||
bool mSwitching;
|
||||
DISALLOW_EVIL_CONSTRUCTORS(CallbackData);
|
||||
};
|
||||
|
||||
}; // AudioOutput
|
||||
|
||||
|
||||
class AudioCache : public MediaPlayerBase::AudioSink
|
||||
{
|
||||
@@ -184,7 +225,7 @@ class MediaPlayerService : public BnMediaPlayerService
|
||||
bool mCommandComplete;
|
||||
|
||||
sp<Thread> mCallbackThread;
|
||||
};
|
||||
}; // AudioCache
|
||||
|
||||
public:
|
||||
static void instantiate();
|
||||
@@ -278,6 +319,7 @@ private:
|
||||
virtual status_t setParameter(int key, const Parcel &request);
|
||||
virtual status_t getParameter(int key, Parcel *reply);
|
||||
virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint);
|
||||
virtual status_t setNextPlayer(const sp<IMediaPlayer>& player);
|
||||
|
||||
sp<MediaPlayerBase> createPlayer(player_type playerType);
|
||||
|
||||
@@ -350,6 +392,7 @@ private:
|
||||
sp<IBinder> mConnectedWindowBinder;
|
||||
struct sockaddr_in mRetransmitEndpoint;
|
||||
bool mRetransmitEndpointValid;
|
||||
sp<Client> mNextClient;
|
||||
|
||||
// Metadata filters.
|
||||
media::Metadata::Filter mMetadataAllow; // protected by mLock
|
||||
@@ -364,7 +407,7 @@ private:
|
||||
#if CALLBACK_ANTAGONIZER
|
||||
Antagonizer* mAntagonizer;
|
||||
#endif
|
||||
};
|
||||
}; // Client
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -419,7 +419,11 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
|
||||
timeToCompletionUs, timeToCompletionUs / 1E6);
|
||||
|
||||
postEOS = true;
|
||||
postEOSDelayUs = timeToCompletionUs + mLatencyUs;
|
||||
if (mAudioSink->needsTrailingPadding()) {
|
||||
postEOSDelayUs = timeToCompletionUs + mLatencyUs;
|
||||
} else {
|
||||
postEOSDelayUs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
mReachedEOS = true;
|
||||
|
||||
Reference in New Issue
Block a user