From a3c9d82d0f898d30982840b48d1f00fd0d831e19 Mon Sep 17 00:00:00 2001 From: Insun Kang Date: Tue, 10 Jul 2012 21:18:22 +0900 Subject: Bugfix: Seek on SRT external track o Need to return closest upcoming subtitle when seeking time is within gap ranges. o Manually cherry-picked from GTV change. (commit e026a83dc88888b42da77eca739b8f09a0cd6dae) related-to-bug: 6796228 Change-Id: I02b7718a432d6b2f5575fa1a1a42bda6f04aa25b --- .../timedtext/TimedTextSRTSource.cpp | 54 ++--- .../libstagefright/timedtext/TimedTextSRTSource.h | 19 ++ media/libstagefright/timedtext/test/Android.mk | 27 +++ .../timedtext/test/TimedTextSRTSource_test.cpp | 224 +++++++++++++++++++++ 4 files changed, 298 insertions(+), 26 deletions(-) create mode 100644 media/libstagefright/timedtext/test/Android.mk create mode 100644 media/libstagefright/timedtext/test/TimedTextSRTSource_test.cpp diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.cpp b/media/libstagefright/timedtext/TimedTextSRTSource.cpp index 1f5d037..eac23ba 100644 --- a/media/libstagefright/timedtext/TimedTextSRTSource.cpp +++ b/media/libstagefright/timedtext/TimedTextSRTSource.cpp @@ -79,6 +79,10 @@ status_t TimedTextSRTSource::read( return OK; } +sp TimedTextSRTSource::getFormat() { + return mMetaData; +} + status_t TimedTextSRTSource::scanFile() { off64_t offset = 0; int64_t startTimeUs; @@ -155,18 +159,16 @@ status_t TimedTextSRTSource::getNextSubtitleInfo( while (needMoreData) { if ((err = readNextLine(offset, &data)) != OK) { if (err == ERROR_END_OF_STREAM) { - needMoreData = false; + break; } else { return err; } } - if (needMoreData) { - data.trim(); - if (data.empty()) { - // it's an empty line used to separate two subtitles - needMoreData = false; - } + data.trim(); + if (data.empty()) { + // it's an empty line used to separate two subtitles + needMoreData = false; } } info->textLen = *offset - info->offset; @@ -221,35 +223,24 @@ status_t TimedTextSRTSource::getText( if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { int64_t lastEndTimeUs = mTextVector.valueAt(mTextVector.size() - 1).endTimeUs; - int64_t firstStartTimeUs = mTextVector.keyAt(0); - if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) { + if (seekTimeUs < 0) { return ERROR_OUT_OF_RANGE; - } else if (seekTimeUs < firstStartTimeUs) { - mIndex = 0; + } else if (seekTimeUs >= lastEndTimeUs) { + return ERROR_END_OF_STREAM; } else { // binary search size_t low = 0; size_t high = mTextVector.size() - 1; size_t mid = 0; - int64_t currTimeUs; while (low <= high) { mid = low + (high - low)/2; - currTimeUs = mTextVector.keyAt(mid); - const int64_t diffTime = currTimeUs - seekTimeUs; - - if (diffTime == 0) { + int diff = compareExtendedRangeAndTime(mid, seekTimeUs); + if (diff == 0) { break; - } else if (diffTime < 0) { + } else if (diff < 0) { low = mid + 1; } else { - if ((high == mid + 1) - && (seekTimeUs < mTextVector.keyAt(high))) { - break; - } - if (mid < 1) { - break; - } high = mid - 1; } } @@ -260,6 +251,7 @@ status_t TimedTextSRTSource::getText( if (mIndex >= mTextVector.size()) { return ERROR_END_OF_STREAM; } + const TextInfo &info = mTextVector.valueAt(mIndex); *startTimeUs = mTextVector.keyAt(mIndex); *endTimeUs = info.endTimeUs; @@ -289,8 +281,18 @@ status_t TimedTextSRTSource::extractAndAppendLocalDescriptions( return OK; } -sp TimedTextSRTSource::getFormat() { - return mMetaData; +int TimedTextSRTSource::compareExtendedRangeAndTime(size_t index, int64_t timeUs) { + CHECK_LT(index, mTextVector.size()); + int64_t endTimeUs = mTextVector.valueAt(index).endTimeUs; + int64_t startTimeUs = (index > 0) ? + mTextVector.valueAt(index - 1).endTimeUs : 0; + if (timeUs >= startTimeUs && timeUs < endTimeUs) { + return 0; + } else if (endTimeUs <= timeUs) { + return -1; + } else { + return 1; + } } } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.h b/media/libstagefright/timedtext/TimedTextSRTSource.h index 9eeab39..598c200 100644 --- a/media/libstagefright/timedtext/TimedTextSRTSource.h +++ b/media/libstagefright/timedtext/TimedTextSRTSource.h @@ -70,6 +70,25 @@ private: status_t extractAndAppendLocalDescriptions( int64_t timeUs, const AString &text, Parcel *parcel); + // Compares the time range of the subtitle at index to the given timeUs. + // The time range of the subtitle to match with given timeUs is extended to + // [endTimeUs of the previous subtitle, endTimeUs of current subtitle). + // + // This compare function is used to find a next subtitle when read() is + // called with seek options. Note that timeUs within gap ranges, such as + // [200, 300) in the below example, will be matched to the closest future + // subtitle, [300, 400). + // + // For instance, assuming there are 3 subtitles in mTextVector, + // 0: [100, 200) ----> [0, 200) + // 1: [300, 400) ----> [200, 400) + // 2: [500, 600) ----> [400, 600) + // If the 'index' parameter contains 1, this function + // returns 0, if timeUs is in [200, 400) + // returns -1, if timeUs >= 400, + // returns 1, if timeUs < 200. + int compareExtendedRangeAndTime(size_t index, int64_t timeUs); + DISALLOW_EVIL_CONSTRUCTORS(TimedTextSRTSource); }; diff --git a/media/libstagefright/timedtext/test/Android.mk b/media/libstagefright/timedtext/test/Android.mk new file mode 100644 index 0000000..a5e7ba2 --- /dev/null +++ b/media/libstagefright/timedtext/test/Android.mk @@ -0,0 +1,27 @@ +LOCAL_PATH:= $(call my-dir) + +# ================================================================ +# Unit tests for libstagefright_timedtext +# See also /development/testrunner/test_defs.xml +# ================================================================ + +# ================================================================ +# A test for TimedTextSRTSource +# ================================================================ +include $(CLEAR_VARS) + +LOCAL_MODULE := TimedTextSRTSource_test + +LOCAL_MODULE_TAGS := eng tests + +LOCAL_SRC_FILES := TimedTextSRTSource_test.cpp + +LOCAL_C_INCLUDES := \ + $(TOP)/external/expat/lib \ + $(TOP)/frameworks/base/media/libstagefright/timedtext + +LOCAL_SHARED_LIBRARIES := \ + libexpat \ + libstagefright + +include $(BUILD_NATIVE_TEST) diff --git a/media/libstagefright/timedtext/test/TimedTextSRTSource_test.cpp b/media/libstagefright/timedtext/test/TimedTextSRTSource_test.cpp new file mode 100644 index 0000000..40e93c7 --- /dev/null +++ b/media/libstagefright/timedtext/test/TimedTextSRTSource_test.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012 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. + */ + +#define LOG_TAG "TimedTextSRTSource_test" +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace android { +namespace test { + +static const int kSecToUsec = 1000000; +static const int kSecToMsec = 1000; +static const int kMsecToUsec = 1000; + +/* SRT format (http://en.wikipedia.org/wiki/SubRip) + * Subtitle number + * Start time --> End time + * Text of subtitle (one or more lines) + * Blank lines + */ +static const char *kSRTString = + "1\n00:00:1,000 --> 00:00:1,500\n1\n\n" + "2\n00:00:2,000 --> 00:00:2,500\n2\n\n" + "3\n00:00:3,000 --> 00:00:3,500\n3\n\n" + "4\n00:00:4,000 --> 00:00:4,500\n4\n\n" + "5\n00:00:5,000 --> 00:00:5,500\n5\n\n" + // edge case : previos end time = next start time + "6\n00:00:5,500 --> 00:00:5,800\n6\n\n" + "7\n00:00:5,800 --> 00:00:6,000\n7\n\n" + "8\n00:00:6,000 --> 00:00:7,000\n8\n\n"; + +class SRTDataSourceStub : public DataSource { +public: + SRTDataSourceStub(const char *data, size_t size) : + mData(data), mSize(size) {} + virtual ~SRTDataSourceStub() {} + + virtual status_t initCheck() const { + return OK; + } + + virtual ssize_t readAt(off64_t offset, void *data, size_t size) { + if (offset >= mSize) return 0; + + ssize_t avail = mSize - offset; + if (avail > size) { + avail = size; + } + memcpy(data, mData + offset, avail); + return avail; + } + +private: + const char *mData; + size_t mSize; +}; + +class TimedTextSRTSourceTest : public testing::Test { +protected: + void SetUp() { + sp stub= new SRTDataSourceStub( + kSRTString, + strlen(kSRTString)); + mSource = new TimedTextSRTSource(stub); + mSource->start(); + } + + void CheckStartTimeMs(const Parcel& parcel, int32_t timeMs) { + int32_t intval; + parcel.setDataPosition(8); + parcel.readInt32(&intval); + EXPECT_EQ(timeMs, intval); + } + + void CheckDataEquals(const Parcel& parcel, const char* content) { + int32_t intval; + parcel.setDataPosition(16); + parcel.readInt32(&intval); + parcel.setDataPosition(24); + const char* data = (const char*) parcel.readInplace(intval); + + int32_t content_len = strlen(content); + EXPECT_EQ(content_len, intval); + EXPECT_TRUE(strncmp(data, content, content_len) == 0); + } + + sp mSource; + int64_t startTimeUs; + int64_t endTimeUs; + Parcel parcel; + AString subtitle; + status_t err; +}; + +TEST_F(TimedTextSRTSourceTest, readAll) { + for (int i = 1; i <= 5; i++) { + err = mSource->read(&startTimeUs, &endTimeUs, &parcel); + EXPECT_EQ(OK, err); + CheckStartTimeMs(parcel, i * kSecToMsec); + subtitle = StringPrintf("%d\n\n", i); + CheckDataEquals(parcel, subtitle.c_str()); + } + // read edge cases + err = mSource->read(&startTimeUs, &endTimeUs, &parcel); + EXPECT_EQ(OK, err); + CheckStartTimeMs(parcel, 5500); + subtitle = StringPrintf("6\n\n"); + CheckDataEquals(parcel, subtitle.c_str()); + + err = mSource->read(&startTimeUs, &endTimeUs, &parcel); + EXPECT_EQ(OK, err); + CheckStartTimeMs(parcel, 5800); + subtitle = StringPrintf("7\n\n"); + CheckDataEquals(parcel, subtitle.c_str()); + + err = mSource->read(&startTimeUs, &endTimeUs, &parcel); + EXPECT_EQ(OK, err); + CheckStartTimeMs(parcel, 6000); + subtitle = StringPrintf("8\n\n"); + CheckDataEquals(parcel, subtitle.c_str()); + + err = mSource->read(&startTimeUs, &endTimeUs, &parcel); + EXPECT_EQ(ERROR_END_OF_STREAM, err); +} + +TEST_F(TimedTextSRTSourceTest, seekTimeIsEarlierThanFirst) { + MediaSource::ReadOptions options; + options.setSeekTo(500, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ(1 * kSecToUsec, startTimeUs); + CheckStartTimeMs(parcel, 1 * kSecToMsec); +} + +TEST_F(TimedTextSRTSourceTest, seekTimeIsLaterThanLast) { + MediaSource::ReadOptions options; + options.setSeekTo(7 * kSecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(ERROR_END_OF_STREAM, err); + + options.setSeekTo(8 * kSecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(ERROR_END_OF_STREAM, err); +} + +TEST_F(TimedTextSRTSourceTest, seekTimeIsMatched) { + for (int i = 1; i <= 5; i++) { + MediaSource::ReadOptions options; + options.setSeekTo(i * kSecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ(i * kSecToUsec, startTimeUs); + + options.setSeekTo(i * kSecToUsec + 100, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ(i * kSecToUsec, startTimeUs); + } +} + +TEST_F(TimedTextSRTSourceTest, seekTimeInBetweenTwo) { + for (int i = 1; i <= 4; i++) { + MediaSource::ReadOptions options; + options.setSeekTo(i * kSecToUsec + 500000, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ((i + 1) * kSecToUsec, startTimeUs); + + options.setSeekTo(i * kSecToUsec + 600000, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ((i + 1) * kSecToUsec, startTimeUs); + } +} + +TEST_F(TimedTextSRTSourceTest, checkEdgeCase) { + MediaSource::ReadOptions options; + options.setSeekTo(5500 * kMsecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ(5500 * kMsecToUsec, startTimeUs); + subtitle = StringPrintf("6\n\n"); + CheckDataEquals(parcel, subtitle.c_str()); + + options.setSeekTo(5800 * kMsecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ(5800 * kMsecToUsec, startTimeUs); + subtitle = StringPrintf("7\n\n"); + CheckDataEquals(parcel, subtitle.c_str()); + + options.setSeekTo(6000 * kMsecToUsec, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mSource->read(&startTimeUs, &endTimeUs, &parcel, &options); + EXPECT_EQ(OK, err); + EXPECT_EQ(6000 * kMsecToUsec, startTimeUs); + subtitle = StringPrintf("8\n\n"); + CheckDataEquals(parcel, subtitle.c_str()); +} + +} // namespace test +} // namespace android -- cgit v1.1