summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commit84b832054552e00257bb04997143ca33d6d100be (patch)
treecf04f15f15da2d9ddbd2112b2b89542f6270b4ce
parentf19d5f0271a9e0cdf3a79c6ccab5aa151b0b3239 (diff)
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.h1
-rw-r--r--include/media/MediaPlayerInterface.h1
-rw-r--r--include/media/mediaplayer.h4
-rw-r--r--media/java/android/media/MediaPlayer.java31
-rw-r--r--media/jni/android_media_MediaPlayer.cpp28
-rw-r--r--media/libmedia/IMediaPlayer.cpp15
-rw-r--r--media/libmedia/mediaplayer.cpp7
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.cpp105
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.h51
-rw-r--r--media/libstagefright/AudioPlayer.cpp6
10 files changed, 239 insertions, 10 deletions
diff --git a/include/media/IMediaPlayer.h b/include/media/IMediaPlayer.h
index 39d58ab586a5..00facc5a6d75 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 8168dff803da..d4aa233940b3 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 9cd5f9fc7d32..662dd13afca5 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 e663e9166026..82b9d4ae88e3 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 f572f71458fc..745e2538854e 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 c47fa41e346b..16ba484e2d77 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 4ff1862e8d9c..eedb3ce5caf8 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 1a85c9ca4798..657cb3d92211 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 85cec220c471..d4e0eb115acb 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 2b3cb1ac3758..f84e37fe016d 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;