tracks.
o Newly introduced APIs are (MediaPlayer):
getTrackInfo() / addExternalSource() / enableTrack() / disableTrack().
o Timed text tracks are supported only, for now.
o TODOs:
- Define the audio/video behavior for enableTrack and disableTrack.
- Refactoring AwesomePlayer / TimedTextDriver so that all types of
track index can be managed in the correct order and be ready for
supporting audio/video tracks.
(MediaExtractor and MediaSource for text file might be necessary.)
Change-Id: Idb85e1b3f2ed49a64f377d05472dd6663ce94e07
MEDIA_INFO_NOT_SEEKABLE = 801,
// New media metadata is available.
MEDIA_INFO_METADATA_UPDATE = 802,
+
+ //9xx
+ MEDIA_INFO_TIMED_TEXT_ERROR = 900,
};
// The same enum space is used for both set and get, in case there are future keys that
// can be both set and get. But as of now, all parameters are either set only or get only.
enum media_parameter_keys {
- KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000, // set only
- KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001, // set only
-
// Streaming/buffering parameters
KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS = 1100, // set only
KEY_PARAMETER_PLAYBACK_RATE_PERMILLE = 1300, // set only
};
+// Keep INVOKE_ID_* in sync with MediaPlayer.java.
+enum media_player_invoke_ids {
+ INVOKE_ID_GET_TRACK_INFO = 1,
+ INVOKE_ID_ADD_EXTERNAL_SOURCE = 2,
+ INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3,
+ INVOKE_ID_SELECT_TRACK = 4,
+ INVOKE_ID_UNSELECT_TRACK = 5,
+};
+
+// Keep MEDIA_TRACK_TYPE_* in sync with MediaPlayer.java.
+enum media_track_type {
+ MEDIA_TRACK_TYPE_UNKNOWN = 0,
+ MEDIA_TRACK_TYPE_VIDEO = 1,
+ MEDIA_TRACK_TYPE_AUDIO = 2,
+ MEDIA_TRACK_TYPE_TIMEDTEXT = 3,
+};
+
// ----------------------------------------------------------------------------
// ref-counted object for callbacks
class MediaPlayerListener: virtual public RefBase
extern const char *MEDIA_MIMETYPE_CONTAINER_WVM;
extern const char *MEDIA_MIMETYPE_TEXT_3GPP;
+extern const char *MEDIA_MIMETYPE_TEXT_SUBRIP;
} // namespace android
~TimedTextDriver();
- // TODO: pause-resume pair seems equivalent to stop-start pair.
- // Check if it is replaceable with stop-start.
status_t start();
- status_t stop();
status_t pause();
- status_t resume();
+ status_t selectTrack(int32_t index);
+ status_t unselectTrack(int32_t index);
status_t seekToAsync(int64_t timeUs);
status_t addInBandTextSource(const sp<MediaSource>& source);
- status_t addOutOfBandTextSource(const Parcel &request);
+ status_t addOutOfBandTextSource(const char *uri, const char *mimeType);
+ // Caller owns the file desriptor and caller is responsible for closing it.
+ status_t addOutOfBandTextSource(
+ int fd, off64_t offset, size_t length, const char *mimeType);
- status_t setTimedTextTrackIndex(int32_t index);
+ void getTrackInfo(Parcel *parcel);
private:
Mutex mLock;
enum State {
UNINITIALIZED,
- STOPPED,
PLAYING,
PAUSED,
};
// Variables to be guarded by mLock.
State mState;
- Vector<sp<TimedTextSource> > mTextInBandVector;
- Vector<sp<TimedTextSource> > mTextOutOfBandVector;
+ int32_t mCurrentTrackIndex;
+ Vector<sp<TimedTextSource> > mTextSourceVector;
// -- End of variables to be guarded by mLock
- status_t setTimedTextTrackIndex_l(int32_t index);
+ status_t selectTrack_l(int32_t index);
DISALLOW_EVIL_CONSTRUCTORS(TimedTextDriver);
};
}
status_t StagefrightPlayer::invoke(const Parcel &request, Parcel *reply) {
- return INVALID_OPERATION;
+ ALOGV("invoke()");
+ return mPlayer->invoke(request, reply);
}
void StagefrightPlayer::setAudioSink(const sp<AudioSink> &audioSink) {
modifyFlags(AUDIO_RUNNING, CLEAR);
}
- if (mFlags & TEXTPLAYER_STARTED) {
+ if (mFlags & TEXTPLAYER_INITIALIZED) {
mTextDriver->pause();
modifyFlags(TEXT_RUNNING, CLEAR);
}
return OK;
}
-status_t AwesomePlayer::setTimedTextTrackIndex(int32_t index) {
- if (mTextDriver != NULL) {
- if (index >= 0) { // to turn on a text track
- status_t err = mTextDriver->setTimedTextTrackIndex(index);
- if (err != OK) {
- return err;
- }
-
- modifyFlags(TEXT_RUNNING, SET);
- modifyFlags(TEXTPLAYER_STARTED, SET);
- return OK;
- } else { // to turn off the text track display
- if (mFlags & TEXT_RUNNING) {
- modifyFlags(TEXT_RUNNING, CLEAR);
- }
- if (mFlags & TEXTPLAYER_STARTED) {
- modifyFlags(TEXTPLAYER_STARTED, CLEAR);
- }
-
- return mTextDriver->setTimedTextTrackIndex(index);
- }
- } else {
- return INVALID_OPERATION;
- }
-}
-
status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
if (mFlags & CACHE_UNDERRUN) {
modifyFlags(CACHE_UNDERRUN, CLEAR);
seekAudioIfNecessary_l();
- if (mFlags & TEXTPLAYER_STARTED) {
+ if (mFlags & TEXTPLAYER_INITIALIZED) {
mTextDriver->seekToAsync(mSeekTimeUs);
}
}
}
- if ((mFlags & TEXTPLAYER_STARTED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) {
- mTextDriver->resume();
+ if ((mFlags & TEXTPLAYER_INITIALIZED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) {
+ mTextDriver->start();
modifyFlags(TEXT_RUNNING, SET);
}
status_t AwesomePlayer::setParameter(int key, const Parcel &request) {
switch (key) {
- case KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX:
- {
- Mutex::Autolock autoLock(mTimedTextLock);
- return setTimedTextTrackIndex(request.readInt32());
- }
- case KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE:
- {
- Mutex::Autolock autoLock(mTimedTextLock);
- if (mTextDriver == NULL) {
- mTextDriver = new TimedTextDriver(mListener);
- }
-
- return mTextDriver->addOutOfBandTextSource(request);
- }
case KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS:
{
return setCacheStatCollectFreq(request);
}
}
+status_t AwesomePlayer::invoke(const Parcel &request, Parcel *reply) {
+ if (NULL == reply) {
+ return android::BAD_VALUE;
+ }
+ int32_t methodId;
+ status_t ret = request.readInt32(&methodId);
+ if (ret != android::OK) {
+ return ret;
+ }
+ switch(methodId) {
+ case INVOKE_ID_GET_TRACK_INFO:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ return INVALID_OPERATION;
+ }
+ mTextDriver->getTrackInfo(reply);
+ return OK;
+ }
+ case INVOKE_ID_ADD_EXTERNAL_SOURCE:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ mTextDriver = new TimedTextDriver(mListener);
+ }
+ // String values written in Parcel are UTF-16 values.
+ String16 uri16 = request.readString16();
+ const char *uri = NULL;
+ if (uri16 != NULL) {
+ uri = String8(uri16).string();
+ }
+ String16 mimeType16 = request.readString16();
+ const char *mimeType = NULL;
+ if (mimeType16 != NULL) {
+ mimeType = String8(mimeType16).string();
+ }
+ return mTextDriver->addOutOfBandTextSource(uri, mimeType);
+ }
+ case INVOKE_ID_ADD_EXTERNAL_SOURCE_FD:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ mTextDriver = new TimedTextDriver(mListener);
+ }
+ int fd = request.readFileDescriptor();
+ off64_t offset = request.readInt64();
+ size_t length = request.readInt64();
+ String16 mimeType16 = request.readString16();
+ const char *mimeType = NULL;
+ if (mimeType16 != NULL) {
+ mimeType = String8(mimeType16).string();
+ }
+
+ return mTextDriver->addOutOfBandTextSource(
+ fd, offset, length, mimeType);
+ }
+ case INVOKE_ID_SELECT_TRACK:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ return INVALID_OPERATION;
+ }
+
+ status_t err = mTextDriver->selectTrack(
+ request.readInt32());
+ if (err == OK) {
+ modifyFlags(TEXTPLAYER_INITIALIZED, SET);
+ if (mFlags & PLAYING && !(mFlags & TEXT_RUNNING)) {
+ mTextDriver->start();
+ modifyFlags(TEXT_RUNNING, SET);
+ }
+ }
+ return err;
+ }
+ case INVOKE_ID_UNSELECT_TRACK:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ return INVALID_OPERATION;
+ }
+ status_t err = mTextDriver->unselectTrack(
+ request.readInt32());
+ if (err == OK) {
+ modifyFlags(TEXTPLAYER_INITIALIZED, CLEAR);
+ modifyFlags(TEXT_RUNNING, CLEAR);
+ }
+ return err;
+ }
+ default:
+ {
+ return ERROR_UNSUPPORTED;
+ }
+ }
+ // It will not reach here.
+ return OK;
+}
+
bool AwesomePlayer::isStreamingHTTP() const {
return mCachedSource != NULL || mWVMExtractor != NULL;
}
const char *MEDIA_MIMETYPE_CONTAINER_WVM = "video/wvm";
const char *MEDIA_MIMETYPE_TEXT_3GPP = "text/3gpp-tt";
+const char *MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
} // namespace android
status_t setParameter(int key, const Parcel &request);
status_t getParameter(int key, Parcel *reply);
+ status_t invoke(const Parcel &request, Parcel *reply);
status_t setCacheStatCollectFreq(const Parcel &request);
status_t seekTo(int64_t timeUs);
void postAudioEOS(int64_t delayUs = 0ll);
void postAudioSeekComplete();
- status_t setTimedTextTrackIndex(int32_t index);
-
status_t dump(int fd, const Vector<String16> &args) const;
private:
INCOGNITO = 0x8000,
TEXT_RUNNING = 0x10000,
- TEXTPLAYER_STARTED = 0x20000,
+ TEXTPLAYER_INITIALIZED = 0x20000,
SLOW_DECODER_HACK = 0x40000,
};
return OK;
}
+sp<MetaData> TimedText3GPPSource::getFormat() {
+ return mSource->getFormat();
+}
+
} // namespace android
Parcel *parcel,
const MediaSource::ReadOptions *options = NULL);
virtual status_t extractGlobalDescriptions(Parcel *parcel);
+ virtual sp<MetaData> getFormat();
protected:
virtual ~TimedText3GPPSource();
#include <binder/IPCThreadState.h>
+#include <media/mediaplayer.h>
#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
}
TimedTextDriver::~TimedTextDriver() {
- mTextInBandVector.clear();
- mTextOutOfBandVector.clear();
+ mTextSourceVector.clear();
mLooper->stop();
}
-status_t TimedTextDriver::setTimedTextTrackIndex_l(int32_t index) {
- if (index >=
- (int)(mTextInBandVector.size() + mTextOutOfBandVector.size())) {
+status_t TimedTextDriver::selectTrack_l(int32_t index) {
+ if (index >= (int)(mTextSourceVector.size())) {
return BAD_VALUE;
}
sp<TimedTextSource> source;
- if (index < mTextInBandVector.size()) {
- source = mTextInBandVector.itemAt(index);
- } else {
- source = mTextOutOfBandVector.itemAt(index - mTextInBandVector.size());
- }
+ source = mTextSourceVector.itemAt(index);
mPlayer->setDataSource(source);
+ if (mState == UNINITIALIZED) {
+ mState = PAUSED;
+ }
+ mCurrentTrackIndex = index;
return OK;
}
switch (mState) {
case UNINITIALIZED:
return INVALID_OPERATION;
- case STOPPED:
- mPlayer->start();
- break;
case PLAYING:
return OK;
case PAUSED:
- mPlayer->resume();
+ mPlayer->start();
break;
default:
TRESPASS();
return OK;
}
-status_t TimedTextDriver::stop() {
- return pause();
-}
-
// TODO: Test if pause() works properly.
// Scenario 1: start - pause - resume
// Scenario 2: start - seek
switch (mState) {
case UNINITIALIZED:
return INVALID_OPERATION;
- case STOPPED:
- return OK;
case PLAYING:
mPlayer->pause();
break;
return OK;
}
-status_t TimedTextDriver::resume() {
- return start();
-}
-
-status_t TimedTextDriver::seekToAsync(int64_t timeUs) {
- mPlayer->seekToAsync(timeUs);
- return OK;
-}
-
-status_t TimedTextDriver::setTimedTextTrackIndex(int32_t index) {
- // TODO: This is current implementation for MediaPlayer::disableTimedText().
- // Find better way for readability.
- if (index < 0) {
- mPlayer->pause();
- return OK;
- }
-
+status_t TimedTextDriver::selectTrack(int32_t index) {
status_t ret = OK;
Mutex::Autolock autoLock(mLock);
switch (mState) {
case UNINITIALIZED:
- ret = INVALID_OPERATION;
- break;
case PAUSED:
- ret = setTimedTextTrackIndex_l(index);
+ ret = selectTrack_l(index);
break;
case PLAYING:
mPlayer->pause();
- ret = setTimedTextTrackIndex_l(index);
- if (ret != OK) {
- break;
- }
- mPlayer->start();
- break;
- case STOPPED:
- // TODO: The only difference between STOPPED and PAUSED is this
- // part. Revise the flow from "MediaPlayer::enableTimedText()" and
- // remove one of the status, PAUSED and STOPPED, if possible.
- ret = setTimedTextTrackIndex_l(index);
+ ret = selectTrack_l(index);
if (ret != OK) {
break;
}
return ret;
}
+status_t TimedTextDriver::unselectTrack(int32_t index) {
+ if (mCurrentTrackIndex != index) {
+ return INVALID_OPERATION;
+ }
+ status_t err = pause();
+ if (err != OK) {
+ return err;
+ }
+ Mutex::Autolock autoLock(mLock);
+ mState = UNINITIALIZED;
+ return OK;
+}
+
+status_t TimedTextDriver::seekToAsync(int64_t timeUs) {
+ mPlayer->seekToAsync(timeUs);
+ return OK;
+}
+
status_t TimedTextDriver::addInBandTextSource(
const sp<MediaSource>& mediaSource) {
sp<TimedTextSource> source =
return ERROR_UNSUPPORTED;
}
Mutex::Autolock autoLock(mLock);
- mTextInBandVector.add(source);
- if (mState == UNINITIALIZED) {
- mState = STOPPED;
- }
+ mTextSourceVector.add(source);
return OK;
}
status_t TimedTextDriver::addOutOfBandTextSource(
- const Parcel &request) {
+ const char *uri, const char *mimeType) {
// TODO: Define "TimedTextSource::CreateFromURI(uri)"
// and move below lines there..?
- // String values written in Parcel are UTF-16 values.
- const String16 uri16 = request.readString16();
- String8 uri = String8(request.readString16());
-
- uri.toLower();
// To support local subtitle file only for now
- if (strncasecmp("file://", uri.string(), 7)) {
+ if (strncasecmp("file://", uri, 7)) {
return ERROR_UNSUPPORTED;
}
sp<DataSource> dataSource =
}
sp<TimedTextSource> source;
- if (uri.getPathExtension() == String8(".srt")) {
+ if (strcasecmp(mimeType, MEDIA_MIMETYPE_TEXT_SUBRIP)) {
source = TimedTextSource::CreateTimedTextSource(
dataSource, TimedTextSource::OUT_OF_BAND_FILE_SRT);
}
}
Mutex::Autolock autoLock(mLock);
+ mTextSourceVector.add(source);
+ return OK;
+}
- mTextOutOfBandVector.add(source);
- if (mState == UNINITIALIZED) {
- mState = STOPPED;
+status_t TimedTextDriver::addOutOfBandTextSource(
+ int fd, off64_t offset, size_t length, const char *mimeType) {
+ // Not supported yet. This requires DataSource::sniff to detect various text
+ // formats such as srt/smi/ttml.
+ return ERROR_UNSUPPORTED;
+}
+
+void TimedTextDriver::getTrackInfo(Parcel *parcel) {
+ Mutex::Autolock autoLock(mLock);
+ Vector<sp<TimedTextSource> >::const_iterator iter;
+ parcel->writeInt32(mTextSourceVector.size());
+ for (iter = mTextSourceVector.begin();
+ iter != mTextSourceVector.end(); ++iter) {
+ sp<MetaData> meta = (*iter)->getFormat();
+ if (meta != NULL) {
+ // There are two fields.
+ parcel->writeInt32(2);
+
+ // track type.
+ parcel->writeInt32(MEDIA_TRACK_TYPE_TIMEDTEXT);
+
+ const char *lang = "und";
+ meta->findCString(kKeyMediaLanguage, &lang);
+ parcel->writeString16(String16(lang));
+ } else {
+ parcel->writeInt32(0);
+ }
}
- return OK;
}
} // namespace android
(new AMessage(kWhatPause, id()))->post();
}
-void TimedTextPlayer::resume() {
- start();
-}
-
void TimedTextPlayer::seekToAsync(int64_t timeUs) {
sp<AMessage> msg = new AMessage(kWhatSeek, id());
msg->setInt64("seekTimeUs", timeUs);
if (obj != NULL) {
sp<ParcelEvent> parcelEvent;
parcelEvent = static_cast<ParcelEvent*>(obj.get());
- notifyListener(MEDIA_TIMED_TEXT, &(parcelEvent->parcel));
+ notifyListener(&(parcelEvent->parcel));
} else {
- notifyListener(MEDIA_TIMED_TEXT);
+ notifyListener();
}
doRead();
break;
mSource->stop();
}
mSource = static_cast<TimedTextSource*>(obj.get());
- mSource->start();
+ status_t err = mSource->start();
+ if (err != OK) {
+ notifyError(err);
+ break;
+ }
Parcel parcel;
- if (mSource->extractGlobalDescriptions(&parcel) == OK &&
- parcel.dataSize() > 0) {
- notifyListener(MEDIA_TIMED_TEXT, &parcel);
- } else {
- notifyListener(MEDIA_TIMED_TEXT);
+ err = mSource->extractGlobalDescriptions(&parcel);
+ if (err != OK) {
+ notifyError(err);
+ break;
}
+ notifyListener(&parcel);
break;
}
}
void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) {
int64_t timeUs = 0;
sp<ParcelEvent> parcelEvent = new ParcelEvent();
- mSource->read(&timeUs, &(parcelEvent->parcel), options);
- postTextEvent(parcelEvent, timeUs);
+ status_t err = mSource->read(&timeUs, &(parcelEvent->parcel), options);
+ if (err != OK) {
+ notifyError(err);
+ } else {
+ postTextEvent(parcelEvent, timeUs);
+ }
}
void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) {
int64_t positionUs, delayUs;
int32_t positionMs = 0;
listener->getCurrentPosition(&positionMs);
- positionUs = positionMs * 1000;
+ positionUs = positionMs * 1000ll;
if (timeUs <= positionUs + kAdjustmentProcessingTimeUs) {
delayUs = 0;
}
}
-void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) {
+void TimedTextPlayer::notifyError(int error) {
+ sp<MediaPlayerBase> listener = mListener.promote();
+ if (listener != NULL) {
+ listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error);
+ }
+}
+
+void TimedTextPlayer::notifyListener(const Parcel *parcel) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
if (parcel != NULL && (parcel->dataSize() > 0)) {
- listener->sendEvent(msg, 0, 0, parcel);
+ listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel);
} else { // send an empty timed text to clear the screen
- listener->sendEvent(msg);
+ listener->sendEvent(MEDIA_TIMED_TEXT);
}
}
}
void start();
void pause();
- void resume();
void seekToAsync(int64_t timeUs);
void setDataSource(sp<TimedTextSource> source);
void doRead(MediaSource::ReadOptions* options = NULL);
void onTextEvent();
void postTextEvent(const sp<ParcelEvent>& parcel = NULL, int64_t timeUs = -1);
- void notifyListener(int msg, const Parcel *parcel = NULL);
+ void notifyError(int error = 0);
+ void notifyListener(const Parcel *parcel = NULL);
DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer);
};
#include <binder/Parcel.h>
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
#include "TimedTextSRTSource.h"
#include "TextDescriptions.h"
TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource)
: mSource(dataSource),
+ mMetaData(new MetaData),
mIndex(0) {
}
if (err != OK) {
reset();
}
+ // TODO: Need to detect the language, because SRT doesn't give language
+ // information explicitly.
+ mMetaData->setCString(kKeyMediaLanguage, "");
return err;
}
void TimedTextSRTSource::reset() {
+ mMetaData->clear();
mTextVector.clear();
mIndex = 0;
}
return OK;
}
+sp<MetaData> TimedTextSRTSource::getFormat() {
+ return mMetaData;
+}
+
} // namespace android
int64_t *timeUs,
Parcel *parcel,
const MediaSource::ReadOptions *options = NULL);
+ virtual sp<MetaData> getFormat();
protected:
virtual ~TimedTextSRTSource();
private:
sp<DataSource> mSource;
+ sp<MetaData> mMetaData;
struct TextInfo {
int64_t endTimeUs;
return NULL;
}
+sp<MetaData> TimedTextSource::getFormat() {
+ return NULL;
+}
+
} // namespace android
namespace android {
class DataSource;
+class MetaData;
class Parcel;
class TimedTextSource : public RefBase {
virtual status_t extractGlobalDescriptions(Parcel *parcel) {
return INVALID_OPERATION;
}
+ virtual sp<MetaData> getFormat();
protected:
virtual ~TimedTextSource() { }