ts 流基础(白话讲解).

----------------------------------------
author: hjjdebug
date:     2022年 09月 27日 星期二
----------------------------------------

ts 流就是188个字节构成的流数据.
先来点最简单的. ts 头部,4字节
ts 流是47开头的,以188字节为单位的打包流,由4字节包头及包体构成.
4字节第一个4字节为47.第2,3个字节为包id,占低位13个bit,故最多8192个包id, 第4各字节是计数器占低4bit.
包ID 是标明包身份的,以后慢慢会熟悉起来,连续计数器是用来判断是否有丢包的,正常同id的包是依次向上加1.
这就是最基本的认知了

要想搞清头部,再说说第2byte的前3bit,
最高位bit7, error bit, 表示该包是否出错,出错当然应该扔掉了.
次高位bit6, 叫syntax_indicatior, 该位以后会讲到,不同的包类型会对它分别置0或置1.
bit5, 是表示该包的重要性的.
再说说第4byte 的高4bit,
最高2bit是加扰控制, 00为未加扰,其它用户定义
次2bit为适配域,00:保留,01:无适配域有负载,10:有适配域无负载,11:有适配域有负载

于是用c语言可以定义出下面一个结构: ts_header
typedef struct
{
    uint8_t sync_byte; /*0x47*/
    uint16_t transport_error_indicator : 1;
    uint16_t payload_unit_start_indicator : 1;        //负载开始标记
    uint16_t transport_priority : 1;
    uint16_t PID : 13;
    uint8_t transport_scrambling_control : 2;
    uint8_t adaptation_field_control : 2;
    uint8_t continuity_counter : 4;  
} __attribute__((packed)) ts_header;
第5个字节是负载偏移长度,负载偏移为0就从下一个字节,第6个字节开始。

要想播出节目,ts流中除了要包含音频流,视频流这些基础流外,还要包含其它一些信息。
这些信息专业名词叫PSI/SI, (节目专有信息/服务信息),
那么什么是节目专有信息呢(program specific information)?
PAT,PMT,CAT,NIT这4个表是节目专有信息 , 节目播放所必须
服务信息是由其它表构成的,例如SDT,EIT,TDT等,这些是附加信息。

各种表一般都对应的是一个table表头,后面是表体.
table表头由1byte tableid,及2bytes 长度构成,长度占低12位,意味着表不会超过4K大小
后边的数据归入本表.
那长度前面的4bit 又是什么意思呢? 用途不大,看后面定义. 这样讲清了table表的前3byte
长度之后的后5bytes分别是扩展表id 2byte,version_number,section_number,last_section_number
版本号 version_number 的意思是,以pat表为例,当pat表变化时, version_number 会改变
section_number,last_section_number 用来表达pat 表是否已经被收完整了. 这样就占掉了8个字节
后面的数据用表体来解释.


用c可如下定义了table_header
struct table_header
{
    uint8_t table_id;   // 代表表类型
    uint16_t section_syntax_indicator : 1;        //格式标记,pat,pmt 都为1,标记section 开始?
    uint16_t private_bit : 1;
    uint16_t reserved : 2;
    uint16_t section_length : 12;                //节的长度
    uint16_t table_id_ext;                        //扩展的id,代表本表ID.
    uint8_t reserved1 : 2;
    uint8_t version_number : 5;                    //版本号
    uint8_t current_next_indicator : 1;            //当前,下一个标记
    uint8_t section_number;                        // 节号及最后的节号
    uint8_t last_section_number;                //对应到流中的字节,后面除crc外,只在内存中存在
};

还好,这些表头的格式各种表是一致的
开头8bytes, tableID(1)+len(2)+表头(5),及crcbyte, 中间为表体.

--------------------------------------------------------
那么什么是PAT呢?(pragram association table) 节目关联表.
--------------------------------------------------------
这个表用来描述ts流中由几套节目,节目的pmt_id 是多少.
这个表的13bits pid 固定为0,8bits tableID 固定为0,播放节目都是从搜pat开始的。
前面20个pid 都是被固定分配好的,都成标准了,不能随便定义了,这里先介绍pid固定为0的pat
ts 流中可能有一套节目,也可能有多套节目。
这个pat表头就是从第6byte 开始的. 当然pmt表头也是从第6字节开始.此为后话。
从第6字节开始,

我们给一个实例分析:
47 40 00 10 00 00 B0 0D 00 01 C1 00 00 00 01 F0
00 2A B1 04 B2

47 40 00 10:
47 同步头
40 00: 13bitpid 为0, 前面3bit代表

:transport_error_indicatior(1),payload_unit_start(1),transport_priority(1)

第4byte含义trasport_scramling(2),adaption_field_ctrol(2),continuity_counter(4)
10: 不加扰,有负载,计数值为0
00 负载偏移值为0

下面就是pat表数据了

其数据结构定义如下:也请参考后面的图做对照

PAT数据结构:y
program_association_section()
{
    table_id;//8位固定为0x00,表示该表是PAT表
    section_syntax_indicator;//1,段语法标志位,固定为1
    '0';//1,固定为0 为了防止和ISO13818Video流格式中的控制字冲突而设置的
    reserved;//2,保留位,一般都是1,这样前4位就是B
    section_length;//12,段的大小,表示这个字节后面有用的字节数,包括CRC32.假如后面的字节加上前面的字节数少于188,后面会用0xFF填充,假如这个数值比较大,则PAT会分成几部分来传输。
    transport_stream_id;//16该传输流的ID,区别于一个网络中其他多路复用的流
    reserved;//2
    version_number;//5,范围0-31,表示PAT的版本号,标注当前节目的版本,这是个非常有用的参数,当检测到这个字段改变时,说明TS流中的节目已经改变了,程序必须重新收索节目
    current_next_indicator;//1,表示发送的PAT是当前有效还是下一个PAT有效
    section_number;//8,分段的号码,PAT可能分为多个段传输,第一段为00,以后每个分段加1,最大可能有256个分段
    last_section_number;//8,最后一个分段的号码
    for(i=0;i<N;i++)
    {
        program_number;//16,节目号
        reserved;//3
        if(program_number=='0')
        {
            network_PID;//13,网络信息表(NIT)的PID,网络信息表提供了该物理网络的一些信息,和电视台相关的,
        }
        else
        {
            program_map_PID//13,节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个
        }
        CRC_32;//32,CRC32校验码
    }
}

下面来分析具体数据:

00 B0 0D:
00 tableid
B0 0D: 数据长度12bits,B的含义'1011' 必需要这样写,代表section_syntax_indicator 为1,保留位11
00 01 c1 00 00: 5数据解释,扩展id及3个number
00 01 是transport_stream_id, 节目中它是不变的,它的用途 不同的厂家可以对应不同的PAT的transport_stream_id
c1 是,reserved1(2bit),version_number(5)=0, current_next_indicator((1)
00 00, section_number 和last_section_number 都是0,在本节就把表数据表示完了.

循环体数据从pat偏移8字节开始,每4bytes为1组

00 01 F0 00
前16个bit为节目号,后13bit 为pmt_pid, 当节目号为0时, 后13bit 时nit pid, 本码流没有nit信息
此时的pmt_pid为0x0100

我看到有人写代码, 当判定是PAT表时(PID=0),直接从buffer[15],buffer[16]拿PMT的PID, 真是简单直接啊,省掉了一大堆没有啥用途的东西!. PAT主要就是干这个的.

2A B1 04 B2: 为crc32
讲清了pat, 再来看什么是pmt
--------------------------------------------------------
什么是PMT(pragram map table) 节目映射表
--------------------------------------------------------
回顾pat 它告诉了我们有几套节目,每套节目的pmt表id是多少.
pmt 会告诉我们该套节目包含哪些基础流,及基础流的相关信息. lets go!

其数据结构定义如下:

PMT数据结构:也请参考后面的图做对照

TS_program_map_section()
{
    table_id; // 8 固定为0x02,表示该表是PMT
    section_syntax_indicator; //1 段语法标志位,固定为1
    '0';//1
    reserved;//2
    section_length; //12 表示这个字节后面有用的字节数,包括CRC32。假如后面的字节加上前面的字节数少于188,后面会用0XFF填充。假如这个数值比较大,则PMT会分成几部分来传输。
    program_number; //16 节目号,表示该PMT对应的节目号
    reserved;// 2
    version_number; //5范围0-31,表示PMT的版本号,标注当前节目的版本,这是个非常有用的参数,当检测到这个字段改变时,说明TS流中的节目已经改变了,程序必须重新收索节目
    current_next_indicator;//1表示发送的PMT是当前有效还是下一个PAT有效
    section_number; // 8 分段的号码固定为0x00
    last_section_number;//8最后分段的号码 固定为0x00
    reserved;//3
    PCR_PID; //13 PCR(节目时钟参考)所在TS分组的PID,根据PID可以去搜索相应的TS分组,解出PCR信息。
    reserved; //4
    program_info_length; //12该节目的信息长度,在此字段之后可能会有一些字节描述该节目的信息
    for (i=0; i<N; i++)
    {
        descriptor();
    }
    for (i=0;i<N1;i++)
    {
        stream_type;//8 指示了PID为elementary_PID的PES分组中原始流的类型,比如视频流,音频流等
        reserved; //3
        elementary_PID;//13 该节目中包括的视频流,音频流等对应的TS分组的PID
        reserved; //4
        ES_info_length;//12 该节目相关原始流的描述符的信息长度
        for (i=0; i<N2; i++)
        {
            descriptor();
        }
    }
}

上个例子数据吧.
47 50 00 10 00 02 B0 1D 00 01 C1 00 00 E1 00 F0
00 1B E1 00 F0 00 0F E1 01 F0 06 0A 04 65 6E 67
00 8D 82 9A 07
//有了上面的基础,下面拣重要的叙述
47 50 00 10: pid 是0x1000, pmt pid
00 : 负载数据偏移为0,紧跟的下一个byte,byte6就是负载了.
02 B0 1D: tableid 02, 长度0x1d, 正好把数据分完 ,  PMT_TID = 0x02  buffer[7]是loop 长度
00 01 c1 00 00: 扩展id 2bytes,,这里的扩展id代表本表ID,实际就是节目号, 后面是version号0,current_next_indicator(1), sec_num:00, last_sec_num:00,一个表搞定.
最末4byte为crc, 中间为私有数据
E1 00 : 低13bits 是PCR_PID = 0x100                有人直接从buffer[13],buffer[14]取PCR_PID
F0 00 : 低12bits 是节目信息长度,为0                buffer[15],buffer[16]为额外信息长度,可以跳过
然后进入循环体:

1B : 流类型, H.264/14496-10 video (MPEG-4/AVC)  (0x1B,0x02 都是视频流)
E1 00 : 低13bit 为基础流id = 0x100
F0 00 : 低12bit 为描述符长度 = 0

0F : 流类型 ,     STEAM_TYPE_ADTS_AUDIO = 0x0F,  (0x03,0x04,0x06,0x0F都是音频流)
E1 01 : 基础流id=0x101
F0 06 : 描述符长度为6,
0A 04 65 6E 67 00

0A 是一个标签,代表语言 _(ISO_639_language, 0x0A)
04 为标签长度,后面是描述字符串. "eng" 代表英文

有了这两个表,就可以找到基础流了,至于其它表例如sdt等,告诉你节目叫什么名字等,并不是必需的,但有时也是需要的


参考: 1. tsanalyse     代码(github)
参考: 2. tsinfo     代码(ubuntu source)
参考: 3. ISO/IEC 13818-1 标准

参考图1:pat 表

 参考图2: pmt 表

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值