diff options
-rw-r--r-- | include/media/mediaplayer.h | 23 | ||||
-rw-r--r-- | include/media/stagefright/MediaDefs.h | 1 | ||||
-rw-r--r-- | include/media/stagefright/timedtext/TimedTextDriver.h | 20 | ||||
-rw-r--r-- | media/java/android/media/MediaPlayer.java | 360 | ||||
-rw-r--r-- | media/libmediaplayerservice/StagefrightPlayer.cpp | 3 | ||||
-rw-r--r-- | media/libstagefright/AwesomePlayer.cpp | 145 | ||||
-rw-r--r-- | media/libstagefright/MediaDefs.cpp | 1 | ||||
-rw-r--r-- | media/libstagefright/include/AwesomePlayer.h | 5 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedText3GPPSource.cpp | 4 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedText3GPPSource.h | 1 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextDriver.cpp | 136 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextPlayer.cpp | 47 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextPlayer.h | 4 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextSRTSource.cpp | 11 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextSRTSource.h | 2 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextSource.cpp | 4 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextSource.h | 2 |
17 files changed, 576 insertions, 193 deletions
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h index 662dd13afca5..a68ab4e8edbd 100644 --- a/include/media/mediaplayer.h +++ b/include/media/mediaplayer.h @@ -120,6 +120,9 @@ enum media_info_type { MEDIA_INFO_NOT_SEEKABLE = 801, // New media metadata is available. MEDIA_INFO_METADATA_UPDATE = 802, + + //9xx + MEDIA_INFO_TIMED_TEXT_ERROR = 900, }; @@ -140,9 +143,6 @@ enum media_player_states { // 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 @@ -155,6 +155,23 @@ enum media_parameter_keys { 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 diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h index 2eb259e8b590..457d5d72e98c 100644 --- a/include/media/stagefright/MediaDefs.h +++ b/include/media/stagefright/MediaDefs.h @@ -54,6 +54,7 @@ extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG2PS; extern const char *MEDIA_MIMETYPE_CONTAINER_WVM; extern const char *MEDIA_MIMETYPE_TEXT_3GPP; +extern const char *MEDIA_MIMETYPE_TEXT_SUBRIP; } // namespace android diff --git a/include/media/stagefright/timedtext/TimedTextDriver.h b/include/media/stagefright/timedtext/TimedTextDriver.h index efedb6e9028e..b9752df5a7e6 100644 --- a/include/media/stagefright/timedtext/TimedTextDriver.h +++ b/include/media/stagefright/timedtext/TimedTextDriver.h @@ -37,26 +37,26 @@ public: ~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, }; @@ -67,11 +67,11 @@ private: // 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); }; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index f5fa877c97d3..d92180db62d5 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -24,6 +24,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.util.Log; @@ -455,6 +456,22 @@ import java.lang.ref.WeakReference; * <td>Successful invoke of this method in a valid state transfers the * object to the <em>Stopped</em> state. Calling this method in an * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>getTrackInfo </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>addExternalSource </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>selectTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>disableTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> * * </table> * @@ -572,6 +589,15 @@ public class MediaPlayer */ private native void _setVideoSurface(Surface surface); + /* Do not change these values (starting with INVOKE_ID) without updating + * their counterparts in include/media/mediaplayer.h! + */ + private static final int INVOKE_ID_GET_TRACK_INFO = 1; + private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2; + private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3; + private static final int INVOKE_ID_SELECT_TRACK = 4; + private static final int INVOKE_ID_UNSELECT_TRACK = 5; + /** * Create a request parcel which can be routed to the native media * player using {@link #invoke(Parcel, Parcel)}. The Parcel @@ -1312,23 +1338,6 @@ public class MediaPlayer /* Do not change these values (starting with KEY_PARAMETER) without updating * their counterparts in include/media/mediaplayer.h! */ - /* - * Key used in setParameter method. - * Indicates the index of the timed text track to be enabled/disabled. - * The index includes both the in-band and out-of-band timed text. - * The index should start from in-band text if any. Application can retrieve the number - * of in-band text tracks by using MediaMetadataRetriever::extractMetadata(). - * Note it might take a few hundred ms to scan an out-of-band text file - * before displaying it. - */ - private static final int KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000; - /* - * Key used in setParameter method. - * Used to add out-of-band timed text source path. - * Application can add multiple text sources by calling setParameter() with - * KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE multiple times. - */ - private static final int KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001; // There are currently no defined keys usable from Java with get*Parameter. // But if any keys are defined, the order must be kept in sync with include/media/mediaplayer.h. @@ -1373,7 +1382,7 @@ public class MediaPlayer return ret; } - /** + /* * Gets the value of the parameter indicated by key. * @param key key indicates the parameter to get. * @param reply value of the parameter to get. @@ -1435,7 +1444,7 @@ public class MediaPlayer */ public native void setAuxEffectSendLevel(float level); - /** + /* * @param request Parcel destinated to the media player. The * Interface token must be set to the IMediaPlayer * one to be routed correctly through the system. @@ -1445,7 +1454,7 @@ public class MediaPlayer private native final int native_invoke(Parcel request, Parcel reply); - /** + /* * @param update_only If true fetch only the set of metadata that have * changed since the last invocation of getMetadata. * The set is built using the unfiltered @@ -1462,7 +1471,7 @@ public class MediaPlayer boolean apply_filter, Parcel reply); - /** + /* * @param request Parcel with the 2 serialized lists of allowed * metadata types followed by the one to be * dropped. Each list starts with an integer @@ -1476,33 +1485,289 @@ public class MediaPlayer private native final void native_finalize(); /** - * @param index The index of the text track to be turned on. - * @return true if the text track is enabled successfully. + * Class for MediaPlayer to return each audio/video/subtitle track's metadata. + * + * {@see #getTrackInfo()}. + * {@hide} + */ + static public class TrackInfo implements Parcelable { + /** + * Gets the track type. + * @return TrackType which indicates if the track is video, audio, timed text. + */ + public int getTrackType() { + return mTrackType; + } + + /** + * Gets the language code of the track. + * @return a language code in either way of ISO-639-1 or ISO-639-2. + * When the language is unknown or could not be determined, + * ISO-639-2 language code, "und", is returned. + */ + public String getLanguage() { + return mLanguage; + } + + public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; + public static final int MEDIA_TRACK_TYPE_VIDEO = 1; + public static final int MEDIA_TRACK_TYPE_AUDIO = 2; + public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; + + final int mTrackType; + final String mLanguage; + + TrackInfo(Parcel in) { + mTrackType = in.readInt(); + mLanguage = in.readString(); + } + + /* + * No special parcel contents. Keep it as hide. + * {@hide} + */ + @Override + public int describeContents() { + return 0; + } + + /* + * {@hide} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mTrackType); + dest.writeString(mLanguage); + } + + /** + * Used to read a TrackInfo from a Parcel. + */ + static final Parcelable.Creator<TrackInfo> CREATOR + = new Parcelable.Creator<TrackInfo>() { + @Override + public TrackInfo createFromParcel(Parcel in) { + return new TrackInfo(in); + } + + @Override + public TrackInfo[] newArray(int size) { + return new TrackInfo[size]; + } + }; + + }; + + /** + * Returns an array of track information. + * + * @return Array of track info. null if an error occured. + * {@hide} + */ + // FIXME: It returns timed text tracks' information for now. Other types of tracks will be + // supported in future. + public TrackInfo[] getTrackInfo() { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_GET_TRACK_INFO); + invoke(request, reply); + TrackInfo trackInfo[] = reply.createTypedArray(TrackInfo.CREATOR); + return trackInfo; + } + + /* + * A helper function to check if the mime type is supported by media framework. + */ + private boolean availableMimeTypeForExternalSource(String mimeType) { + if (mimeType == MEDIA_MIMETYPE_TEXT_SUBRIP) { + return true; + } + return false; + } + + /* TODO: Limit the total number of external timed text source to a reasonable number. + */ + /** + * Adds an external source file. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param path The file path of external source file. + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(String path, String mimeType) + throws IllegalArgumentException { + if (!availableMimeTypeForExternalSource(mimeType)) { + throw new IllegalArgumentException("Illegal mimeType for external source: " + mimeType); + } + + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE); + request.writeString(path); + request.writeString(mimeType); + invoke(request, reply); + } + + /** + * Adds an external source file (Uri). + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play * {@hide} */ - public boolean enableTimedTextTrackIndex(int index) { - if (index < 0) { - return false; + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(Context context, Uri uri, String mimeType) + throws IOException, IllegalArgumentException { + String scheme = uri.getScheme(); + if(scheme == null || scheme.equals("file")) { + addExternalSource(uri.getPath(), mimeType); + return; } - return setParameter(KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX, index); + + AssetFileDescriptor fd = null; + try { + ContentResolver resolver = context.getContentResolver(); + fd = resolver.openAssetFileDescriptor(uri, "r"); + if (fd == null) { + return; + } + addExternalSource(fd.getFileDescriptor(), mimeType); + return; + } catch (SecurityException ex) { + } catch (IOException ex) { + } finally { + if (fd != null) { + fd.close(); + } + } + + // TODO: try server side. } + /* Do not change these values without updating their counterparts + * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp! + */ /** - * Enables the first timed text track if any. - * @return true if the text track is enabled successfully + * MIME type for SubRip (SRT) container. Used in {@link #addExternalSource()} APIs. * {@hide} */ - public boolean enableTimedText() { - return enableTimedTextTrackIndex(0); + public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; + + /** + * Adds an external source file (FileDescriptor). + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param mimeType A MIME type for the content. It can be null. + * <ul> + * <li>{@link #MEDIA_MIMETYPE_TEXT_SUBRIP} + * </ul> + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(FileDescriptor fd, String mimeType) + throws IllegalArgumentException { + // intentionally less than LONG_MAX + addExternalSource(fd, 0, 0x7ffffffffffffffL, mimeType); + } + + /** + * Adds an external timed text file (FileDescriptor). + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param offset the offset into the file where the data to be played starts, in bytes + * @param length the length in bytes of the data to be played + * @param mimeType A MIME type for the content. It can be null. + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(FileDescriptor fd, long offset, long length, String mimeType) + throws IllegalArgumentException { + if (!availableMimeTypeForExternalSource(mimeType)) { + throw new IllegalArgumentException("Illegal mimeType for external source: " + mimeType); + } + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE_FD); + request.writeFileDescriptor(fd); + request.writeLong(offset); + request.writeLong(length); + request.writeString(mimeType); + invoke(request, reply); } /** - * Disables timed text display. - * @return true if the text track is disabled successfully. + * Selects a track. + * <p> + * If a MediaPlayer is in invalid state, it throws exception. + * If a MediaPlayer is in Started state, the selected track will be presented immediately. + * If a MediaPlayer is not in Started state, it just marks the track to be played. + * </p> + * <p> + * In any valid state, if it is called multiple times on the same type of track (ie. Video, + * Audio, Timed Text), the most recent one will be chosen. + * </p> + * <p> + * The first audio and video tracks will be selected by default, even though this function is not + * called. However, no timed text track will be selected until this function is called. + * </p> * {@hide} */ - public boolean disableTimedText() { - return setParameter(KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX, -1); + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException, IllegalArgumentException). + public void selectTrack(int index) { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_SELECT_TRACK); + request.writeInt(index); + invoke(request, reply); + } + + /** + * Unselect a track. + * If the track identified by index has not been selected before, it throws an exception. + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException, IllegalArgumentException). + public void unselectTrack(int index) { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_UNSELECT_TRACK); + request.writeInt(index); + invoke(request, reply); } /** @@ -1641,14 +1906,14 @@ public class MediaPlayer // No real default action so far. return; case MEDIA_TIMED_TEXT: - if (mOnTimedTextListener != null) { - if (msg.obj == null) { - mOnTimedTextListener.onTimedText(mMediaPlayer, null); - } else { - if (msg.obj instanceof byte[]) { - TimedText text = new TimedText((byte[])(msg.obj)); - mOnTimedTextListener.onTimedText(mMediaPlayer, text); - } + if (mOnTimedTextListener == null) + return; + if (msg.obj == null) { + mOnTimedTextListener.onTimedText(mMediaPlayer, null); + } else { + if (msg.obj instanceof byte[]) { + TimedText text = new TimedText((byte[])(msg.obj)); + mOnTimedTextListener.onTimedText(mMediaPlayer, text); } } return; @@ -1663,7 +1928,7 @@ public class MediaPlayer } } - /** + /* * Called from native code when an interesting event happens. This method * just uses the EventHandler system to post the event back to the main app thread. * We use a weak reference to the original MediaPlayer object so that the native @@ -1977,6 +2242,13 @@ public class MediaPlayer */ public static final int MEDIA_INFO_METADATA_UPDATE = 802; + /** Failed to handle timed text track properly. + * @see android.media.MediaPlayer.OnInfoListener + * + * {@hide} + */ + public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; + /** * Interface definition of a callback to be invoked to communicate some * info and/or warning about the media or its playback. diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp index 052ebf04df2c..619c14908962 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.cpp +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -166,7 +166,8 @@ player_type StagefrightPlayer::playerType() { } 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) { diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 9e00bb349144..b4cb1abff94f 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -1114,7 +1114,7 @@ status_t AwesomePlayer::pause_l(bool at_eos) { modifyFlags(AUDIO_RUNNING, CLEAR); } - if (mFlags & TEXTPLAYER_STARTED) { + if (mFlags & TEXTPLAYER_INITIALIZED) { mTextDriver->pause(); modifyFlags(TEXT_RUNNING, CLEAR); } @@ -1268,32 +1268,6 @@ status_t AwesomePlayer::seekTo(int64_t timeUs) { 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); @@ -1315,7 +1289,7 @@ status_t AwesomePlayer::seekTo_l(int64_t timeUs) { seekAudioIfNecessary_l(); - if (mFlags & TEXTPLAYER_STARTED) { + if (mFlags & TEXTPLAYER_INITIALIZED) { mTextDriver->seekToAsync(mSeekTimeUs); } @@ -1691,8 +1665,8 @@ void AwesomePlayer::onVideoEvent() { } } - 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); } @@ -2232,20 +2206,6 @@ void AwesomePlayer::postAudioSeekComplete() { 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); @@ -2294,6 +2254,103 @@ status_t AwesomePlayer::getParameter(int key, Parcel *reply) { } } +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; } diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp index 444e823295a3..2549de68afb6 100644 --- a/media/libstagefright/MediaDefs.cpp +++ b/media/libstagefright/MediaDefs.cpp @@ -52,5 +52,6 @@ const char *MEDIA_MIMETYPE_CONTAINER_MPEG2PS = "video/mp2p"; 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 diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 4c7bfa684a13..06e946887f18 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -90,6 +90,7 @@ struct AwesomePlayer { 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); @@ -100,8 +101,6 @@ struct AwesomePlayer { 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: @@ -136,7 +135,7 @@ private: INCOGNITO = 0x8000, TEXT_RUNNING = 0x10000, - TEXTPLAYER_STARTED = 0x20000, + TEXTPLAYER_INITIALIZED = 0x20000, SLOW_DECODER_HACK = 0x40000, }; diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.cpp b/media/libstagefright/timedtext/TimedText3GPPSource.cpp index 4a3bfd323e74..c423ef063f30 100644 --- a/media/libstagefright/timedtext/TimedText3GPPSource.cpp +++ b/media/libstagefright/timedtext/TimedText3GPPSource.cpp @@ -110,4 +110,8 @@ status_t TimedText3GPPSource::extractGlobalDescriptions(Parcel *parcel) { return OK; } +sp<MetaData> TimedText3GPPSource::getFormat() { + return mSource->getFormat(); +} + } // namespace android diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.h b/media/libstagefright/timedtext/TimedText3GPPSource.h index dfc6418764a4..4ec3d8a13dfb 100644 --- a/media/libstagefright/timedtext/TimedText3GPPSource.h +++ b/media/libstagefright/timedtext/TimedText3GPPSource.h @@ -37,6 +37,7 @@ public: Parcel *parcel, const MediaSource::ReadOptions *options = NULL); virtual status_t extractGlobalDescriptions(Parcel *parcel); + virtual sp<MetaData> getFormat(); protected: virtual ~TimedText3GPPSource(); diff --git a/media/libstagefright/timedtext/TimedTextDriver.cpp b/media/libstagefright/timedtext/TimedTextDriver.cpp index c70870ea690f..ed8389401926 100644 --- a/media/libstagefright/timedtext/TimedTextDriver.cpp +++ b/media/libstagefright/timedtext/TimedTextDriver.cpp @@ -20,10 +20,13 @@ #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> @@ -47,24 +50,22 @@ TimedTextDriver::TimedTextDriver( } 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; } @@ -73,13 +74,10 @@ status_t TimedTextDriver::start() { 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(); @@ -88,10 +86,6 @@ status_t TimedTextDriver::start() { return OK; } -status_t TimedTextDriver::stop() { - return pause(); -} - // TODO: Test if pause() works properly. // Scenario 1: start - pause - resume // Scenario 2: start - seek @@ -101,8 +95,6 @@ status_t TimedTextDriver::pause() { switch (mState) { case UNINITIALIZED: return INVALID_OPERATION; - case STOPPED: - return OK; case PLAYING: mPlayer->pause(); break; @@ -115,45 +107,17 @@ status_t TimedTextDriver::pause() { 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; } @@ -165,6 +129,24 @@ status_t TimedTextDriver::setTimedTextTrackIndex(int32_t index) { 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 = @@ -173,25 +155,17 @@ status_t TimedTextDriver::addInBandTextSource( 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 = @@ -201,7 +175,7 @@ status_t TimedTextDriver::addOutOfBandTextSource( } 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); } @@ -211,12 +185,38 @@ status_t TimedTextDriver::addOutOfBandTextSource( } 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 diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index bda7b4639d4f..8717914f984c 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -56,10 +56,6 @@ void TimedTextPlayer::pause() { (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); @@ -104,9 +100,9 @@ void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) { 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; @@ -119,14 +115,18 @@ void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) { 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; } } @@ -141,8 +141,12 @@ void TimedTextPlayer::doSeekAndRead(int64_t seekTimeUs) { 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) { @@ -151,7 +155,7 @@ void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeU int64_t positionUs, delayUs; int32_t positionMs = 0; listener->getCurrentPosition(&positionMs); - positionUs = positionMs * 1000; + positionUs = positionMs * 1000ll; if (timeUs <= positionUs + kAdjustmentProcessingTimeUs) { delayUs = 0; @@ -167,13 +171,20 @@ void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeU } } -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); } } } diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h index 837beeb0ee3e..b869f187d819 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.h +++ b/media/libstagefright/timedtext/TimedTextPlayer.h @@ -40,7 +40,6 @@ public: void start(); void pause(); - void resume(); void seekToAsync(int64_t timeUs); void setDataSource(sp<TimedTextSource> source); @@ -68,7 +67,8 @@ private: 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); }; diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.cpp b/media/libstagefright/timedtext/TimedTextSRTSource.cpp index 3752d34469a5..c44a99b400df 100644 --- a/media/libstagefright/timedtext/TimedTextSRTSource.cpp +++ b/media/libstagefright/timedtext/TimedTextSRTSource.cpp @@ -21,8 +21,10 @@ #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" @@ -31,6 +33,7 @@ namespace android { TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource) : mSource(dataSource), + mMetaData(new MetaData), mIndex(0) { } @@ -42,10 +45,14 @@ status_t TimedTextSRTSource::start() { 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; } @@ -272,4 +279,8 @@ status_t TimedTextSRTSource::extractAndAppendLocalDescriptions( return OK; } +sp<MetaData> TimedTextSRTSource::getFormat() { + return mMetaData; +} + } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.h b/media/libstagefright/timedtext/TimedTextSRTSource.h index acc01f94d9f5..62710a01e735 100644 --- a/media/libstagefright/timedtext/TimedTextSRTSource.h +++ b/media/libstagefright/timedtext/TimedTextSRTSource.h @@ -39,12 +39,14 @@ public: 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; diff --git a/media/libstagefright/timedtext/TimedTextSource.cpp b/media/libstagefright/timedtext/TimedTextSource.cpp index ffbe1c3bca41..953f7b5f45d2 100644 --- a/media/libstagefright/timedtext/TimedTextSource.cpp +++ b/media/libstagefright/timedtext/TimedTextSource.cpp @@ -59,4 +59,8 @@ sp<TimedTextSource> TimedTextSource::CreateTimedTextSource( return NULL; } +sp<MetaData> TimedTextSource::getFormat() { + return NULL; +} + } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextSource.h b/media/libstagefright/timedtext/TimedTextSource.h index 06bae7150db4..93493426267c 100644 --- a/media/libstagefright/timedtext/TimedTextSource.h +++ b/media/libstagefright/timedtext/TimedTextSource.h @@ -25,6 +25,7 @@ namespace android { class DataSource; +class MetaData; class Parcel; class TimedTextSource : public RefBase { @@ -48,6 +49,7 @@ class TimedTextSource : public RefBase { virtual status_t extractGlobalDescriptions(Parcel *parcel) { return INVALID_OPERATION; } + virtual sp<MetaData> getFormat(); protected: virtual ~TimedTextSource() { } |