diff options
author | Marco Nelissen <marcone@google.com> | 2012-02-28 16:07:44 -0800 |
---|---|---|
committer | Marco Nelissen <marcone@google.com> | 2012-03-13 13:13:14 -0700 |
commit | 84b832054552e00257bb04997143ca33d6d100be (patch) | |
tree | cf04f15f15da2d9ddbd2112b2b89542f6270b4ce | |
parent | f19d5f0271a9e0cdf3a79c6ccab5aa151b0b3239 (diff) | |
download | frameworks_base-84b832054552e00257bb04997143ca33d6d100be.zip frameworks_base-84b832054552e00257bb04997143ca33d6d100be.tar.gz frameworks_base-84b832054552e00257bb04997143ca33d6d100be.tar.bz2 |
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
-rw-r--r-- | include/media/IMediaPlayer.h | 1 | ||||
-rw-r--r-- | include/media/MediaPlayerInterface.h | 1 | ||||
-rw-r--r-- | include/media/mediaplayer.h | 4 | ||||
-rw-r--r-- | media/java/android/media/MediaPlayer.java | 31 | ||||
-rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 28 | ||||
-rw-r--r-- | media/libmedia/IMediaPlayer.cpp | 15 | ||||
-rw-r--r-- | media/libmedia/mediaplayer.cpp | 7 | ||||
-rw-r--r-- | media/libmediaplayerservice/MediaPlayerService.cpp | 105 | ||||
-rw-r--r-- | media/libmediaplayerservice/MediaPlayerService.h | 51 | ||||
-rw-r--r-- | media/libstagefright/AudioPlayer.cpp | 6 |
10 files changed, 239 insertions, 10 deletions
diff --git a/include/media/IMediaPlayer.h b/include/media/IMediaPlayer.h index 39d58ab..00facc5 100644 --- a/include/media/IMediaPlayer.h +++ b/include/media/IMediaPlayer.h @@ -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. diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index 8168dff..d4aa233 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -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) {} diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h index 9cd5f9f..662dd13 100644 --- a/include/media/mediaplayer.h +++ b/include/media/mediaplayer.h @@ -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(); diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index e663e91..82b9d4a 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1148,6 +1148,26 @@ public class MediaPlayer } /** + * 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 * done using the MediaPlayer. In particular, whenever an Activity @@ -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 diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index f572f71..745e253 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -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"; diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp index c47fa41..16ba484 100644 --- a/media/libmedia/IMediaPlayer.cpp +++ b/media/libmedia/IMediaPlayer.cpp @@ -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: diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp index 4ff1862..eedb3ce 100644 --- a/media/libmedia/mediaplayer.cpp +++ b/media/libmedia/mediaplayer.cpp @@ -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 diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 1a85c9c..657cb3d 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -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,7 +1621,26 @@ 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) { @@ -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() diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index 85cec22..d4e0eb1 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -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 // ---------------------------------------------------------------------------- diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 2b3cb1a..f84e37f 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -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; |