TS码流解析(一)TS Header

有一些音视频初学者想要了解TS码流结构,但网上资料不全或者讲得不够清楚,使得学习过程变得异常艰难。这一篇内容将对TS码流结构做详尽解析,争取做到通俗易懂,成为最好的TS码流解析文章。

本篇TS码流解析将会参照Android的ATSParser代码。

首先我们要知道一个标准的TS包一般有188字节,但是也有TS包是192字节或者204字节的情况。同一路TS流中,每个TS包大小是一致的,不会同时出现188字节和192字节的TS包。接下来的文章中,我们只解析标准188字节的TS包。

标准TS包包括一个4字节的包头、n字节自适应域(可选)和184-n字节的负载(可选),可选的意思是它可能在TS包中存在,也可能不存在。TS包的结构如图:

在这里插入图片描述

图中橙色部分是包头(header),蓝色部分是自适应域(adaptation field),黄色部分是负载(payload)。

1、TS Header解析

4字节包头共包含有8个字段,接下来依次解析包头字段:

  1. sync_byte:同步字节,值为0x47,关于同步字节有以下几点需要了解:
    • 同步字节表示一个新的TS包的开始,从同步字节开始往后的188字节为一个完整的TS包;
    • 根据以上特点,可以通过检查一个流的188个字节的整数倍位置是否为0x47来判断流是否为TS流;
    • 如果一段码流中间丢了一部分数据(丢失同步),可以利用同步字节重新找回同步,但是要注意,码流本身可能包含0x47的数据,所以当找到一个0x47时,需要向前和向后各跳跃188个字节,如果这些位置仍然时0x47,那么就可以确定这个0x47是同步字节,TS流已经对齐成功。
  2. transport_error_indicator:传输误差标志,该数据包在传输过程中是否出现错误,如果该位为1,说明应该立即丢弃该TS包;
  3. payload_unit_start_indicator:有效负载单元起始标志,
    • 值为1,表示该TS包中的负载数据是新的PES包或是PSI包的开始;
    • 值为0,表示该TS包中的负载数据是上一个PES包或是PSI包的续传数据。
    • 如何理解这个字段的意义:PES和PSI(后面了解这两个数据包的具体内容)数据包的大小往往是大于184字节的,要传输这些数据包肯定要把它们拆开装到多个TS包中,解析TS包时需要知道被拆开数据的头和尾,从而能够正确拼接成为完整的PES和PSI数据,这个标志位就是用来做拆开数据头的作用。
  4. transport_priority:传输优先级,指示TS包的优先级,这个字段不影响TS流解析,暂不做了解;
  5. PID:包ID(Packet Identifier),用于标识TS包中的负载是什么类型的数据。视频流、音频流、程序关联表(PAT),节目映射表(PMT),条件接入表(CAT)等信息都会被赋予一个唯一的PID,我们解析TS包时就是根据PID来解析的。在TS流中,有几个预先定义的、有特殊含义的PID值:
    • 0:TS包中的数据是PAT(Program Association Table),存储有节目信息;
    • 1:TS包中的数据是CAT(Conditional Access Table),存储有流的加密、条件访问信息;
    • 0x1FFF:空包,用于保证数据流的连续性;
  6. transport_scrambling_control:传输加扰控制,表示负载的PES数据是否经过了加密处理,要注意的是加密只会对PES数据进行加密,我们这里不会对加密数据解析做了解:
    • 00:不进行加扰处理;
    • 10:进行偶数密钥加扰;
    • 11:进行奇数密钥加扰。
  7. adaptation_field_control:自适应字段控制,用来指示该TS包是否含有自适应字段和负载数据,该字段的取值有四种:
    • 00:表示TS包既没有自适应字段,也没有负载数据,就是一个填充包;
    • 01:表示TS包没有自适应字段,只有负载数据;
    • 10:表示TS包只有自适应字段,没有负载数据;
    • 11:表示TS包既有自适应字段,也有负载数据;
  8. continuity_counter:连续计数器,用于检测传输流中的包是否丢失或者错序。连续计数器为4位,每当一个新的TS包(PID相同)被发送出去,该包中的连续计数器就会增加1,最大值为15,到达最大值后又从0开始。如果接收到一个包的连续计数器值比前一个包的值大于1,那么就可以判断出中间丢失了一个或者多个包。

以上就是TS Header中的内容,我们暂且先了解到这,里面一些陌生的术语我们后面再做解释。

2、Adaptation Field解析

继续往后解析,需要先看自适应字段控制的值,值为1011时TS Header后会跟有一个(自适应域)adaptation field,接下来对这个部分做解析:

adaptation field的结构如图:

在这里插入图片描述

结构图中的橙色部分是必选(肯定存在)的控制字段,绿色部分是可选,绿色部分的内容由控制字段的值所决定。接下来先看控制字段有哪些:

  1. adaptation Field Length:自适应域的长度,不包含这个字段本身;
  2. discontinuity indicator:非连续指示符,标记当前TS包的数据流在此处是否有间断,我们要注意的是discontinuity indicator和continuity counter是两个不同的概念,前者关注的是数据的连续性,后者关注的是TS包本身的连续性;
  3. random access indicator:随机存取指示符,标记当前包的是否为随机存取点,即解码器是否可以从此处开始解码,要注意的是该标志位为1不能说明当前帧为I帧;
  4. elementary stream priority indicator:基本流优先级指示符,记录当前的ES数据流的优先级;
  5. PCR flag:PCR(Program Clock Reference)标志位,值为1会在自适应域中插入6字节的PCR字段;
  6. OPCR flag:OPCR标志位,值为1会在自适应域中插入6字节的OPCR字段(暂不了解);
  7. Splicing point flag:剪接点标志,用于记录在传输流中插入或替换媒体数据的位置,广告插播的实现方式,(暂不了解);
  8. Transport private data flag:传输私有数据标志,值为1会在自适应域中插入私有数据字段(暂不了解);
  9. Adaptation field extension flag:自适应域扩展标志,值为1会在自适应域中插入自适应域扩展字段(暂不了解);

我们先假定控制字段的值都为1,看看后面自适应域的结构是怎么样的:

在这里插入图片描述

结构相当的复杂,不过也有规律可循的,这种结构一般包含三个部分:

  1. 整个字段的长度,这是相当关键的;
  2. 控制字段,指示存在哪些数据;
  3. 可选内容,其中可能又嵌套有控制字段和可选内容;

Adaptation Field是否存在取决于TS包的内容和结构:

  • 有效负载不足以填充完整个TS包时,利用Adaptation Field可以填充剩余的空间;
  • 需要提供额外的控制信息,如PCR(Program Clock Reference)、OPCR(Original Program Clock Reference)、随机访问指示、私有数据等时,这些信息会被放置在Adaptation Field中;
  • Adaptation field的长度要小于184字节,不能跨越多个包。

在这里了解一下PCR的作用,PCR是一个全局性的时间基准,在数字视频广播中维护音频和视频的同步,它和PTS有什么区别呢?

我们都知道PTS用于描述音频和视频帧的渲染时间,播放器根据PTS来决定何时将音频或视频帧送到显示设备或者音频设备进行播放,从而确保视频和音频之间的同步性。

PCR是数字电视系统中的概念,数字电视流中可能会有多个不同的节目同时在传输,每个节目流都有自己的PTS,但是这些PTS彼此之间并没有直接的关联,PCR就是提供一个基准用于多个节目流之间的同步。(我也没用过PCR,暂时无法举例)

3、代码解析

接下来看Android是如何解析TS Header和Adaptation Field的:

status_t ATSParser::parseTS(ABitReader *br, SyncEvent *event) {
// 1
unsigned sync_byte = br->getBits(8);
if (sync_byte != 0x47u) {
return BAD_VALUE;
}
// 2
if (br->getBits(1)) { // transport_error_indicator
return OK;
}
unsigned payload_unit_start_indicator = br->getBits(1);
MY_LOGV("transport_priority = %u", br->getBits(1));
unsigned PID = br->getBits(13);
unsigned transport_scrambling_control = br->getBits(2);
unsigned adaptation_field_control = br->getBits(2);
unsigned continuity_counter = br->getBits(4);
status_t err = OK;
// 3
unsigned random_access_indicator = 0;
if (adaptation_field_control == 2 || adaptation_field_control == 3) {
err = parseAdaptationField(br, PID, &random_access_indicator);
}
// 4
if (err == OK) {
if (adaptation_field_control == 1 || adaptation_field_control == 3) {
err = parsePID(br, PID, continuity_counter,
payload_unit_start_indicator,
transport_scrambling_control,
random_access_indicator,
event);
}
}
return err;
}
  1. 如果同步字节不是0x47则退出该包TS的解析;
  2. 如果传送误差指示符为1,说明该数据包在传输过程中是否出现错误,丢弃该包数据;
  3. 如果自适应字段控制值为2或3,说明TS包中包含有自适应域,需要解析部分内容;
  4. 解析完成自适域中的内容后,需要再查看是否还有负载数据,自适应字段控制值为1或3时,说明有负载数据,需要进一步解析负载数据;
status_t ATSParser::parseAdaptationField(
ABitReader *br, unsigned PID, unsigned *random_access_indicator) {
*random_access_indicator = 0;
// 1
unsigned adaptation_field_length = br->getBits(8);
if (adaptation_field_length > 0) {
if (adaptation_field_length * 8 > br->numBitsLeft()) {
return ERROR_MALFORMED;
}
unsigned discontinuity_indicator = br->getBits(1);
if (discontinuity_indicator) {
}
*random_access_indicator = br->getBits(1);
if (*random_access_indicator) {
}
unsigned elementary_stream_priority_indicator = br->getBits(1);
if (elementary_stream_priority_indicator) {
}
unsigned PCR_flag = br->getBits(1);
// 2
size_t numBitsRead = 4;
if (PCR_flag) {
if (adaptation_field_length * 8 < 52) {
return ERROR_MALFORMED;
}
br->skipBits(4);
uint64_t PCR_base = br->getBits(32);
PCR_base = (PCR_base << 1) | br->getBits(1);
br->skipBits(6);
unsigned PCR_ext = br->getBits(9);
size_t byteOffsetFromStartOfTSPacket =
(188 - br->numBitsLeft() / 8);
uint64_t PCR = PCR_base * 300 + PCR_ext;
uint64_t byteOffsetFromStart =
uint64_t(mNumTSPacketsParsed) * 188 + byteOffsetFromStartOfTSPacket;
for (size_t i = 0; i < mPrograms.size(); ++i) {
updatePCR(PID, PCR, byteOffsetFromStart);
}
numBitsRead += 52;
}
// 3
br->skipBits(adaptation_field_length * 8 - numBitsRead);
}
return OK;
}
  1. 解析自适应域的长度,长度不包含adaptation_field_length这个字段;
  2. 用numBitsRead记录已经读取的bit数量;
  3. 跳过不需要解析的数据;

到这里,TS包的包头和自适应域的解析就完成了,下一节我们来了解TS负载的解析。

posted @   青山渺渺  阅读(387)  评论(0编辑  收藏  举报  
编辑推荐:
· MySQL 优化利器 SHOW PROFILE 的实现原理
· 在.NET Core中使用异步多线程高效率的处理大量数据
· 聊一聊 C#前台线程 如何阻塞程序退出
· 几种数据库优化技巧
· 聊一聊坑人的 C# MySql.Data SDK
阅读排行:
· 为什么推荐在 .NET 中使用 YAML 配置文件
· 干掉EasyExcel!FastExcel初体验
· .NET 阻止系统睡眠/息屏
· .NET 9 中的 多级缓存 HybridCache
· 快手后端面试,被面试官秒挂了!
点击右上角即可分享
微信分享提示