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

Commit 76bb1200 authored by Andreas Huber's avatar Andreas Huber Committed by Android (Google) Code Review
Browse files

Merge "Provide better duration and seek accuracy if playing vorbis audio from...

Merge "Provide better duration and seek accuracy if playing vorbis audio from a non-streaming source."
parents 25c0619b c655790d
Loading
Loading
Loading
Loading
+125 −16
Original line number Original line Diff line number Diff line
@@ -73,6 +73,7 @@ struct MyVorbisExtractor {
    // Returns an approximate bitrate in bits per second.
    // Returns an approximate bitrate in bits per second.
    uint64_t approxBitrate();
    uint64_t approxBitrate();


    status_t seekToTime(int64_t timeUs);
    status_t seekToOffset(off64_t offset);
    status_t seekToOffset(off64_t offset);
    status_t readNextPacket(MediaBuffer **buffer);
    status_t readNextPacket(MediaBuffer **buffer);


@@ -90,6 +91,11 @@ private:
        uint8_t mLace[255];
        uint8_t mLace[255];
    };
    };


    struct TOCEntry {
        off64_t mPageOffset;
        int64_t mTimeUs;
    };

    sp<DataSource> mSource;
    sp<DataSource> mSource;
    off64_t mOffset;
    off64_t mOffset;
    Page mCurrentPage;
    Page mCurrentPage;
@@ -107,6 +113,8 @@ private:
    sp<MetaData> mMeta;
    sp<MetaData> mMeta;
    sp<MetaData> mFileMeta;
    sp<MetaData> mFileMeta;


    Vector<TOCEntry> mTableOfContents;

    ssize_t readPage(off64_t offset, Page *page);
    ssize_t readPage(off64_t offset, Page *page);
    status_t findNextPage(off64_t startOffset, off64_t *pageOffset);
    status_t findNextPage(off64_t startOffset, off64_t *pageOffset);


@@ -115,7 +123,9 @@ private:


    void parseFileMetaData();
    void parseFileMetaData();


    uint64_t findPrevGranulePosition(off64_t pageOffset);
    status_t findPrevGranulePosition(off64_t pageOffset, uint64_t *granulePos);

    void buildTableOfContents();


    MyVorbisExtractor(const MyVorbisExtractor &);
    MyVorbisExtractor(const MyVorbisExtractor &);
    MyVorbisExtractor &operator=(const MyVorbisExtractor &);
    MyVorbisExtractor &operator=(const MyVorbisExtractor &);
@@ -164,10 +174,7 @@ status_t OggSource::read(
    int64_t seekTimeUs;
    int64_t seekTimeUs;
    ReadOptions::SeekMode mode;
    ReadOptions::SeekMode mode;
    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
        off64_t pos = seekTimeUs * mExtractor->mImpl->approxBitrate() / 8000000ll;
        if (mExtractor->mImpl->seekToTime(seekTimeUs) != OK) {
        LOGV("seeking to offset %ld", pos);

        if (mExtractor->mImpl->seekToOffset(pos) != OK) {
            return ERROR_END_OF_STREAM;
            return ERROR_END_OF_STREAM;
        }
        }
    }
    }
@@ -237,7 +244,7 @@ status_t MyVorbisExtractor::findNextPage(


        if (!memcmp(signature, "OggS", 4)) {
        if (!memcmp(signature, "OggS", 4)) {
            if (*pageOffset > startOffset) {
            if (*pageOffset > startOffset) {
                LOGV("skipped %ld bytes of junk to reach next frame",
                LOGV("skipped %lld bytes of junk to reach next frame",
                     *pageOffset - startOffset);
                     *pageOffset - startOffset);
            }
            }


@@ -252,7 +259,10 @@ status_t MyVorbisExtractor::findNextPage(
// it (if any) and return its granule position.
// it (if any) and return its granule position.
// To do this we back up from the "current" page's offset until we find any
// To do this we back up from the "current" page's offset until we find any
// page preceding it and then scan forward to just before the current page.
// page preceding it and then scan forward to just before the current page.
uint64_t MyVorbisExtractor::findPrevGranulePosition(off64_t pageOffset) {
status_t MyVorbisExtractor::findPrevGranulePosition(
        off64_t pageOffset, uint64_t *granulePos) {
    *granulePos = 0;

    off64_t prevPageOffset = 0;
    off64_t prevPageOffset = 0;
    off64_t prevGuess = pageOffset;
    off64_t prevGuess = pageOffset;
    for (;;) {
    for (;;) {
@@ -262,9 +272,12 @@ uint64_t MyVorbisExtractor::findPrevGranulePosition(off64_t pageOffset) {
            prevGuess = 0;
            prevGuess = 0;
        }
        }


        LOGV("backing up %ld bytes", pageOffset - prevGuess);
        LOGV("backing up %lld bytes", pageOffset - prevGuess);


        CHECK_EQ(findNextPage(prevGuess, &prevPageOffset), (status_t)OK);
        status_t err = findNextPage(prevGuess, &prevPageOffset);
        if (err != OK) {
            return err;
        }


        if (prevPageOffset < pageOffset || prevGuess == 0) {
        if (prevPageOffset < pageOffset || prevGuess == 0) {
            break;
            break;
@@ -273,27 +286,64 @@ uint64_t MyVorbisExtractor::findPrevGranulePosition(off64_t pageOffset) {


    if (prevPageOffset == pageOffset) {
    if (prevPageOffset == pageOffset) {
        // We did not find a page preceding this one.
        // We did not find a page preceding this one.
        return 0;
        return UNKNOWN_ERROR;
    }
    }


    LOGV("prevPageOffset at %ld, pageOffset at %ld", prevPageOffset, pageOffset);
    LOGV("prevPageOffset at %lld, pageOffset at %lld",
         prevPageOffset, pageOffset);


    for (;;) {
    for (;;) {
        Page prevPage;
        Page prevPage;
        ssize_t n = readPage(prevPageOffset, &prevPage);
        ssize_t n = readPage(prevPageOffset, &prevPage);


        if (n <= 0) {
        if (n <= 0) {
            return 0;
            return (status_t)n;
        }
        }


        prevPageOffset += n;
        prevPageOffset += n;


        if (prevPageOffset == pageOffset) {
        if (prevPageOffset == pageOffset) {
            return prevPage.mGranulePosition;
            *granulePos = prevPage.mGranulePosition;
            return OK;
        }
        }
    }
    }
}
}


status_t MyVorbisExtractor::seekToTime(int64_t timeUs) {
    if (mTableOfContents.isEmpty()) {
        // Perform approximate seeking based on avg. bitrate.

        off64_t pos = timeUs * approxBitrate() / 8000000ll;

        LOGV("seeking to offset %lld", pos);
        return seekToOffset(pos);
    }

    size_t left = 0;
    size_t right = mTableOfContents.size();
    while (left < right) {
        size_t center = left / 2 + right / 2 + (left & right & 1);

        const TOCEntry &entry = mTableOfContents.itemAt(center);

        if (timeUs < entry.mTimeUs) {
            right = center;
        } else if (timeUs > entry.mTimeUs) {
            left = center + 1;
        } else {
            left = right = center;
            break;
        }
    }

    const TOCEntry &entry = mTableOfContents.itemAt(left);

    LOGV("seeking to entry %d / %d at offset %lld",
         left, mTableOfContents.size(), entry.mPageOffset);

    return seekToOffset(entry.mPageOffset);
}

status_t MyVorbisExtractor::seekToOffset(off64_t offset) {
status_t MyVorbisExtractor::seekToOffset(off64_t offset) {
    if (mFirstDataOffset >= 0 && offset < mFirstDataOffset) {
    if (mFirstDataOffset >= 0 && offset < mFirstDataOffset) {
        // Once we know where the actual audio data starts (past the headers)
        // Once we know where the actual audio data starts (past the headers)
@@ -311,7 +361,7 @@ status_t MyVorbisExtractor::seekToOffset(off64_t offset) {
    // We found the page we wanted to seek to, but we'll also need
    // We found the page we wanted to seek to, but we'll also need
    // the page preceding it to determine how many valid samples are on
    // the page preceding it to determine how many valid samples are on
    // this page.
    // this page.
    mPrevGranulePosition = findPrevGranulePosition(pageOffset);
    findPrevGranulePosition(pageOffset, &mPrevGranulePosition);


    mOffset = pageOffset;
    mOffset = pageOffset;


@@ -330,7 +380,8 @@ ssize_t MyVorbisExtractor::readPage(off64_t offset, Page *page) {
    uint8_t header[27];
    uint8_t header[27];
    if (mSource->readAt(offset, header, sizeof(header))
    if (mSource->readAt(offset, header, sizeof(header))
            < (ssize_t)sizeof(header)) {
            < (ssize_t)sizeof(header)) {
        LOGV("failed to read %d bytes at offset 0x%08lx", sizeof(header), offset);
        LOGV("failed to read %d bytes at offset 0x%016llx",
             sizeof(header), offset);


        return ERROR_IO;
        return ERROR_IO;
    }
    }
@@ -447,7 +498,8 @@ status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) {
                    packetSize);
                    packetSize);


            if (n < (ssize_t)packetSize) {
            if (n < (ssize_t)packetSize) {
                LOGV("failed to read %d bytes at 0x%08lx", packetSize, dataOffset);
                LOGV("failed to read %d bytes at 0x%016llx",
                     packetSize, dataOffset);
                return ERROR_IO;
                return ERROR_IO;
            }
            }


@@ -563,9 +615,66 @@ status_t MyVorbisExtractor::init() {


    mFirstDataOffset = mOffset + mCurrentPageSize;
    mFirstDataOffset = mOffset + mCurrentPageSize;


    off64_t size;
    uint64_t lastGranulePosition;
    if (!(mSource->flags() & DataSource::kIsCachingDataSource)
            && mSource->getSize(&size) == OK
            && findPrevGranulePosition(size, &lastGranulePosition) == OK) {
        // Let's assume it's cheap to seek to the end.
        // The granule position of the final page in the stream will
        // give us the exact duration of the content, something that
        // we can only approximate using avg. bitrate if seeking to
        // the end is too expensive or impossible (live streaming).

        int64_t durationUs = lastGranulePosition * 1000000ll / mVi.rate;

        mMeta->setInt64(kKeyDuration, durationUs);

        buildTableOfContents();
    }

    return OK;
    return OK;
}
}


void MyVorbisExtractor::buildTableOfContents() {
    off64_t offset = mFirstDataOffset;
    Page page;
    ssize_t pageSize;
    while ((pageSize = readPage(offset, &page)) > 0) {
        mTableOfContents.push();

        TOCEntry &entry =
            mTableOfContents.editItemAt(mTableOfContents.size() - 1);

        entry.mPageOffset = offset;
        entry.mTimeUs = page.mGranulePosition * 1000000ll / mVi.rate;

        offset += (size_t)pageSize;
    }

    // Limit the maximum amount of RAM we spend on the table of contents,
    // if necessary thin out the table evenly to trim it down to maximum
    // size.

    static const size_t kMaxTOCSize = 8192;
    static const size_t kMaxNumTOCEntries = kMaxTOCSize / sizeof(TOCEntry);

    size_t numerator = mTableOfContents.size();

    if (numerator > kMaxNumTOCEntries) {
        size_t denom = numerator - kMaxNumTOCEntries;

        size_t accum = 0;
        for (ssize_t i = mTableOfContents.size() - 1; i >= 0; --i) {
            accum += denom;
            if (accum >= numerator) {
                mTableOfContents.removeAt(i);
                accum -= numerator;
            }
        }
    }
}

status_t MyVorbisExtractor::verifyHeader(
status_t MyVorbisExtractor::verifyHeader(
        MediaBuffer *buffer, uint8_t type) {
        MediaBuffer *buffer, uint8_t type) {
    const uint8_t *data =
    const uint8_t *data =