Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f5545b96 authored by James Dong's avatar James Dong Committed by Android (Google) Code Review
Browse files

Merge "Bugfix: Seek on SRT external track"

parents e65c8911 a3c9d82d
Loading
Loading
Loading
Loading
+28 −26
Original line number Original line Diff line number Diff line
@@ -79,6 +79,10 @@ status_t TimedTextSRTSource::read(
    return OK;
    return OK;
}
}


sp<MetaData> TimedTextSRTSource::getFormat() {
    return mMetaData;
}

status_t TimedTextSRTSource::scanFile() {
status_t TimedTextSRTSource::scanFile() {
    off64_t offset = 0;
    off64_t offset = 0;
    int64_t startTimeUs;
    int64_t startTimeUs;
@@ -155,20 +159,18 @@ status_t TimedTextSRTSource::getNextSubtitleInfo(
    while (needMoreData) {
    while (needMoreData) {
        if ((err = readNextLine(offset, &data)) != OK) {
        if ((err = readNextLine(offset, &data)) != OK) {
            if (err == ERROR_END_OF_STREAM) {
            if (err == ERROR_END_OF_STREAM) {
                needMoreData = false;
                break;
            } else {
            } else {
                return err;
                return err;
            }
            }
        }
        }


        if (needMoreData) {
        data.trim();
        data.trim();
        if (data.empty()) {
        if (data.empty()) {
            // it's an empty line used to separate two subtitles
            // it's an empty line used to separate two subtitles
            needMoreData = false;
            needMoreData = false;
        }
        }
    }
    }
    }
    info->textLen = *offset - info->offset;
    info->textLen = *offset - info->offset;
    return OK;
    return OK;
}
}
@@ -221,35 +223,24 @@ status_t TimedTextSRTSource::getText(
    if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
    if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
        int64_t lastEndTimeUs =
        int64_t lastEndTimeUs =
                mTextVector.valueAt(mTextVector.size() - 1).endTimeUs;
                mTextVector.valueAt(mTextVector.size() - 1).endTimeUs;
        int64_t firstStartTimeUs = mTextVector.keyAt(0);
        if (seekTimeUs < 0) {
        if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) {
            return ERROR_OUT_OF_RANGE;
            return ERROR_OUT_OF_RANGE;
        } else if (seekTimeUs < firstStartTimeUs) {
        } else if (seekTimeUs >= lastEndTimeUs) {
            mIndex = 0;
            return ERROR_END_OF_STREAM;
        } else {
        } else {
            // binary search
            // binary search
            size_t low = 0;
            size_t low = 0;
            size_t high = mTextVector.size() - 1;
            size_t high = mTextVector.size() - 1;
            size_t mid = 0;
            size_t mid = 0;
            int64_t currTimeUs;


            while (low <= high) {
            while (low <= high) {
                mid = low + (high - low)/2;
                mid = low + (high - low)/2;
                currTimeUs = mTextVector.keyAt(mid);
                int diff = compareExtendedRangeAndTime(mid, seekTimeUs);
                const int64_t diffTime = currTimeUs - seekTimeUs;
                if (diff == 0) {

                if (diffTime == 0) {
                    break;
                    break;
                } else if (diffTime < 0) {
                } else if (diff < 0) {
                    low = mid + 1;
                    low = mid + 1;
                } else {
                } else {
                    if ((high == mid + 1)
                        && (seekTimeUs < mTextVector.keyAt(high))) {
                        break;
                    }
                    if (mid < 1) {
                        break;
                    }
                    high = mid - 1;
                    high = mid - 1;
                }
                }
            }
            }
@@ -260,6 +251,7 @@ status_t TimedTextSRTSource::getText(
    if (mIndex >= mTextVector.size()) {
    if (mIndex >= mTextVector.size()) {
        return ERROR_END_OF_STREAM;
        return ERROR_END_OF_STREAM;
    }
    }

    const TextInfo &info = mTextVector.valueAt(mIndex);
    const TextInfo &info = mTextVector.valueAt(mIndex);
    *startTimeUs = mTextVector.keyAt(mIndex);
    *startTimeUs = mTextVector.keyAt(mIndex);
    *endTimeUs = info.endTimeUs;
    *endTimeUs = info.endTimeUs;
@@ -289,8 +281,18 @@ status_t TimedTextSRTSource::extractAndAppendLocalDescriptions(
    return OK;
    return OK;
}
}


sp<MetaData> TimedTextSRTSource::getFormat() {
int TimedTextSRTSource::compareExtendedRangeAndTime(size_t index, int64_t timeUs) {
    return mMetaData;
    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
}  // namespace android
+19 −0
Original line number Original line Diff line number Diff line
@@ -70,6 +70,25 @@ private:
    status_t extractAndAppendLocalDescriptions(
    status_t extractAndAppendLocalDescriptions(
            int64_t timeUs, const AString &text, Parcel *parcel);
            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);
    DISALLOW_EVIL_CONSTRUCTORS(TimedTextSRTSource);
};
};


+27 −0
Original line number Original line Diff line number Diff line
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)
+224 −0
Original line number Original line Diff line number Diff line
/*
 * 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