Android Stagefright MPEG4Extractor分析
视频播放的基本流程
播放器从DataSource获取媒体数据,通过Demuxer分离音视频轨道,分别送到相应的音视频解码器,最后将解码后的数据输出到音视频设备。
在Stagefright里,MediaExtractor即是用于分离音视频轨道的Demuxer。它是一个抽象类,声明了方法
sp
用于获取分离后的音视频流(track)。具体的逻辑则由不同的子类依据媒体文件容器的格式实现。
class MediaExtractor : public RefBase {
public:
static sp
const sp
virtual size_t countTracks() = 0;
virtual sp
virtual sp
size_t index, uint32_t flags = 0) = 0;
virtual sp
...
};
MPEG4Extractor是MediaExtractor的子类,用于解析MP4格式的媒体文件。
MP4文件是由一系列的Box构成的,Box的Header包含两个属性size,type,指明Box的大小和类型。Box的Body可以仅是包含其它的Box(容器Box),也可以仅包含数据(叶子Box)。这种结构与XML类似,不同的是XML的Tag是文本数据,MP4的Box是二进制数据。
主要包含如下Box类型,层次关系由缩进表示。
moov
mvhd
trak
tkhd
edts
elst
mdia
mdhd
minf
stbl
stsd
stco
co64
stts
stss
stsc
stsz
trak
trak
..
mdat
[data]
[data]
[...]
The moov atom contains instructions for playing the data in the file. The mdat atom contains the data that will be played.
Finding a Key Frame
Finding a key frame for a specified time in a movie is slightly more complicated than finding a sample for a specified time. The media handler must use the sync sample atom and the time-to-sample atom together in order to find a key frame.
The media handler performs the following steps:
在read方法里首先检查是否有seek option,如果有,则根据seek到的时间点找到对应的sample index,并找到该sample index之前的关键帧,设置为当前帧mCurrentSampleIndex
if (options && options->getSeekTo(&seekTimeUs, &mode)) {
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;
// 通过 time to sample box 找到与时间点对应的 sampleIndex
status_t err = mSampleTable->findSampleAtTime(
seekTimeUs * mTimescale / 1000000,
&sampleIndex, findFlags);
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) {
// 找到sampleIndex之前的一个关键帧syncSampleIndex
err = mSampleTable->findSyncSampleNear(
sampleIndex, &syncSampleIndex, findFlags);
}
if (err != OK) {
if (err == ERROR_OUT_OF_RANGE) {
// An attempt to seek past the end of the stream would
// normally cause this ERROR_OUT_OF_RANGE error. Propagating
// this all the way to the MediaPlayer would cause abnormal
// termination. Legacy behaviour appears to be to behave as if
// we had seeked to the end of stream, ending normally.
err = ERROR_END_OF_STREAM;
}
return err;
}
uint32_t sampleTime;
CHECK_EQ((status_t)OK, 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));
LOGI("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(因为seek之后送给解码器的第一帧需要是关键帧)
mCurrentSampleIndex = syncSampleIndex;
if (mBuffer != NULL) {
mBuffer->release();
mBuffer = NULL;
}
// fall through
}
调用SampleTable的getMetaDataForSample方法获取sample的文件偏移量和大小,以及解码时间戳,是否是关键帧。
off_t offset;
size_t size;
uint32_t dts;
bool isSyncSample;
bool newBuffer = false;
if (mBuffer == NULL) {
newBuffer = true;
// 获取当前 sample 的文件偏移量和大小,解码时间戳,是否关键帧
status_t err =
mSampleTable->getMetaDataForSample(
mCurrentSampleIndex, &offset, &size, &dts, &isSyncSample);
if (err != OK) {
return err;
}
err = mGroup->acquire_buffer(&mBuffer);
if (err != OK) {
CHECK(mBuffer == NULL);
return err;
}
}
有了offset和size之后就可以将未解码的数据读入buffer,推给解码器。Sample读取完成之后mCurrentSampleIndex加一,准备下一次read调用。
下面重点分析SampleTable#getMetaDataForSample的实现。该方法一开始调用SampleIterator#seekTo方法定位到给定的sample index,设置 mCurrentSampleOffset, mCurrentSampleSize.
//
// set the following instance variable
// * mCurrentSampleOffset
// * mCurrentSampleSize
status_t SampleIterator::seekTo(uint32_t sampleIndex) {
LOGV("seekTo(%d)", sampleIndex);
if (sampleIndex >= mTable->mNumSampleSizes) {
return ERROR_END_OF_STREAM;
}
if (mTable->mSampleToChunkOffset < 0
|| mTable->mChunkOffsetOffset < 0
|| mTable->mSampleSizeOffset < 0
|| mTable->mTimeToSampleCount == 0) {
return ERROR_MALFORMED;
}
if (mInitialized && mCurrentSampleIndex == sampleIndex) {
return OK;
}
// 如果 sampleIndex 不在当前的 sample-to-trunk block 里,则重置
if (!mInitialized || sampleIndex < mFirstChunkSampleIndex) {
reset();
}
// 3. Scans the sample-to-chunk atom to discover which chunk contains the sample in question.
if (sampleIndex >= mStopChunkSampleIndex) {
status_t err;
if ((err = findChunkRange(sampleIndex)) != OK) {
LOGE("findChunkRange failed");
return err;
}
}
CHECK(sampleIndex < mStopChunkSampleIndex);
// shaobin: get the current chunk index which contain the given sample
uint32_t chunk =
(sampleIndex - mFirstChunkSampleIndex) / mSamplesPerChunk
+ mFirstChunk;
if (!mInitialized || chunk != mCurrentChunkIndex) {
mCurrentChunkIndex = chunk;
// 4. Extracts the offset of current chunk from the chunk offset atom.
status_t err;
if ((err = getChunkOffset(chunk, &mCurrentChunkOffset)) != OK) {
LOGE("getChunkOffset return error");
return err;
}
mCurrentChunkSampleSizes.clear();
// the first sample index in current chunk
uint32_t firstChunkSampleIndex =
mFirstChunkSampleIndex
+ mSamplesPerChunk * (mCurrentChunkIndex - mFirstChunk);
for (uint32_t i = 0; i < mSamplesPerChunk; ++i) {
size_t sampleSize;
if ((err = getSampleSizeDirect(
firstChunkSampleIndex + i, &sampleSize)) != OK) {
LOGE("getSampleSizeDirect return error");
return err;
}
mCurrentChunkSampleSizes.push(sampleSize);
}
}
// the sample index offset within current chunk
uint32_t chunkRelativeSampleIndex =
(sampleIndex - mFirstChunkSampleIndex) % mSamplesPerChunk;
// 5. Finds the offset within the chunk and the sample's size by using the sample size atom.
mCurrentSampleOffset = mCurrentChunkOffset;
for (uint32_t i = 0; i < chunkRelativeSampleIndex; ++i) {
mCurrentSampleOffset += mCurrentChunkSampleSizes[i];
}
mCurrentSampleSize = mCurrentChunkSampleSizes[chunkRelativeSampleIndex];
if (sampleIndex < mTTSSampleIndex) {
mTimeToSampleIndex = 0;
mTTSSampleIndex = 0;
mTTSSampleTime = 0;
mTTSCount = 0;
mTTSDuration = 0;
}
status_t err;
// set mCurrentSampleTime with the given sampleIndex
if ((err = findSampleTime(sampleIndex, &mCurrentSampleTime)) != OK) {
LOGE("findSampleTime return error");
return err;
}
mCurrentSampleIndex = sampleIndex;
mInitialized = true;
return OK;
}
SampleIterator#findChunkRange用于在Sample-to-Chunk Box中查找包含给定sample index的Chunk集合,设置如下属性
| .... | mFirstChuck +========+ mFirstChunkSampleIndex |--------| |--------| mCurrentChunkIndex +========+ firstChunkSampleIndex |--------| |--------| sampleIndex(chunkRelativeSampleIndex) +========+ |--------| |--------| mStopChunk +========+ mStopChunkSampleIndex | .... | |
mSamplesPerChunk = 3;
mCurrentChunkIndex = mFirstChunk + (sampleIndex - mFirstChunkSampleIndex) / mSamplesPerChunk;
firstChunkSampleIndex = mFirstChunkSampleIndex + (mCurrentChunkIndex - mFirstChunk) * mSamplesPerChunk;
chunkRelativeSampleIndex = (sampleIndex - mFirstChunkSampleIndex) % mSamplesPerchunk;