有一些音视频初学者想要了解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负载的解析。

TS码流解析(一)TS Header的更多相关文章

  1. 关于ES、PES、PS/TS 码流

    一.基本概念 )ES   ES--Elementary  Streams  (原始流)是直接从编码器出来的数据流,可以是编码过的视频数据流(H.264,MJPEG等),音频数据流(AAC),或其他编码 ...

  2. 【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

    一 前言 最近在尝试学习一些视频相关的知识,随便一搜才知道原来国内有雷神这么一个真正神级的人物存在,尤其是在这里(传送门)看到他的感言更是对他膜拜不已,雷神这种无私奉献的精神应当被我辈发扬光大.那写这 ...

  3. H.264/H265码流解析

    H.264/H265码流解析 一.H.264码流解析 一个原始的H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成 一个原始的 ...

  4. 视音频数据处理入门:AAC音频码流解析

    ===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...

  5. 视音频数据处理入门:H.264视频码流解析

    ===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...

  6. go http 下载视频(TS码流文件)(推荐一个网站学习 go example)

    视频  http下载代码 dn.go(注意:代码很ugly,没怎么花时间) 总体感觉特别简单,网上看了下 net/http ,io这2个库的使用, 几分钟就写完了,感觉cpp 在做工具这块 开发效率的 ...

  7. H264码流解析及NALU

    ffmpeg 从mp4上提取H264的nalu http://blog.csdn.net/gavinr/article/details/7183499 639     /* bitstream fil ...

  8. h.264码流解析_一个SPS的nalu及获取视频的分辨率

    00 00 00 01 67 42 00 28 E9 00  A0 0B 77 FE 00 02 00 03 C4 80  00 00 03 00 80 00 00 1A 4D 88  10 94 0 ...

  9. TS流解析 一

    一 从TS流开始 数字电视机顶盒接收到的是一段段的码流,我们称之为TS(Transport Stream,传输流),每个TS流都携带一些信息,如Video.Audio以及我们需要学习的PAT.PMT等 ...

  10. TS流解析 四

    一 从TS流开始 数字电视机顶盒接收到的是一段段的码流,我们称之为TS(Transport Stream,传输流),每个TS流都携带一些信息,如Video.Audio以及我们需要学习的PAT.PMT等 ...

随机推荐

  1. keycloak~对架构提供的provider总结

    提供者目录 Provider Authenticator BaseDirectGrantAuthenticator AbstractFormAuthenticator AbstractUsername ...

  2. HarmonyOS应用性能与功耗云测试

    性能测试 性能测试主要验证HarmonyOS应用在华为真机设备上运行的性能问题,包括启动时长.界面显示.CPU占用和内存占用.具体性能测试项的详细说明请参考性能测试标准. 性能测试支持Phone和TV ...

  3. HDC2021技术分论坛:HarmonyOS低代码开发介绍

    作者:sunyuhui,wangxiaoyan,华为2012实验室软件IDE专家 什么是低代码开发?低代码开发主要特点有哪些?如何利用低代码开发原子化服务?本文带你一探究竟~ 一.什么是Harmony ...

  4. 重新整理数据结构与算法(c#)—— 算法套路分治算法[二十五]

    前言 有一个汉罗塔的游戏如下: 汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具. 大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘. 大梵天 ...

  5. Oracle 将字符中含有的字母或特殊字符去除并将字符串置换成数字

    将字符中含有的字母或特殊字符去除并将字符串置换成数字 将字符中含有的字母或特殊字符去除并将字符串置换成数字 to_number(nvl(TRANSLATE(u.scsqrbzl, 'qwertyuio ...

  6. 力扣620(MySQL)-有趣的电影(简单)

    题目: 某城市开了一家新的电影院,吸引了很多人过来看电影.该电影院特别注意用户体验,专门有个 LED显示板做电影推荐,上面公布着影评和相关电影描述. 作为该电影院的信息部主管,您需要编写一个 SQL查 ...

  7. 力扣500(java&python)-键盘行(简单)

    题目: 给你一个字符串数组 words ,只返回可以使用在 美式键盘 同一行的字母打印出来的单词.键盘如下图所示. 美式键盘 中: 第一行由字符 "qwertyuiop" 组成.第 ...

  8. 力扣6(java)-Z字形变换(中等)

    题目: 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下.从左到右进行 Z 字形排列. 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如 ...

  9. portainer安装,配置,使用

    Portainer安装 Portainer是Docker容器管理可视化界面,主要是可以通过可视化界面创建,管理Dockert容器,并且支持多个节点管理(免费版支持五个节点). Portainer官网地 ...

  10. 持续定义Saas模式云数据仓库+BI

    云数据仓库概述 今天和大家一起探讨一下我们Saas模式下云数据仓库加上商业智能BI能有什么新的东西出来.我们先来看一下云数据仓库的一些概述.预测到2025年, 全球数据增长至175ZB, 中国数据量增 ...