diff options
-rw-r--r-- | include/media/mediaplayer.h | 1 | ||||
-rw-r--r-- | media/libstagefright/Android.mk | 2 | ||||
-rw-r--r-- | media/libstagefright/AwesomePlayer.cpp | 26 | ||||
-rw-r--r-- | media/libstagefright/MPEG4Extractor.cpp | 2 | ||||
-rw-r--r-- | media/libstagefright/include/AwesomePlayer.h | 1 | ||||
-rw-r--r-- | media/libstagefright/timedtext/Android.mk | 16 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextParser.cpp | 257 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextParser.h | 75 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextPlayer.cpp (renamed from media/libstagefright/TimedTextPlayer.cpp) | 165 | ||||
-rw-r--r-- | media/libstagefright/timedtext/TimedTextPlayer.h (renamed from media/libstagefright/include/TimedTextPlayer.h) | 23 |
10 files changed, 527 insertions, 41 deletions
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h index cfa4cfd..50a378f 100644 --- a/include/media/mediaplayer.h +++ b/include/media/mediaplayer.h @@ -127,6 +127,7 @@ enum media_player_states { enum media_set_parameter_keys { KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000, + KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001, }; // ---------------------------------------------------------------------------- // ref-counted object for callbacks diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index f731dfb..8c4b274 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -50,7 +50,6 @@ LOCAL_SRC_FILES:= \ ThrottledSource.cpp \ TimeSource.cpp \ TimedEventQueue.cpp \ - TimedTextPlayer.cpp \ Utils.cpp \ VBRISeeker.cpp \ WAVExtractor.cpp \ @@ -89,6 +88,7 @@ LOCAL_STATIC_LIBRARIES := \ libstagefright_avcenc \ libstagefright_m4vh263enc \ libstagefright_matroska \ + libstagefright_timedtext \ libvpx \ libstagefright_mpeg2ts \ libstagefright_httplive \ diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 70053ea..3d270f8 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -29,9 +29,10 @@ #include "include/NuCachedSource2.h" #include "include/ThrottledSource.h" #include "include/MPEG2TSExtractor.h" -#include "include/TimedTextPlayer.h" #include "include/WVMExtractor.h" +#include "timedtext/TimedTextPlayer.h" + #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <media/IMediaPlayerService.h> @@ -1282,6 +1283,7 @@ void AwesomePlayer::setAudioSource(sp<MediaSource> source) { } void AwesomePlayer::addTextSource(sp<MediaSource> source) { + Mutex::Autolock autoLock(mTimedTextLock); CHECK(source != NULL); if (mTextPlayer == NULL) { @@ -2066,10 +2068,26 @@ void AwesomePlayer::postAudioSeekComplete_l() { } status_t AwesomePlayer::setParameter(int key, const Parcel &request) { - if (key == KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX) { - return setTimedTextTrackIndex(request.readInt32()); + 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 (mTextPlayer == NULL) { + mTextPlayer = new TimedTextPlayer(this, mListener, &mQueue); + } + + return mTextPlayer->setParameter(key, request); + } + default: + { + return ERROR_UNSUPPORTED; + } } - return ERROR_UNSUPPORTED; } status_t AwesomePlayer::getParameter(int key, Parcel *reply) { diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 6692809..98ac044 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -20,7 +20,7 @@ #include "include/MPEG4Extractor.h" #include "include/SampleTable.h" #include "include/ESDS.h" -#include "include/TimedTextPlayer.h" +#include "timedtext/TimedTextPlayer.h" #include <arpa/inet.h> diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 3c9a121..a9e8e95 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -231,6 +231,7 @@ private: int64_t mLastVideoTimeUs; TimedTextPlayer *mTextPlayer; + mutable Mutex mTimedTextLock; sp<WVMExtractor> mWVMExtractor; diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk new file mode 100644 index 0000000..9a6062c --- /dev/null +++ b/media/libstagefright/timedtext/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + TimedTextParser.cpp \ + TimedTextPlayer.cpp + +LOCAL_CFLAGS += -Wno-multichar +LOCAL_C_INCLUDES:= \ + $(JNI_H_INCLUDE) \ + $(TOP)/frameworks/base/media/libstagefright \ + $(TOP)/frameworks/base/include/media/stagefright/openmax + +LOCAL_MODULE:= libstagefright_timedtext + +include $(BUILD_STATIC_LIBRARY) diff --git a/media/libstagefright/timedtext/TimedTextParser.cpp b/media/libstagefright/timedtext/TimedTextParser.cpp new file mode 100644 index 0000000..0bada16 --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextParser.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TimedTextParser.h" +#include <media/stagefright/DataSource.h> + +namespace android { + +TimedTextParser::TimedTextParser() + : mDataSource(NULL), + mOffset(0), + mIndex(0) { +} + +TimedTextParser::~TimedTextParser() { + reset(); +} + +status_t TimedTextParser::init( + const sp<DataSource> &dataSource, FileType fileType) { + mDataSource = dataSource; + mFileType = fileType; + + status_t err; + if ((err = scanFile()) != OK) { + reset(); + return err; + } + + return OK; +} + +void TimedTextParser::reset() { + mDataSource.clear(); + mTextVector.clear(); + mOffset = 0; + mIndex = 0; +} + +// scan the text file to get start/stop time and the +// offset of each piece of text content +status_t TimedTextParser::scanFile() { + if (mFileType != OUT_OF_BAND_FILE_SRT) { + return ERROR_UNSUPPORTED; + } + + off64_t offset = 0; + int64_t startTimeUs; + bool endOfFile = false; + + while (!endOfFile) { + TextInfo info; + status_t err = getNextInSrtFileFormat(&offset, &startTimeUs, &info); + + if (err != OK) { + if (err == ERROR_END_OF_STREAM) { + endOfFile = true; + } else { + return err; + } + } else { + mTextVector.add(startTimeUs, info); + } + } + + if (mTextVector.isEmpty()) { + return ERROR_MALFORMED; + } + return OK; +} + +// read one line started from *offset and store it into data. +status_t TimedTextParser::readNextLine(off64_t *offset, AString *data) { + char character; + + data->clear(); + + while (true) { + ssize_t err; + if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) { + if (err == 0) { + return ERROR_END_OF_STREAM; + } + return ERROR_IO; + } + + (*offset) ++; + + // a line could end with CR, LF or CR + LF + if (character == 10) { + break; + } else if (character == 13) { + if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) { + if (err == 0) { // end of the stream + return OK; + } + return ERROR_IO; + } + + (*offset) ++; + + if (character != 10) { + (*offset) --; + } + break; + } + + data->append(character); + } + + return OK; +} + +/* SRT format: + * Subtitle number + * Start time --> End time + * Text of subtitle (one or more lines) + * Blank line + * + * .srt file example: + * 1 + * 00:00:20,000 --> 00:00:24,400 + * Altocumulus clouds occur between six thousand + * + * 2 + * 00:00:24,600 --> 00:00:27,800 + * and twenty thousand feet above ground level. + */ +status_t TimedTextParser::getNextInSrtFileFormat( + off64_t *offset, int64_t *startTimeUs, TextInfo *info) { + AString data; + status_t err; + if ((err = readNextLine(offset, &data)) != OK) { + return err; + } + + // to skip the first line + if ((err = readNextLine(offset, &data)) != OK) { + return err; + } + + int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2; + // the start time format is: hours:minutes:seconds,milliseconds + // 00:00:24,600 --> 00:00:27,800 + if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d", + &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) { + return ERROR_MALFORMED; + } + + *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll; + info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll; + if (info->endTimeUs <= *startTimeUs) { + return ERROR_MALFORMED; + } + + info->offset = *offset; + + bool needMoreData = true; + while (needMoreData) { + if ((err = readNextLine(offset, &data)) != OK) { + if (err == ERROR_END_OF_STREAM) { + needMoreData = false; + } else { + return err; + } + } + + if (needMoreData) { + data.trim(); + if (data.empty()) { + // it's an empty line used to separate two subtitles + needMoreData = false; + } + } + } + + info->textLen = *offset - info->offset; + + return OK; +} + +status_t TimedTextParser::getText( + AString *text, int64_t *startTimeUs, int64_t *endTimeUs, + const MediaSource::ReadOptions *options) { + Mutex::Autolock autoLock(mLock); + + text->clear(); + + int64_t seekTimeUs; + MediaSource::ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + int64_t lastEndTimeUs = mTextVector.valueAt(mTextVector.size() - 1).endTimeUs; + int64_t firstStartTimeUs = mTextVector.keyAt(0); + + if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) { + return ERROR_OUT_OF_RANGE; + } else if (seekTimeUs < firstStartTimeUs) { + mIndex = 0; + } else { + // binary search + ssize_t low = 0; + ssize_t high = mTextVector.size() - 1; + ssize_t mid = 0; + int64_t currTimeUs; + + while (low <= high) { + mid = low + (high - low)/2; + currTimeUs = mTextVector.keyAt(mid); + const int diff = currTimeUs - seekTimeUs; + + if (diff == 0) { + break; + } else if (diff < 0) { + low = mid + 1; + } else { + if ((high == mid + 1) + && (seekTimeUs < mTextVector.keyAt(high))) { + break; + } + high = mid - 1; + } + } + + mIndex = mid; + } + } + + TextInfo info = mTextVector.valueAt(mIndex); + *startTimeUs = mTextVector.keyAt(mIndex); + *endTimeUs = info.endTimeUs; + mIndex ++; + + char *str = new char[info.textLen]; + if (mDataSource->readAt(info.offset, str, info.textLen) < info.textLen) { + delete[] str; + return ERROR_IO; + } + + text->append(str, info.textLen); + delete[] str; + return OK; +} + +} // namespace android diff --git a/media/libstagefright/timedtext/TimedTextParser.h b/media/libstagefright/timedtext/TimedTextParser.h new file mode 100644 index 0000000..44774c2 --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextParser.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMED_TEXT_PARSER_H_ + +#define TIMED_TEXT_PARSER_H_ + +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/MediaSource.h> + +namespace android { + +class DataSource; + +class TimedTextParser : public RefBase { +public: + TimedTextParser(); + virtual ~TimedTextParser(); + + enum FileType { + OUT_OF_BAND_FILE_SRT = 1, + }; + + status_t getText(AString *text, int64_t *startTimeUs, int64_t *endTimeUs, + const MediaSource::ReadOptions *options = NULL); + status_t init(const sp<DataSource> &dataSource, FileType fileType); + void reset(); + +private: + Mutex mLock; + + sp<DataSource> mDataSource; + off64_t mOffset; + + struct TextInfo { + int64_t endTimeUs; + // the offset of the text in the original file + off64_t offset; + int textLen; + }; + + int mIndex; + FileType mFileType; + + // the key indicated the start time of the text + KeyedVector<int64_t, TextInfo> mTextVector; + + status_t getNextInSrtFileFormat( + off64_t *offset, int64_t *startTimeUs, TextInfo *info); + status_t readNextLine(off64_t *offset, AString *data); + + status_t scanFile(); + + DISALLOW_EVIL_CONSTRUCTORS(TimedTextParser); +}; + +} // namespace android + +#endif // TIMED_TEXT_PARSER_H_ + diff --git a/media/libstagefright/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index 1ac22cb..50bb16d 100644 --- a/media/libstagefright/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -25,9 +25,11 @@ #include <media/stagefright/MediaSource.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/FileSource.h> #include <media/stagefright/Utils.h> #include "include/AwesomePlayer.h" -#include "include/TimedTextPlayer.h" +#include "TimedTextPlayer.h" +#include "TimedTextParser.h" namespace android { @@ -59,13 +61,16 @@ TimedTextPlayer::TimedTextPlayer( const wp<MediaPlayerBase> &listener, TimedEventQueue *queue) : mSource(NULL), + mOutOfBandSource(NULL), mSeekTimeUs(0), mStarted(false), mTextEventPending(false), mQueue(queue), mListener(listener), mObserver(observer), - mTextBuffer(NULL) { + mTextBuffer(NULL), + mTextParser(NULL), + mTextType(kNoText) { mTextEvent = new TimedTextEvent(this, &TimedTextPlayer::onTextEvent); } @@ -75,22 +80,43 @@ TimedTextPlayer::~TimedTextPlayer() { } mTextTrackVector.clear(); + mTextOutOfBandVector.clear(); } status_t TimedTextPlayer::start(uint8_t index) { CHECK(!mStarted); - if (index >= mTextTrackVector.size()) { - LOGE("Incorrect text track index"); + if (index >= + mTextTrackVector.size() + mTextOutOfBandVector.size()) { + LOGE("Incorrect text track index: %d", index); return BAD_VALUE; } - mSource = mTextTrackVector.itemAt(index); + if (index < mTextTrackVector.size()) { // start an in-band text + mSource = mTextTrackVector.itemAt(index); - status_t err = mSource->start(); + status_t err = mSource->start(); - if (err != OK) { - return err; + if (err != OK) { + return err; + } + mTextType = kInBandText; + } else { // start an out-of-band text + OutOfBandText text = + mTextOutOfBandVector.itemAt(index - mTextTrackVector.size()); + + mOutOfBandSource = text.source; + TimedTextParser::FileType fileType = text.type; + + if (mTextParser == NULL) { + mTextParser = new TimedTextParser(); + } + + status_t err; + if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) { + return err; + } + mTextType = kOutOfBandText; } int64_t positionUs; @@ -127,15 +153,26 @@ void TimedTextPlayer::reset() { mSeeking = false; mStarted = false; - if (mTextBuffer != NULL) { - mTextBuffer->release(); - mTextBuffer = NULL; - } + if (mTextType == kInBandText) { + if (mTextBuffer != NULL) { + mTextBuffer->release(); + mTextBuffer = NULL; + } - if (mSource != NULL) { - mSource->stop(); - mSource.clear(); - mSource = NULL; + if (mSource != NULL) { + mSource->stop(); + mSource.clear(); + mSource = NULL; + } + } else { + if (mTextParser != NULL) { + mTextParser.clear(); + mTextParser = NULL; + } + if (mOutOfBandSource != NULL) { + mOutOfBandSource.clear(); + mOutOfBandSource = NULL; + } } } @@ -145,11 +182,14 @@ status_t TimedTextPlayer::seekTo(int64_t time_us) { mSeeking = true; mSeekTimeUs = time_us; + postTextEvent(); + return OK; } status_t TimedTextPlayer::setTimedTextTrackIndex(int32_t index) { - if (index >= (int)(mTextTrackVector.size())) { + if (index >= + (int)(mTextTrackVector.size() + mTextOutOfBandVector.size())) { return BAD_VALUE; } @@ -177,34 +217,54 @@ void TimedTextPlayer::onTextEvent() { MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); mSeeking = false; - if (mTextBuffer != NULL) { - mTextBuffer->release(); - mTextBuffer = NULL; + if (mTextType == kInBandText) { + if (mTextBuffer != NULL) { + mTextBuffer->release(); + mTextBuffer = NULL; + } + } else { + mText.clear(); } notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen } - if (mTextBuffer != NULL) { - uint8_t *tmp = (uint8_t *)(mTextBuffer->data()); - size_t len = (*tmp) << 8 | (*(tmp + 1)); + int64_t positionUs, timeUs; + mObserver->getPosition(&positionUs); - notifyListener(MEDIA_TIMED_TEXT, - tmp + 2, - len); + if (mTextType == kInBandText) { + if (mTextBuffer != NULL) { + uint8_t *tmp = (uint8_t *)(mTextBuffer->data()); + size_t len = (*tmp) << 8 | (*(tmp + 1)); - mTextBuffer->release(); - mTextBuffer = NULL; + notifyListener(MEDIA_TIMED_TEXT, + tmp + 2, + len); - } + mTextBuffer->release(); + mTextBuffer = NULL; - if (mSource->read(&mTextBuffer, &options) != OK) { - return; - } + } - int64_t positionUs, timeUs; - mObserver->getPosition(&positionUs); - mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs); + if (mSource->read(&mTextBuffer, &options) != OK) { + return; + } + + mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs); + } else { + if (mText.size() > 0) { + notifyListener(MEDIA_TIMED_TEXT, + mText.c_str(), + mText.size()); + mText.clear(); + } + + int64_t endTimeUs; + if (mTextParser->getText( + &mText, &timeUs, &endTimeUs, &options) != OK) { + return; + } + } //send the text now if (timeUs <= positionUs + 100000ll) { @@ -229,9 +289,44 @@ void TimedTextPlayer::cancelTextEvent() { } void TimedTextPlayer::addTextSource(sp<MediaSource> source) { + Mutex::Autolock autoLock(mLock); mTextTrackVector.add(source); } +status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { + Mutex::Autolock autoLock(mLock); + + if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) { + String8 uri = request.readString8(); + KeyedVector<String8, String8> headers; + + // To support local subtitle file only for now + if (strncasecmp("file://", uri.string(), 7)) { + return INVALID_OPERATION; + } + sp<DataSource> dataSource = + DataSource::CreateFromURI(uri, &headers); + status_t err = dataSource->initCheck(); + + if (err != OK) { + return err; + } + + OutOfBandText text; + text.source = dataSource; + if (uri.getPathExtension() == String8(".srt")) { + text.type = TimedTextParser::OUT_OF_BAND_FILE_SRT; + } else { + return ERROR_UNSUPPORTED; + } + + mTextOutOfBandVector.add(text); + + return OK; + } + return INVALID_OPERATION; +} + void TimedTextPlayer::notifyListener( int msg, const void *data, size_t size) { if (mListener != NULL) { diff --git a/media/libstagefright/include/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h index ac41b4f..590760b 100644 --- a/media/libstagefright/include/TimedTextPlayer.h +++ b/media/libstagefright/timedtext/TimedTextPlayer.h @@ -20,8 +20,10 @@ #include <media/MediaPlayerInterface.h> #include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/AString.h> #include "include/TimedEventQueue.h" +#include "TimedTextParser.h" namespace android { @@ -50,11 +52,19 @@ public: void addTextSource(sp<MediaSource> source); status_t setTimedTextTrackIndex(int32_t index); + status_t setParameter(int key, const Parcel &request); private: + enum TextType { + kNoText = 0, + kInBandText = 1, + kOutOfBandText = 2, + }; + Mutex mLock; sp<MediaSource> mSource; + sp<DataSource> mOutOfBandSource; bool mSeeking; int64_t mSeekTimeUs; @@ -72,8 +82,21 @@ private: MediaBuffer *mTextBuffer; Parcel mData; + // for in-band timed text Vector<sp<MediaSource> > mTextTrackVector; + // for out-of-band timed text + struct OutOfBandText { + TimedTextParser::FileType type; + sp<DataSource> source; + }; + Vector<OutOfBandText > mTextOutOfBandVector; + + sp<TimedTextParser> mTextParser; + AString mText; + + TextType mTextType; + void reset(); void onTextEvent(); |