MP4 seek状态 sample读取流程

前面一篇博客详细剖析了正常情况下,按照sample的顺序从前往后读取sample数据的流程,最重要的过程在于对那几张表的充分利用,将前面那篇博客的内容搞明白后,接下来的内容其实也很简单,这篇博客主要跟踪,在seek状态下的,sample内容的读取,即任意时间点对应的sampleIndex的确定,这里还需要确定的是关键帧的sampleIndex,有了sampleIndex之后,一切就和前面的博客流程一致了,所以这篇博客的重点在于蓝色部分。

 

当我们在播放界面上做了一个拖动进度条的操作,放手的那一刻我们最终给代码传递了一个值:我们拖动到的视频的播放的时刻,这里以代码中的seekTimeUs表示,得到这个值之后,我们大致的流程就是通过这个值,获取对应的sampleIndex,得到sampleIndex之后我们找离这个sampleIndex附近的某一关键帧的syncSampleIndex(附近的判定原则有三种,向前,向后,前后最近,对应三个case),得到syncSampleIndex之后,按照前一篇博客的内容,得到这个sampleIndex对应的offset和size,cts等信息,就可以送到解码器去播放

 

下面就从代码上跟一下整个流程,仍然以MP4文件为例:

我们知道MP4文件的读取最后都会进入到MPEG4Extractor类的read函数中,在这个函数中首先就需要判断标志位,是否是seek的模式

 

status_t MPEG4Source::read(

        MediaBuffer **out, const ReadOptions *options) {

    Mutex::Autolock autoLock(mLock);

    int64_t seekTimeUs;

    ReadOptions::SeekMode mode;

    if (options && options->getSeekTo(&seekTimeUs, &mode)) { seek标志的判断

        uint32_t findFlags = 0;

        switch (mode) {

            case ReadOptions::SEEK_PREVIOUS_SYNC:

                findFlags = SampleTable::kFlagBefore;

                break;

            case ReadOptions::SEEK_NEXT_SYNC:

                findFlags = SampleTable::kFlagAfter;

                break;

            case ReadOptions::SEEK_CLOSEST_SYNC:

            case ReadOptions::SEEK_CLOSEST:

                findFlags = SampleTable::kFlagClosest;

                break;

            default:

                CHECK(!"Should not be here.");

                break;

        }

 

        uint32_t sampleIndex;

        status_t err = mSampleTable->findSampleAtTime(

                seekTimeUs * mTimescale / 1000000,

                &sampleIndex, findFlags);  ----------------------- 注释1

 

        if (mode == ReadOptions::SEEK_CLOSEST) {

            // We found the closest sample already, now we want the sync

            // sample preceding it (or the sample itself of course), even

            // if the subsequent sync sample is closer.

            findFlags = SampleTable::kFlagBefore;

        }

 

        uint32_t syncSampleIndex;

        if (err == OK) {

            err = mSampleTable->findSyncSampleNear(

                    sampleIndex, &syncSampleIndex, findFlags);------------------- 注释2

        }

 

        uint32_t sampleTime;

        if (err == OK) {

            err = mSampleTable->getMetaDataForSample(

                    sampleIndex, NULL, NULL, &sampleTime);

        }

        if (mode == ReadOptions::SEEK_CLOSEST) {

            targetSampleTimeUs = (sampleTime * 1000000ll) / mTimescale;

        }

 

#if 0

        uint32_t syncSampleTime;

        CHECK_EQ(OK, mSampleTable->getMetaDataForSample(

                    syncSampleIndex, NULL, NULL, &syncSampleTime));

        ALOGI("seek to time %lld us => sample at time %lld us, "

             "sync sample at time %lld us",

             seekTimeUs,

             sampleTime * 1000000ll / mTimescale,

             syncSampleTime * 1000000ll / mTimescale);

#endif

 

        mCurrentSampleIndex = syncSampleIndex; 

      ------------注释3:好了得到了关键帧的sampleIndex,赋值为当前需要读取的sampleIndex。

        if (mBuffer != NULL) {

            mBuffer->release();

            mBuffer = NULL;

        }

        // fall through

    }

     函数后面的内容是上一篇博客讲解的,通过mCurrentSampleIndex获取offset,size和cts,略过。

}

 

------------------------------注释1:通过时间戳找其对应的sampleIndex

 

status_t SampleTable::findSampleAtTime(

        uint32_t req_time, uint32_t *sample_index, uint32_t flags) {

    buildSampleEntriesTable();  通过time找sampleIndex会调用该函数初始化一下,可以跳到下面看这个函数的解析

    使用二分查找在有序的数组mSampleTimeEntries中查找元素对应的mCompositionTime和seekTimeUs最接近的那个sampleIndex

    uint32_t left = 0;

    uint32_t right = mNumSampleSizes;

    while (left < right) {

        uint32_t center = (left + right) / 2;

        uint32_t centerTime = mSampleTimeEntries[center].mCompositionTime;

        if (req_time < centerTime) {

            right = center;

        } else if (req_time > centerTime) {

            left = center + 1;

        } else {

            left = center;

            break;

        }

    }

 

    if (left == mNumSampleSizes) {

        if (flags == kFlagAfter) {

            return ERROR_OUT_OF_RANGE;

        }

        --left;

    }

    uint32_t closestIndex = left;

    根据不同的规则,对sampleIndex进行微调,找到最接近的sample

    switch (flags) {

        case kFlagBefore:

        {

            while (closestIndex > 0

                    && mSampleTimeEntries[closestIndex].mCompositionTime

                            > req_time) {

                --closestIndex;

            }

            break;

        }

 

        case kFlagAfter:

        {

            while (closestIndex + 1 < mNumSampleSizes

                    && mSampleTimeEntries[closestIndex].mCompositionTime

                            < req_time) {

                ++closestIndex;

            }

            break;

        }

        default:

        {

            CHECK(flags == kFlagClosest);

            if (closestIndex > 0) {

                // Check left neighbour and pick closest.

                uint32_t absdiff1 =

                    abs_difference(

                            mSampleTimeEntries[closestIndex].mCompositionTime,

                            req_time);

                uint32_t absdiff2 =

                    abs_difference(

                            mSampleTimeEntries[closestIndex - 1].mCompositionTime,

                            req_time);

                if (absdiff1 > absdiff2) {

                    closestIndex = closestIndex - 1;

                }

            }

            break;

        }

    }

    *sample_index = mSampleTimeEntries[closestIndex].mSampleIndex;  

    return OK;

}

 

 

void SampleTable::buildSampleEntriesTable() {

    Mutex::Autolock autoLock(mLock);

    if (mSampleTimeEntries != NULL) {

        return;

    }

    这个if功能类似于类中的singleton,只会初始化一次,初始化之后在进来就直接return了,提高了效率

    mSampleTimeEntries = new SampleTimeEntry[mNumSampleSizes];

    建立一个数组,数组大小为sample的个数,数组的内容为一个结构体,结构体的定义如下:

    ******************************************

    struct SampleTimeEntry {

        uint32_t mSampleIndex;   sample的索引号

        uint32_t mCompositionTime;  该sample对应的时间戳

    };

    ******************************************

    uint32_t sampleIndex = 0;

    uint32_t sampleTime = 0;

    通过stts表,将每一个sample的时间戳记录下来,保存在数组mSampleTimeEntries中,具体做法是如下:第一个for循环遍历所有stts表的记录,第二个for循环遍历stts表中一个记录中包含的所有的sample,每个sample的时间戳计算公式:mCompositionTime = 前面所有sample的时间和 + 当前sample的composition time(查ctts表得到)

    for (uint32_t i = 0; i < mTimeToSampleCount; ++i) {

        uint32_t n = mTimeToSample[2 * i];

        uint32_t delta = mTimeToSample[2 * i + 1];

        for (uint32_t j = 0; j < n; ++j) {

            if (sampleIndex < mNumSampleSizes) {

                // Technically this should always be the case if the file

                // is well-formed, but you know... there's (gasp) malformed

                // content out there.

                mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex;

                uint32_t compTimeDelta =

                    mCompositionDeltaLookup->getCompositionTimeOffset(

                            sampleIndex);

                mSampleTimeEntries[sampleIndex].mCompositionTime =

                    sampleTime + compTimeDelta;

            }

            ++sampleIndex;

            sampleTime += delta;

        }

    }

    qsort(mSampleTimeEntries, mNumSampleSizes, sizeof(SampleTimeEntry),

          CompareIncreasingTime);

    按照升序排列所有的记录,后面的二分查找需要在一个有序的数组内进行。

}

通过上面的两个函数,以及二分查找,找到了和seekTimeUs最接近的一个sample的sampleIndex,下面需要找这个sampleIndex最接近的关键帧对应的syncSampleIndex

 

-----------------------------注释2:通过sampleIndex找syncSampleIndex

 

status_t SampleTable::findSyncSampleNear(

        uint32_t start_sample_index, uint32_t *sample_index, uint32_t flags) {

    Mutex::Autolock autoLock(mLock);

    *sample_index = 0;

    没有stsc表的情况,默认所有的sample都是关键帧

    if (mSyncSampleOffset < 0) {

        // All samples are sync-samples.

        *sample_index = start_sample_index;

        return OK;

    }

    如果没有关键帧,则使用第一个sample

    if (mNumSyncSamples == 0) {

        *sample_index = 0;

        return OK;

    }

 

    又是二分查找法,查找关键帧数组中和制定的上面找出来的sampleIndex最近的syncSampleIndex

    uint32_t left = 0;

    uint32_t right = mNumSyncSamples;

    while (left < right) {

        uint32_t center = left + (right - left) / 2;

        uint32_t x = mSyncSamples[center];

        if (start_sample_index < x) {

            right = center;

        } else if (start_sample_index > x) {

            left = center + 1;

        } else {

            left = center;

            break;

        }

    }

    if (left == mNumSyncSamples) {

        if (flags == kFlagAfter) {

            ALOGE("tried to find a sync frame after the last one: %d", left);

            return ERROR_OUT_OF_RANGE;

        }

        left = left - 1;

    }

    // Now ssi[left] is the sync sample index just before (or at)

    // start_sample_index.

    // Also start_sample_index < ssi[left + 1], if left + 1 < mNumSyncSamples.

    uint32_t x = mSyncSamples[left];

    if (left + 1 < mNumSyncSamples) {

        uint32_t y = mSyncSamples[left + 1];

        // our sample lies between sync samples x and y.

        status_t err = mSampleIterator->seekTo(start_sample_index);

        if (err != OK) {

            return err;

        }

        uint32_t sample_time = mSampleIterator->getSampleTime();

        err = mSampleIterator->seekTo(x);

        if (err != OK) {

            return err;

        }

        uint32_t x_time = mSampleIterator->getSampleTime();

        err = mSampleIterator->seekTo(y);

        if (err != OK) {

            return err;

        }

        uint32_t y_time = mSampleIterator->getSampleTime();

        if (abs_difference(x_time, sample_time)

                > abs_difference(y_time, sample_time)) {

            // Pick the sync sample closest (timewise) to the start-sample.

            x = y;

            ++left;

        }

    }

    switch (flags) {

        case kFlagBefore:

        {

            if (x > start_sample_index) {

                CHECK(left > 0);

                x = mSyncSamples[left - 1];

                if (x > start_sample_index) {

                    // The table of sync sample indices was not sorted

                    // properly.

                    return ERROR_MALFORMED;

                }

            }

            break;

        }

        case kFlagAfter:

        {

            if (x < start_sample_index) {

                if (left + 1 >= mNumSyncSamples) {

                    return ERROR_OUT_OF_RANGE;

                }

                x = mSyncSamples[left + 1];

                if (x < start_sample_index) {

                    // The table of sync sample indices was not sorted

                    // properly.

                    return ERROR_MALFORMED;

                }

            }

            break;

        }

        default:

            break;

    }

    上面一段是微调,找到最接近的syncSampleIndex

    *sample_index = x;

    return OK;

}

 

到此,找到了相对于seekTimeUs最近的一个关键帧对应的syncSampleIndex,将这个值赋值给mCurrentSampleIndex,然后去解析offset和size。

 

Over.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值