MP4 seek状态 sample读取流程
(2013-01-03 20:24:59)
标签:
androidextractormp4杂谈 |
前面一篇博客详细剖析了正常情况下,按照sample的顺序从前往后读取sample数据的流程,最重要的过程在于对那几张表的充分利用,将前面那篇博客的内容搞明白后,接下来的内容其实也很简单,这篇博客主要跟踪,在seek状态下的,sample内容的读取,即任意时间点对应的sampleIndex的确定,这里还需要确定的是关键帧的sampleIndex,有了sampleIndex之后,一切就和前面的博客流程一致了,所以这篇博客的重点在于蓝色部分。
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;
}
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);
mCurrentSampleIndex =
syncSampleIndex;
------------注释3:好了得到了关键帧的sampleIndex,赋值为当前需要读取的sampleIndex。
if (mBuffer != NULL) {
mBuffer->release();
mBuffer =
NULL;
}
// fall through
}
函数后面的内容是上一篇博客讲解的,通过mCurrentSampleIndex获取offset,size和cts,略过。
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;
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);
按照升序排列所有的记录,后面的二分查找需要在一个有序的数组内进行。
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表示,得到这个值之后,我们大致的流程就是通过这个值,获取对应的sampleIndex,得到sampleIndex之后我们找离这个sampleIndex附近的某一关键帧的syncSampleIndex(附近的判定原则有三种,向前,向后,前后最近,对应三个case),得到syncSampleIndex之后,按照前一篇博客的内容,得到这个sampleIndex对应的offset和size,cts等信息,就可以送到解码器去播放。
下面就从代码上跟一下整个流程,仍然以MP4文件为例:
我们知道MP4文件的读取最后都会进入到MPEG4Extractor类的read函数中,在这个函数中首先就需要判断标志位,是否是seek的模式
status_t MPEG4Source::read(
#if 0
#endif
}
------------------------------注释1:通过时间戳找其对应的sampleIndex
status_t SampleTable::findSampleAtTime(
}
void SampleTable::buildSampleEntriesTable() {
}
通过上面的两个函数,以及二分查找,找到了和seekTimeUs最接近的一个sample的sampleIndex,下面需要找这个sampleIndex最接近的关键帧对应的syncSampleIndex
-----------------------------注释2:通过sampleIndex找syncSampleIndex
status_t SampleTable::findSyncSampleNear(
}
到此,找到了相对于seekTimeUs最近的一个关键帧对应的syncSampleIndex,将这个值赋值给mCurrentSampleIndex,然后去解析offset和size。
Over.