常见视频封装格式(2)

概述

日常生活中,看到的视频文件的后缀名如 .mp4、.avi、.rmvb 都是属于视频文件的封装格式。所谓封装格式,就是以怎样的方式将视频轨、音频轨、字幕轨等信息组合在一起。说得通俗点,视频轨相当于饭,而音频轨相当于菜,封装格式就是一个碗或者一个锅,是用来盛放饭菜的容器。

视频文件的封装格式并不影响视频的画质,影响视频画面质量的是视频的编码格式。

下面介绍常见的视频封装格式 - TS。

1 TS 简述

TS 全称是 MPEG2-TS,MPEG2-TS 是一种标准容器格式,传输与存储音视频、节目与系统信息协议数据,广泛应用于数字广播系统,我们日常数字机顶盒接收到的就是 TS(Transport Stream,传输流)流。

首先需要先分辨 TS 传输流中几个基本概念

  1. ES(Elementary Stream):基本流,直接从编码器出来的数据流,可以是编码过的音频、视频或其他连续码流
  2. PES(Packetized Elementary Streams):PES 流是 ES 流经过 PES 打包器处理后形成的数据流,在这个过程中完成了将 ES 流分组、加入包头信息 (PTS、DTS 等)操作。PES 流的基本单位是 PES 包,PES 包由包头和 payload 组成
  3. PS 流(Program Stream):节目流,PS 流由 PS 包组成,而一个 PS 包又由若干个 PES 包组成。一个 PS 包由具有同一时间基准的一个或多个 PES 包复合合成。
  4. TS 流(Transport Stream):传输流,TS 流由固定长度(188 字节)的 TS 包组成,TS 包是对 PES 包的另一种封装方式,同样由具有同一时间基准的一个或多个 PES 包复合合成。PS 包是不固定长度,而 TS 包为固定长度。

为便于传输,实现时分复用,基本流 ES 必须打包,就是将顺序连续、连续传输的数据流按一定的时间长度进行分割,分割的小段叫做包,因此打包也被称为分组。

MPEG-2 标准中,有两种不同的码流可以输出到信号,一种是节目码流(PS Program Stream),一种是传输流(TS Transport Stream)。

PS 流包结构长度可变,一旦某一 PS 包的同步信息丢失,接收机就无法确认下一包的同步位置,导致信息丢失,因此 PS 流适用于合理可靠的媒体,如光盘(DVD),PS 流的后缀名一般为 vob 或 evo。而 TS 传输流不同,TS 流的包结构为固定长度(一般为 188 字节),当传输误码破坏了某一 TS 包的同步信息时,接收机可在固定的位置检测它后面包中的同步信息,从而恢复同步,避免信息丢失,因此 TS 可适用于不太可靠的传输,即地面或卫星传播,TS 流的后缀一般为 ts、mpg、mpeg。

由于 TS 码流具有较强的抵抗传输误码的能力,因此目前在传输媒体中进行传输的 MPEG-2 码流基本上都采用了 TS

2 基本流程

2.1 TS 流形成过程

以电视数字信号为例:

1) 原始音视频数据经过压缩编码得到基本流 ES 流

生成的 ES 基本流比较大,并且只是 I、P、B 这些视频帧或音频取样信息。

2) 对 ES 基本流 进行打包生成 PES 流

通过 PES 打包器,首先对 ES 基本流进行分组打包,在每一个包前加上包头就构成了 PES 流的基本单位 —— PES 包,对视频 PES 来说,一般是一帧一个包,音频 PES 一般一个包不超过 64KB。

PES 包头信息中加入了 PTS、DTS 信息,用与音视频的同步。

3) 同一时间基准的 PES 包经过 TS 复用器生成 TS 传输包

PES 包的长度通常都是远大于 TS 包的长度,一个 PES 包必须由整数个 TS 包来传送,没装满的 TS 包由填充字节填充。PES 包进行 TS 复用时,往往一个 PES 包会分存到多个 TS 包中

将 PES 包内容分配到一系列固定长度的传输包(TS Packet)中。TS 流中 TS 传输包头加入了 PCR(节目参考时钟)与 PSI(节目专用信息),其中 PCR 用于解码器的系统时钟恢复。

PCR 时钟作用:我们知道,编码器中有一个系统时钟,用于产生指示音视频正确显示和解码的时间标签(DTS、PTS)。解码器在解码时首先利用 PCR 时钟重建与编码器同步的系统时钟,再利用 PES 流中的 DTS、PTS 进行音视频的同步。

4) 连续输出传输包形成具有恒定比特率的 MPEG-TS 流

2.2 TS 流解析过程

1) 从复用的 MPEG-TS 流中解析出 TS 包
2) 从 TS 包中获取 PAT 及节目对应的 PMT,解析获取音视频

首先简单了解一下什么是 PSI,后面会通过例子更详细的介绍。

PSI 是节目特定信息,该表格信息用来描述传送流的组成结构。PSI 信息由四种类型的表组成,包括节目关联表(PAT,Program Association Table)、节目映射表(PMT,Program Map Table)、条件接收表(CAT)、网络信息表(NIT)。PAT 与 PMT 两张表帮助我们找到该传送流中的所有节目与流,PAT 告诉我们,TS 流是由哪些节目组成,每个节目的节目映射表 PMT 的 PID 是什么,而 PMT 告诉我们,该节目由哪些流组成,每一路流的类型与 PID 是什么。CAT 与 NIT 暂时不考虑。

从图中 PAT 表中可以获取该 TS 流中包含哪些节目,并通过 PAT 表中具体节目的 PMT 表 PID 值(如节目 0 对应 17 PMT PID),找到该节目对应的 PMT 表,而有了 PMT 表我们就知道该节目有哪些流以及流的类型(视频、音频等),进而获取到音视频流对应的 PID。

3) 通过 PID 筛选出特定音视频流的 TS 包,并解析出 PES
4) 从 PES 中读取到 PTS/DTS,并从 PES 中解析出基本码流 ES
5) 将 ES 交给解码器解码

3 TS 格式

3.1 TS 包格式

TS 包主要由两部分组成,一个是 4 字节的包头信息,二是有效负载,另外由于每个包固定需要 188 字节,所以中间有可能需要插入自适应调整字段。其中有效负载包括 PSI(节目专用信息)、PES(打包后的基本流)及其他业务信息。

TS 包头 4 个字节(32 bit)语法结构如下:

序号 数据 占用 bit 说明
1 sync_byte 8 bit 同步字节
2 transport_error_indicator 1 bit 传输错误标识,1 表示错误则丢弃该包
3 payload_unit_start_indicator 1 bit 负载单元开始标志(packet不满188字节时需填充)
4 transport_priority 1 bit 优先级,1 表示优先级高
5 pid 13 bit Packet ID,包的标识,非常重要
6 transport_scrambling_control 2 bit 加密标志(00:未加密;其他表示已加密)
7 adaptation_field_control 2 bit 自适应控制
8 continuity_counter 4 bit 包递增计数器,范围 0 ~ 15

其中:

❤️. payload_unit_start_indicator> 负载单元开始标志:

对于有效负载为 PES 包或 PSI 数据的传输流包,它标识 PES 包头以及包含 PSI 特定信息表的头是否包含在该包中,具有含义如下:

  1. 一个 PES 包经过 TS 复用器会拆分成多个定长的 TS 包,那么怎么知道 PES 包从哪个 TS 包开始呢?payload_unit_start_indicator 的作用就在此,当 TS 包有效载荷包含 PES 包数据时,payload_unit_start_indicator 具有以下意义:1 指示此 TS 包的有效载荷随着 PES 包的首字节开始,0 指示在此 TS 包中无任何 PES 包将开始。
  2. 当 TS 包有效载荷包含 PSI 数据时,payload_unit_start_indicator 具有以下意义:若 TS 包承载 PSI 分段的首字节,则 payload_unit_start_indicator 值必为 1,指示此 TS 包的有效载荷的首字节承载 pointer_field。若 TS 包不承载 PSI 分段的首字节,则 payload_unit_start_indicator 值为 0,指示在此有效载荷中不存在 pointer_field

与 PES 包传输一样,通过 TS 包传送 PSI 表时,因为 TS 包的数据负载能力是有限的,即每个 TS 包的长度有限,所有当 PSI 表比较大时,PSI 被分成多段(section),再由多个 TS 包传输段。每一个段的长度不一,一个段的开始由 TS 包的有效负载中的 payload_unit_start_indicator 来标识

<7.pid > 包标识

PID 是识别 TS 包的重要参数,用来识别 TS 包承载的数据类型。下面是几种常见 PID 值代表的含义:

描述
0x0000 PAT,节目关联表
0x0001 CAT,条件访问表
0x0002 TSDT,传输流描述表

可以看到 PAT 表的 PID 值为 0x0000,而 PMT 节目映射表的的 PID 在 PAT 表中指定。

adaptation_field_control 自适应控制域

自适应控制,用来指示 TS 包 payload 是否跟随调整字段或有效净荷

调整字段值 描述
00 保留
01 没有调整字段,仅含 184 字节的有效净荷
10 没有有效净荷,仅含 183 字节的调整字段
11 0~182 字节调整字段后为有效净荷

3.2 PAT

PAT,Program Association Table 节目关联表,每个 TS 流对应一张,用来描述该 TS 流中有多少个节目。

  • TS 流中中,PAT 包重复实现,大约 0.5 秒出现一个,保证实时解码性
  • 表示 PAT 表的 TS 包 PID 值为 0,便于识别
  • PAT 的 payload 中传送特殊 PID 的列表,每个 PID 对应一个节目(对应一张 PMT 表)
  • PAT 表是 TS 流的基础,任何一个 TS 流解析寻找节目都是从 PAT 表开始查找

使用 UltraEdit 编辑器或 MPEG-TS anlyser 随便打开一个 ts 视频文件分析,第一个 TS 包就是 PAT 表:

取包头前 4 个字节分析:

0x47 0x40 0x00 0x30

根据前面的 TS 包头数据属性描述可以得到:

描述
sync_byte 0x47 固定同步字节
transport_error_indicator ‘0’ 没有传输错误
payload_unit_start_indicator ‘1’ PID 为 0,所以标识 PAT 表的开始
transport_priority ‘0’ 传输优先级低
PID 0x0000 PAT 表
transport_scrambling_control ‘00’ 未加密
adaptation_field_control ‘11’ 自适应控制,‘11’ 表示若干调整字节后为有效净荷
continuity_counte ‘0000’ 包递增控制器

字使用控制域 ‘11’ 可知 payload 184 字节中首先跟若干调整字节后才是有效净荷。payload 184 字节如下:

Payload:A6 00 FF .. FF 00 00 B0 0D 194D F7 00 00 00 01 E0 20 4F 8A E4 1E 

当 PAT 表 payload 包含调整字段时,payload 第一个字节为调整字节数,因此调整字节个数为 0xA6 = 166 字节,所以有效净荷如下(17 bytes):

00(前一个0x00不解析) 00 B0 0D 19 4D F7 00 00 00 01 E0 20 4F 8A E4 1E 

下面分析 PAT 表有效净荷的内容,前面说过 PAT 表中保存 TS 流中所有节目信息,这些信息以 PMT 表呈现,因此我们在处理 PAT 表中将需要将每一个节目 PMT 表 PID 值保存起来,以后会使用这些数据。

PAT 表可以用代码定义如下:

typedef struct TS_PAT_Program {    unsigned program_number       :16;   //节目号    unsigned program_map_PID      :13;   //节目映射表 PMT 表 PID} TS_PAT_Program;typedef struct TS_PAT {    unsigned table_id                     : 8; // 固定 0x00 ,标志是该表是 PAT    unsigned section_syntax_indicator     : 1; // 段语法标志位,固定为 1    unsigned zero                         : 1; // 0    unsigned reserved_1                   : 2; // 保留位    unsigned section_length               : 12; // 段长度,表示从下一个字段开始到CRC32(含)之间有用的字节数    unsigned transport_stream_id          : 16; // 该 TS 流 ID,区别于一个网络中其它多路复用的流    unsigned reserved_2                   : 2; // 保留位    unsigned version_number               : 5; // PAT 版本号    unsigned current_next_indicator       : 1; // 发送的 PAT 是当前有效还是下一个PAT有效    unsigned section_number               : 8; // 分段的号码。PAT 可能分为多段传输,第一段为 00,以后每个分段加1,最多可能有 256 个分段    unsigned last_section_number          : 8; //最后一个分段的号码     std::vector<TS_PAT_Program> program;        unsigned reserved_3                    : 3; // 保留位    unsigned network_PID                   : 13; // 网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID    unsigned CRC_32                        : 32; // CRC32 校验码} TS_PAT; 

对应有效净荷 00(开始标识不解析) 00 B0 0D 19 4D F7 00 00 00 01 E0 20 4F 8A E4 1E 解析如下:

0000 0000

字段名 具体值 说明
table_id 8 00000000 PAT 表 id 固定为 0
section_syntax_indicator 1 1 段语法标志位,固定为 1
zero 1 0 0 位,无说明
reserved 2 11 保留位
section_length 12 0x000D = 13 段长度为 13 个字节,transport_stream_id 到CRC_32(含)的字节总数
transport_stream_id 16 0x194D 流 ID 为 0x194D
reserved 2 11 保留位
version_number 5 11011 PAT 版本号
current_next_indicator 1 1 1 表示当前 PAT 表有效
section_number 8 0x00 分段号码
last_section_number 8 0x00 总分段号码
PMT 循环 start ==== ====
program_number 16 0x0001 节目号为 1
reserved 3 111 保留位为 111
program_map_PID 13 0x20 该节目 PMT ID 为 0x20
PMT 循环 end ==== ====
CRC_32 32 0x4F8AE41E CRT32 校验码

如果 program_number 节目号为 0,表示这是网络信息表 NIT(Network Information Table),所表示的 program_map_PID 含义为 netword_id。

通过上面实例分析,对 PAT 表有一定了解的基础上,下面 PAT 表解析代码就很容理解了:

HRESULT CTS_Stream_Parse::adjust_PAT_table(TS_PAT *packet, unsigned char* buffer) {      packet->table_id                    = buffer[0];      packet->section_syntax_indicator    = buffer[1] >> 7;      packet->zero                        = buffer[1] >> 6 & 0x1;      packet->reserved_1                  = buffer[1] >> 4 & 0x3;      packet->section_length              = (buffer[1] & 0x0F) << 8 | buffer[2];          packet->transport_stream_id           = buffer[3] << 8 | buffer[4];         packet->reserved_2                    = buffer[5] >> 6;      packet->version_number                = buffer[5] >> 1 &  0x1F;      packet->current_next_indicator        = (buffer[5] << 7) >> 7;      packet->section_number                = buffer[6];      packet->last_section_number           = buffer[7];         int len = 0;      len = 3 + packet->section_length;      packet->CRC_32                        = (buffer[len-4] & 0x000000FF) << 24    | (buffer[len-3] & 0x000000FF) << 16    | (buffer[len-2] & 0x000000FF) << 8     | (buffer[len-1] & 0x000000FF);          int n = 0;      for ( n = 0; n < packet->section_length - 12; n += 4 ) {          unsigned  program_num = buffer[8 + n ] << 8 | buffer[9 + n ];            packet->reserved_3           = buffer[10 + n ] >> 5;               packet->network_PID = 0x00;          if ( program_num == 0x00) {                packet->network_PID = (buffer[10 + n ] & 0x1F) << 8 | buffer[11 + n ];                 TS_network_Pid = packet->network_PID; //记录该TS流的网络PID                 TRACE(" packet->network_PID %0x /n/n", packet->network_PID );          } else {             TS_PAT_Program PAT_program;             PAT_program.program_map_PID = (buffer[10 + n] & 0x1F) << 8 | buffer[11 + n];             PAT_program.program_number = program_num;             packet->program.push_back( PAT_program );             TS_program.push_back( PAT_program ); //向全局PAT节目数组中添加PAT节目信息               }               }      return 0;  }  

3.3 PMT

PMT 表(Program Map Table,节目映射表),该表的 PID 是由 PAT 表 提供给出的。表征一路节目所有流信息。包含:

(1) 当前节目中包含的所有 Video 数据的PID(2) 当前节目中包含的所有 Audio 数据的PID(3) 与当前节目关联在一起的其他数据的 PID(如数字广播,数据通讯等使用的 PID)

如果 TS 流中包含多个节目,那么就会有多个 PMT 表。只要我们处理了PMT 表,那么我们就可以获取该节目中所有的流信息,如当前节目包含多少个 Video、多少个 Audio 和其他数据及每种数据对用的流 PID 分别是多少。

上面使用的 ts 流在 PAT 分析完我们知道,只有一路节目,且 PID 为 0x10 = 32,我们来看一下这个节目对应的 PMT 表。

同 PAT 一样的方法,先分析 TS 包头,取前 4 个字节分析:

0x47 0x40 0x20 0x30
描述
sync_byte 0x47 固定同步字节
transport_error_indicator ‘0’ 没有传输错误
payload_unit_start_indicator ‘1’ PID 为 0,所以标识 PAT 表的开始
transport_priority ‘0’ 传输优先级低
PID 0x0020 PAT 表映射的 PMT 表,PID 为 0x20 = 32
transport_scrambling_control ‘00’ 未加密
adaptation_field_control ‘11’ 自适应控制,‘11’ 表示若干调整字节后为有效净荷
continuity_counte ‘0000’ 包递增控制器

字使用控制域 ‘11’ 可知 payload 184 字节中首先跟若干调整字节后才是有效净荷。payload 184 字节如下:

Payload:.. .. .. .. .. 87 00 FF .. .. .. FF 00 02 B0 2C00 01 D5 00 00 E0 64 F0 00 06 E0 C8 F0 0F 05 0441 43 2D 33 6A 01 00 0A 04 00 00 00 00 1B E0 64F0 06 0A 04 00 00 00 00 7A 8C 6F D9 

当 PMT 表 payload 包含调整字段时,payload 第一个字节为调整字节数,因此调整字节个数为 0x87 = 135 字节,所以有效净荷如下(48 bytes):

00 02 B0 2C 00 01 D5 00 00 E0 64 F0 00 06 E0 C8 F0 0F 05 04 41 43 2D 33 6A 01 00 0A 04 00 00 00 00 1B E0 64 F0 06 0A 04 00 00 00 00 7A 8C 6F D9 

下面分析 PMT 表有效净荷的内容,MPT 表中保存该节目中所有流数据信息。

PMT 表可以用代码定义如下:

typedef struct TS_PMT_Stream {      unsigned stream_type                       : 8;  // 指示本节目流的类型      unsigned elementary_PID                    : 13; // 指示该流的 PID 值    unsigned ES_info_length                    : 12; // 前两位 bit 为 00,指示跟随其后的描述相关节目元素的字节数      unsigned descriptor;  } TS_PMT_Stream;   typedef struct TS_PMT {      unsigned table_id                        : 8; // 固定 0x02, 表示 PMT 表      unsigned section_syntax_indicator        : 1; // 段语法标志位,固定为 1     unsigned zero                            : 1; // 固定为 0    unsigned reserved_1                      : 2; // 保留位,为'11'      unsigned section_length                  : 12; // 段长度,表示从下一个字段开始到CRC32(含)之间有用的字节数    unsigned program_number                  : 16; // 当前 PMT 表映射到的节目号,1、2、3    unsigned reserved_2                      : 2; // 保留位,固定为 '11'      unsigned version_number                  : 5; // PMT 版本号码      unsigned current_next_indicator          : 1; // 发送的 PMT 表 是当前有效还是下一个 PMT 有效      unsigned section_number                  : 8; // 分段的号码。PMT 可能分为多段传输,第一段为 00,以后每个分段加1,最多可能有 256 个分段    unsigned last_section_number             : 8; // 分段数      unsigned reserved_3                      : 3; // 保留位,固定为 111      unsigned PCR_PID                         : 13; // 指明 TS 包的 PID 值,该 TS 包含有 PCR 同步时钟,      unsigned reserved_4                      : 4; // 预留位,固定为 1111     unsigned program_info_length             : 12; // 前 2 bit 为 00,该域指出跟随其后对节目信息的描述的字节数。            std::vector<TS_PMT_Stream> PMT_Stream;     unsigned reserved_5                      : 3; // 保留位,0x07      unsigned reserved_6                      : 4; // 保留位,0x0F      unsigned CRC_32                          : 32; // CRC32 校验码} TS_PMT;  

对应有效净荷解析如下:

00(第一个 00 开始标识,不解析)02 B0 2C 00 01 D5 00 00 E0 64 F0 00 06 E0 C8 F0 0F 05 04 41 43 2D 33 6A 01 00 0A 04 00 00 00 00 1B E0 64 F0 06 0A 04 00 00 00 00 7A 8C 6F D9 
字段名 具体值 说明
table_id 8 00000010 PMT 表 table id 固定为 0x02
section_syntax_indicator 1 1 段语法标志位,固定为 1
zero 1 0 0 位,无说明
reserved 2 11 保留位
section_length 12 0x002C = 44 段长度为 44 个字节,从 program_number 开始,到 CRC_32 (含)的字节总数
program_number 16 0x0001 节目号码,表示当前 PMT 表映射到的节目号
reserved 2 11 保留位
version_number 5 01010 PMT 版本号码,如果 PMT 内容有更新,则递增 1 通知解复用程序重新接收节目信息
current_next_indicator 1 1 1 表示当前 MPT 表有效
section_number 8 0x00 分段号码
last_section_number 8 0x00 总分段号码
reserved 3 111 保留位,固定为 111
PCR_PID 13 0x00064 PCR(节目参考时钟)所在 TS 分组的 PID 为 0x64 = 100
reserved 4 1111 保留位,固定为 1111
program_info_length 12 0x000 节目信息长度
节目流查找循环 start === ====
(NO1)stream_type 8 0x06 流类型,视频流、音频流或其他数据类型流
reserved 3 111 保留位为 111
elementary_PID 13 0xC8 该节目中包括的视频流、音频流等对应的 TS 分组的 PID 0xC8 = 200
reserved 4 1111 保留位,固定为 1111
ES_info_length 12 0x00F 该流描述信息长度,为 15 个字节
descriptor 15 * 8 15 个字节描述该流信息
(NO2)stream_type 8 0x1B 流类型,视频流、音频流或其他数据类型流
reserved 3 111 保留位为 111
elementary_PID 13 0x64 该节目中包括的视频流、音频流等对应的 TS 分组的 PID 0x64 = 100
reserved 4 1111 保留位,固定为 1111
ES_info_length 12 0x06 该流描述信息长度,为 6 个字节
descriptor 6 * 8 6 个字节描述该流信息
节目流查找循环 end === ====
CRC_32 32 0x7A8C6FD9 CRT32 校验码

解析完这张 PMT 表,我们发现两个流,一个流 stream_type 为 0x06 代表 AC3 音频流,一个流 stream_type 为 0x1B 表示 H264 视频流,其他 stream_type 代表流类型可以参考 ISO13838-1。

在对 PMT 表有一定了解的基础上,下面 PMT 表解析代码就很容理解了:

HRESULT CTS_Stream_Parse::adjust_PMT_table(TS_PMT *packet, unsigned char* buffer) {       packet->table_id                            = buffer[0];      packet->section_syntax_indicator            = buffer[1] >> 7;      packet->zero                                = buffer[1] >> 6 & 0x01;       packet->reserved_1                          = buffer[1] >> 4 & 0x03;      packet->section_length                      = (buffer[1] & 0x0F) << 8 | buffer[2];          packet->program_number                      = buffer[3] << 8 | buffer[4];    packet->reserved_2                          = buffer[5] >> 6;      packet->version_number                      = buffer[5] >> 1 & 0x1F;      packet->current_next_indicator              = (buffer[5] << 7) >> 7;      packet->section_number                      = buffer[6];      packet->last_section_number                 = buffer[7];      packet->reserved_3                          = buffer[8] >> 5;      packet->PCR_PID                             = ((buffer[8] << 8) | buffer[9]) & 0x1FFF;        packet->reserved_4                          = buffer[10] >> 4;      packet->program_info_length                 = (buffer[10] & 0x0F) << 8 | buffer[11];       // Get CRC_32      int len = 0;      len = packet->section_length + 3;          packet->CRC_32                = (buffer[len-4] & 0x000000FF) << 24          | (buffer[len-3] & 0x000000FF) << 16          | (buffer[len-2] & 0x000000FF) << 8          | (buffer[len-1] & 0x000000FF);         int pos = 12;      // program info descriptor      if (packet->program_info_length != 0)          pos += packet->program_info_length;          // Get stream type and PID          for ( ; pos <= (packet->section_length + 2) - 4; ) {          TS_PMT_Stream pmt_stream;          pmt_stream.stream_type =  buffer[pos];          packet->reserved_5  =   buffer[pos+1] >> 5;          pmt_stream.elementary_PID =  ((buffer[pos+1] << 8) | buffer[pos+2]) & 0x1FFF;          packet->reserved_6     =   buffer[pos+3] >> 4;          pmt_stream.ES_info_length =   (buffer[pos+3] & 0x0F) << 8 | buffer[pos+4];              pmt_stream.descriptor = 0x00;          if (pmt_stream.ES_info_length != 0) {              pmt_stream.descriptor = buffer[pos + 5];                           for (int len = 2; len <= pmt_stream.ES_info_length; len ++) {                  pmt_stream.descriptor = pmt_stream.descriptor<< 8 | buffer[pos + 4 + len];              }              pos += pmt_stream.ES_info_length;          }          pos += 5;          packet->PMT_Stream.push_back( pmt_stream );          TS_Stream_type.push_back( pmt_stream );      }      return 0;  }  
(0)

相关推荐