summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/timedtext
diff options
context:
space:
mode:
authorInsun Kang <insun@google.com>2012-07-10 21:18:22 +0900
committerInsun Kang <insun@google.com>2012-07-20 11:49:01 +0900
commita3c9d82d0f898d30982840b48d1f00fd0d831e19 (patch)
treef11bc23bc6447b3b6b4410f733424db3cdecac04 /media/libstagefright/timedtext
parentea682976030a3930f6ee49b33b7e21abfc68174a (diff)
downloadframeworks_av-a3c9d82d0f898d30982840b48d1f00fd0d831e19.zip
frameworks_av-a3c9d82d0f898d30982840b48d1f00fd0d831e19.tar.gz
frameworks_av-a3c9d82d0f898d30982840b48d1f00fd0d831e19.tar.bz2
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
Diffstat (limited to 'media/libstagefright/timedtext')
-rw-r--r--media/libstagefright/timedtext/TimedTextSRTSource.cpp54
-rw-r--r--media/libstagefright/timedtext/TimedTextSRTSource.h19
-rw-r--r--media/libstagefright/timedtext/test/Android.mk27
-rw-r--r--media/libstagefright/timedtext/test/TimedTextSRTSource_test.cpp224
4 files changed, 298 insertions, 26 deletions
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<MetaData> 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<MetaData> 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 <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include <binder/Parcel.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaErrors.h>
+#include <utils/misc.h>
+
+#include <TimedTextSource.h>
+#include <TimedTextSRTSource.h>
+
+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<DataSource> 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<TimedTextSource> 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