全新系列文章已更新:
- Android Media Framework - 开篇
- Android Media Framework(一)OpenMAX 框架简介
- Android Media Framework(二)OpenMAX 类型阅读与分析
- Android Media Framework(三)OpenMAX API阅读与分析
- Android Media Framework(四)Non-Tunneled组件的状态转换与buffer分配过程分析
- Android Media Framework(五)Tunnel Mode
- Android Media Framework(六)插件式编程与OMXStore
- Android Media Framework(七)MediaCodecService
- Android Media Framework(八)OMXNodeInstance - Ⅰ
- Android Media Framework(九)OMXNodeInstance - Ⅱ
- Android Media Framework(十)OMXNodeInstance - Ⅲ
- Android Media Framework(十一)OMXNodeInstance - Ⅳ
- Android Media Framework(十二)OMXNodeInstance - Ⅴ
- Android Media Framework(十三)ACodec - Ⅰ
- Android Media Framework(十四)ACodec - Ⅱ
- Android Media Framework(十五)ACodec - Ⅲ
- Android Media Framework(十六)ACodec - Ⅳ
我们常说的音视频数据流在TS中被称为Elementary Stream(ES),也称为原始码流(裸流)。ES流本身不含有传输所需的所有信息,为了在传输过程中同时携带PTS(Presentation Time Stamp,显示时间戳)、DTS(Decoding Time Stamp,解码时间戳)等信息,会将ES流加上一个Header,封装成PES(Packet Elementary Stream)包。
1、PES包的接收
PES包往往比较长,所以需要分段传输,这样会有一个问题,接收了多个TS包后我们如何知道哪一包TS是PES的开头,哪一包又是PES的结尾?
TS header中有一个负载开始指示符(payload_unit_start_indicator,PUSI),表示当前TS包的负载是否属于新的一帧:
- PUSI为0,说明当前的PES包还未接收完成,负载属于当前PES包中的一部分内容,我们需要把这包的负载拼接到之前接收的负载之后;
- PUSI为1,说明前面一个PES包已经接收完成,当前接收的负载属于一个新的PES包,这时候我们可以来解析前面一个PES包,可以理解为,只有在后面一个PES包到达时我们才能知道前面一个PES包接收完成了。
这里有一点要说明,大多数情况下可以认为一个PES包就是一帧数据,对应着一个PTS和一个DTS,但是也有特殊的情况,一帧数据可能会跨多个PES包。
此外,TS包头中还有一个continuity_counter字段,我们可以用它判断PES包是否连续。PES包接收完成后我们就可以愉快的做解析工作了。
以下是Android的接收流程:
status_t ATSParser::Stream::parse(
unsigned continuity_counter,
unsigned payload_unit_start_indicator,
unsigned transport_scrambling_control,
unsigned random_access_indicator,
ABitReader *br, SyncEvent *event) {
if (mQueue == NULL) {
return OK;
}
// 1
if (mExpectedContinuityCounter >= 0
&& (unsigned)mExpectedContinuityCounter != continuity_counter) {
mPayloadStarted = false;
mPesStartOffsets.clear();
mBuffer->setRange(0, 0);
mSubSamples.clear();
mExpectedContinuityCounter = -1;
if (!payload_unit_start_indicator) {
return OK;
}
}
// 2
mExpectedContinuityCounter = (continuity_counter + 1) & 0x0f;
// 3
if (payload_unit_start_indicator) {
off64_t offset = (event != NULL) ? event->getOffset() : 0;
if (mPayloadStarted) {
status_t err = flush(event);
if (err != OK) {
ALOGW("Error (%08x) happened while flushing; we simply discard "
"the PES packet and continue.", err);
}
}
mPayloadStarted = true;
// There should be at most 2 elements in |mPesStartOffsets|.
while (mPesStartOffsets.size() >= 2) {
mPesStartOffsets.erase(mPesStartOffsets.begin());
}
mPesStartOffsets.push_back(offset);
}
if (!mPayloadStarted) {
return OK;
}
size_t payloadSizeBits = br->numBitsLeft();
if (payloadSizeBits % 8 != 0u) {
ALOGE("Wrong value");
return BAD_VALUE;
}
size_t neededSize = mBuffer->size() + payloadSizeBits / 8;
if (!ensureBufferCapacity(neededSize)) {
return NO_MEMORY;
}
// 4
memcpy(mBuffer->data() + mBuffer->size(), br->data(), payloadSizeBits / 8);
mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);
if (mScrambled) {
mSubSamples.push_back({payloadSizeBits / 8,
transport_scrambling_control, random_access_indicator});
}
return OK;
}
- 检查连续计数器是否符合预期,如果不符合预期则直接丢弃已经接收的PES包;
- 将预期的连续计数器加一;
- 如果PUSI为1,将mPayloadStarted置为1表示负载开始,等到下次PUSI为1时执行flush方法解析前一个PES包;
- 将数据存储到mBuffer中,如果已经有了数据就拼接到mBuffer后面;
2、PES包的解析
PES包结构整体如下:
一个完整的PES包有三个部分:
PES packet header
:PES包包头;Optional PES header
:包头可选字段,PES packet data
:PES包的主体,包含音频或视频数据;
PES packet header包含三个部分,
Packet Start Code Prefix
:PES包开始码,固定为0x000001;Stream ID
:标识了该PES包中包含的数据类型,例如是否是音频、视频或其他数据流;PES Packet Length
:表示整个PES包(包括header和data)的长度(字节为单位),如果它为零,则表示该PES包是一个无界长度的数据流;
这里要说明一下Stream ID的作用,我们在parse PES包时会不知道该包的数据到底是audio、video还是其他,所以PES包用Stream ID来做标记。不同的数据类型,也会影响是否存在Optional PES header,在网上其他文章中可能会默认所有TS包都包含下面会讲的flag,但是实际上只有部分PES包包含下面会讲的PTS/DTS flags等可选数据。
一般来说一个流就是一个Track,对应一个Stream Id。
接下来我们一起来看正常音频/视频的可选字段结构:
Markers
:固定的比特标记;Scrambling control
:指示是否对数据进行了加扰;Priority
:优先权标记;Data alignment indicator
:数据对齐标志。Copy right
:版权;Original or Copy
:原始或拷贝标记;PTS DTS flags
:表示是否会包含PTS或DTS等时间戳信息;ESCR flag
:表示是否包含ESCR(Elementary Stream Clock Reference)字段;ES rate flag
:表示是否包含ES码率字段;
扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。