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

Commit c3ae937a authored by James Dong's avatar James Dong
Browse files

Add lost frame handling in AudioSource

- Also collect stats on lost audio frames instead of time spent on reading

Change-Id: I6380b143e4fbdcd894491aaae523331e90d0f04f
parent 31b5d548
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -57,11 +57,10 @@ private:

    bool mCollectStats;
    bool mTrackMaxAmplitude;
    int64_t mTotalReadTimeUs;
    int64_t mTotalReadBytes;
    int64_t mTotalReads;
    int64_t mStartTimeUs;
    int16_t mMaxAmplitude;
    int64_t mPrevSampleTimeUs;
    int64_t mNumLostFrames;

    MediaBufferGroup *mGroup;

+92 −45
Original line number Diff line number Diff line
@@ -26,8 +26,7 @@
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <cutils/properties.h>
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>

namespace android {

@@ -35,9 +34,8 @@ AudioSource::AudioSource(
        int inputSource, uint32_t sampleRate, uint32_t channels)
    : mStarted(false),
      mCollectStats(false),
      mTotalReadTimeUs(0),
      mTotalReadBytes(0),
      mTotalReads(0),
      mPrevSampleTimeUs(0),
      mNumLostFrames(0),
      mGroup(NULL) {

    LOGV("sampleRate: %d, channels: %d", sampleRate, channels);
@@ -110,10 +108,7 @@ status_t AudioSource::stop() {
    mStarted = false;

    if (mCollectStats) {
        LOGI("%lld reads: %.2f bps in %lld us",
                mTotalReads,
                (mTotalReadBytes * 8000000.0) / mTotalReadTimeUs,
                mTotalReadTimeUs);
        LOGI("Total lost audio frames: %lld", mNumLostFrames);
    }

    return OK;
@@ -129,67 +124,113 @@ sp<MetaData> AudioSource::getFormat() {
    return meta;
}

/*
 * Returns -1 if frame skipping request is too long.
 * Returns  0 if there is no need to skip frames.
 * Returns  1 if we need to skip frames.
 */
static int skipFrame(int64_t timestampUs,
        const MediaSource::ReadOptions *options) {

    int64_t skipFrameUs;
    if (!options || !options->getSkipFrame(&skipFrameUs)) {
        return 0;
    }

    if (skipFrameUs <= timestampUs) {
        return 0;
    }

    // Safe guard against the abuse of the kSkipFrame_Option.
    if (skipFrameUs - timestampUs >= 1E6) {
        LOGE("Frame skipping requested is way too long: %lld us",
            skipFrameUs - timestampUs);

        return -1;
    }

    LOGV("skipFrame: %lld us > timestamp: %lld us",
        skipFrameUs, timestampUs);

    return 1;

}

status_t AudioSource::read(
        MediaBuffer **out, const ReadOptions *options) {
    *out = NULL;
    ++mTotalReads;

    MediaBuffer *buffer;
    CHECK_EQ(mGroup->acquire_buffer(&buffer), OK);

    int err = 0;
    while (mStarted) {

        uint32_t numFramesRecorded;
        mRecord->getPosition(&numFramesRecorded);
        int64_t latency = mRecord->latency() * 1000;

        int64_t readTime = systemTime() / 1000;

        if (numFramesRecorded == 0) {
        if (numFramesRecorded == 0 && mPrevSampleTimeUs == 0) {
            // Initial delay
            if (mStartTimeUs > 0) {
                mStartTimeUs = readTime - mStartTimeUs;
                mStartTimeUs = systemTime() / 1000 - mStartTimeUs;
            } else {
                mStartTimeUs += latency;
                // Assume latency is constant.
                mStartTimeUs += mRecord->latency() * 1000;
            }
            mPrevSampleTimeUs = mStartTimeUs;
        }

        uint32_t sampleRate = mRecord->getSampleRate();

        // Insert null frames when lost frames are detected.
        int64_t timestampUs = mPrevSampleTimeUs;
        uint32_t numLostBytes = mRecord->getInputFramesLost() << 1;
#if 0
        // Simulate lost frames
        numLostBytes = ((rand() * 1.0 / RAND_MAX)) * kMaxBufferSize;
        numLostBytes &= 0xFFFFFFFE; // Alignment request

        // Reduce the chance to lose
        if (rand() * 1.0 / RAND_MAX >= 0.05) {
            numLostBytes = 0;
        }
#endif
        if (numLostBytes > 0) {
            // Not expect too many lost frames!
            CHECK(numLostBytes <= kMaxBufferSize);

        ssize_t n = 0;
            timestampUs += (1000000LL * numLostBytes >> 1) / sampleRate;
            CHECK(timestampUs > mPrevSampleTimeUs);
            if (mCollectStats) {
            n = mRecord->read(buffer->data(), buffer->size());
            int64_t endTime = systemTime() / 1000;
            mTotalReadTimeUs += (endTime - readTime);
            if (n >= 0) {
                mTotalReadBytes += n;
                mNumLostFrames += (numLostBytes >> 1);
            }
        } else {
            n = mRecord->read(buffer->data(), buffer->size());
            if ((err = skipFrame(timestampUs, options)) == -1) {
                buffer->release();
                return UNKNOWN_ERROR;
            } else if (err != 0) {
                continue;
            }
            memset(buffer->data(), 0, numLostBytes);
            buffer->set_range(0, numLostBytes);
            buffer->meta_data()->setInt64(kKeyTime, mPrevSampleTimeUs);
            mPrevSampleTimeUs = timestampUs;
            *out = buffer;
            return OK;
        }

        ssize_t n = mRecord->read(buffer->data(), buffer->size());
        if (n < 0) {
            buffer->release();
            buffer = NULL;

            return (status_t)n;
        }

        uint32_t sampleRate = mRecord->getSampleRate();
        int64_t timestampUs = (1000000LL * numFramesRecorded) / sampleRate +
                                 mStartTimeUs;
        int64_t skipFrameUs;
        if (!options || !options->getSkipFrame(&skipFrameUs)) {
            skipFrameUs = timestampUs;  // Don't skip frame
        }

        if (skipFrameUs > timestampUs) {
            // Safe guard against the abuse of the kSkipFrame_Option.
            if (skipFrameUs - timestampUs >= 1E6) {
                LOGE("Frame skipping requested is way too long: %lld us",
                    skipFrameUs - timestampUs);
        int64_t recordDurationUs = (1000000LL * n >> 1) / sampleRate;
        timestampUs += recordDurationUs;
        if ((err = skipFrame(timestampUs, options)) == -1) {
            buffer->release();
            return UNKNOWN_ERROR;
            }
            LOGV("skipFrame: %lld us > timestamp: %lld us, samples %d",
                skipFrameUs, timestampUs, numFramesRecorded);
        } else if (err != 0) {
            continue;
        }

@@ -197,7 +238,13 @@ status_t AudioSource::read(
            trackMaxAmplitude((int16_t *) buffer->data(), n >> 1);
        }

        buffer->meta_data()->setInt64(kKeyTime, timestampUs);
        buffer->meta_data()->setInt64(kKeyTime, mPrevSampleTimeUs);
        CHECK(timestampUs > mPrevSampleTimeUs);
        if (mNumLostFrames == 0) {
            CHECK_EQ(mPrevSampleTimeUs,
                mStartTimeUs + (1000000LL * numFramesRecorded) / sampleRate);
        }
        mPrevSampleTimeUs = timestampUs;
        LOGV("initial delay: %lld, sample rate: %d, timestamp: %lld",
                mStartTimeUs, sampleRate, timestampUs);