TS流相关问题

转自 http://www.cnblogs.com/wangqiguo/archive/2013/03/29/2987949.html


将H264与AAC打包Ipad可播放的TS流的总结


由于要在crtmpserver中实现Http Live Streaming ,本人花了接近3个星期的时间,研究将H264与AAC打包为TS流并能在Ipad上通过HTML5播放,由于没有任何现成代码可供参考,打包代码全部手写,打包格式主要参考ISO/ICE 18318-1.pdf。期间碰到很多问题,走了不少弯路,符合标准的TS不一定能在Ipad上播放,但是Ipad上播放的TS一定是符合标准的,可以说是TS标准中的特例。现将遇到的主要问题以及要点总结如下:

【打包流程以及相关图:】

【视频H264到TS注意要点:】

<1>. 如果h264的包大于 65535  的话,可以设置PES_packet_length为0 ,具体参见ISO/ICE 13818-1.pdf  49 / 174 中关于PES_packet_length的描述
打包PES, 直接读取一帧h264的内容, 此时我们设置PES_packet_length的值为0000
表示不指定PES包的长度,ISO/ICE 13818-1.pdf 49 / 174 有说明,这主要是方便
当一帧H264的长度大于PES_packet_length(2个字节)能表示的最大长度65535
的时候分包的问题, 这里我们设置PES_packet_length的长度为0000之后 ,  那么即使该H264视频帧的长度
大于65535个字节也不需要分多个PES包存放, 事实证明这样做是可以的, ipad可播放
-----------------

<2>. PES头中的stream_id 好像是无所谓,随便改了几个都可以。  不过找到的几个可以ipad播放的样本文件中都设置的是0xe0
而在苹果的http://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/HTTP_Live_Streaming_Metadata_Spec/2/2.html
里面的2.4 PES Stream Format 提供的表里面,使用的是0xbd 表示的是private_stream_id_1,不知道这个说明是否专门针对ID3,但是建议设置为0xeo,我看到的几个TS样本文件都是这么设置的。
-----------------

<3>. 对于视频来说PTS和DTS都需要,可以设置为一样的,如果只有PTS,没有DTS是不行的。可以将pts的值赋值给dts,pts_dts_flag的值应该设置为0x03,也就是二进制的'11'
-----------------

<4>. pmt中的  stream_type = 0x1b 表示 H264  , stream_type = 0x0f 表示AAC
-----------------

<5>. ipad不需要pcr都可以播放,可以将pmt头中的 PCR_PID设置为0x1fff 表示没有PCR ,参考ISO/ICE 13818-1.pdf 65 / 174,  在之后的所有关于adaptation_field中设置PCR_flag: 00 就可以了,并且在adaptation_field中也不需要写入pcr部分的值
-----------------

<6>. 在每一帧的视频帧被打包到pes的时候,其开头一定要加上 00 00 00 01 09 xx  这个nal。否则就有问题
苹果官网有如下说明:
https://developer.apple.com/library/ios/#documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html

10.These settings are the current recommendations. There are also certain requirements. 
The current mediastreamsegmenter tool works only with MPEG-2 Transport Streams as defined in ISO/IEC 13818. 
The transport stream must contain H.264 (MPEG-4, part 10) video and AAC or MPEG audio. 
If AAC audio is used, it must have ADTS headers. H.264 video access units must use Access Unit Delimiter NALs, 
and must be in unique PES packets.

其中 H.264 video access units must use Access Unit Delimiter NALs  中的 Access Unit Delimiter NAL 在H264的文档中是09 ,但是有什么用,我也不太清楚。根据我的实验09后面的一个字节好像设置成什么都可以,但是不能设置为00
-----------------

<7>. 关于pts的计算方法

// 1s = 90000 time scale , 一帧就应该是  90000/video_frame_rate 个timescale
static uint32_t video_frame_rate = 30;
static uint32_t video_pts_increment = 90000 / video_frame_rate;   //用一秒钟除以帧率,得到每一帧应该耗时是多少,单位是 timescale单位
static uint64_t video_pts = 0;

在以后的计算中,每生成一帧的ts,该帧的pts值应该是 video_pts += video_pts_increment; 伪代码如下:

while(..)
{
   ReadH264(h264_buffer)
   PacketTS(h264_buffer,out_ts_buffer,video_pts);
   video_pts += video_pts_increment;
}


<8>. 音频应该是只需要pts,不需要dts。
音频pts的计算方法同上,只不过不是通过帧率,而是通过采样率。
uint32_t audio_pts_increment = (90000 * audio_samples_per_frame) / audio_sample_rate;
上面的伪代码就不贴了。

<9>. 关于pat,pmt的插入时间,实际上pat,pmt是要连续插入到ts流中的,并不是开始插入之后就完了。因为ts要保证从任何时候都可以开始播放。目前我是每4帧视频(这4帧H264可能被打包成很多的ts包,而不止4包)插入一次pat,pmt包。

<10>. PES音视频数据包的ts头中的continuity_counter 必须是连续的,从小到大的,当增长到15之后再从0开始循环。第一包可以不从0开始。但是之后的continuity_counter 必须是连续的,并且是按照步长1增长的。continuity_counter的增长相对于所有PID相同的包他们的增长是独立的,例如第一个PAT包其continuity_counter值是0,那么第二个PAT包的continuity_counter是1。  第一个PMT包其continuity_counter值是2,第二个PMT包其continuity_counter值是3。  

【音频AAC到TS注意要点:】

aac不需要设置DTS,只要PTS就可以了,加上DTS可能还播放不了

aac打包成PES的时候,要想在ipad上播放必须设置PES_packet_length的长度,而视频可以设置为0,但是音频必须设置为正确的长度值,aac的长度不可能超过65535,所以也不可能导致PES_packet_length溢出。
否则ipad播放不了。但是QQ影音可以播放。这也许是ipad的特性。

 //data_len:AAC音频裸流的长度
 // 3:PES头
 //5: 如果是音频的话会有5个字节的PTS
_ts_packet_pes_header.PES_packet_length_16bit = data_len + 3 + 5 ;

【关于CRC32校验算法:】
static uint32_t crc32table[256] = {   
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,   
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,   
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,   
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,   
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,   
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,   
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,   
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,   
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,   
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,   
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,   
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,   
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,   
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,   
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,   
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,   
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,   
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,   
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,   
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,   
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,   
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,   
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,   
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,   
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,   
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,   
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,   
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,   
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,   
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,   
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,   
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,   
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,   
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,   
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,   
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,   
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,   
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,   
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,   
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,   
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,   
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,   
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};

uint32_t CRC32(const uint8_t *data, int len)
{   
    int i;   
    uint32_t crc = 0xFFFFFFFF;       
    for(i = 0; i < len; i++) 
    crc = (crc << 8) ^ crc32table[((crc >> 24) ^ *data++) & 0xFF];       
    return crc;
}

【关于TS流中的填充字节】

TS流中有2中不同的填充形式,因为TS每一包要求是188个字节,当不足188个字节的时候, 必须要补充到188个字节,这就涉及到填充的问题。

<1>. 如果TS包中承载的是PSI数据(PAT,PMT等),那么其填充是在该包的最后一个有效字节的后面填充0xFF直到满足188个字节为止。
解码器会丢弃这些字节,具体说明参考 ISO_IEC 13818-1.pdf   60 / 174

<2>. 如果TS包中承载的是PES数据,那么当不足188个字节的时候,需要使用adaptation_field 这个域,也就是指定TS包中的adaptation_field_control的值来控制payload与adaptation_field的承载关系,同时指定PES中的adaptation_field_length指定填充多少字节,具体的填充字节值应该是随意的建议使用FF,并且此adaptation_field的使用好像只能在PES中使用。   ISO_IEC 13818-1.pdf   39 / 174


==================================================================================================


CMMB中的H264和AAC打包成ts流




参照tstools这个开源项目完成了CMMB的merge aac with 264 to TS.

在做的过程中,分成了几个阶段。先试着将H264打包成TS,这花了最大的精力和时间;接着将AAC打包成TS;最后将其合并起来。

一、处理H264
     1、H264 -> pes
     在查找了大量资料后,对H264有了初步的认识。H264的结构从大到小依次为视频序列(sequence )、图像(picture ,相当于帧)、片组、片(slice)、宏块(MacroBlock)、子块(SubBlock)。
     
          
     其中,帧率是针对于图像来说的,例如25Hz的帧率,就差不多是每个40ms显示1个图像。
     图像分为 I图像、P图像、B图像。
     每个图像又由多个片组组成,片组由多个片组成。
     每个片又由多个宏块组成。片分为I_slice、P_slice、B_slice。宏块有I、P、B三种宏块。
     I_slice里只有I_MB,  p_slice里可有P_MB和I_MB,  B_slice里可有B_MB和I_MB。
     通常的一个图像里就含一种片,这次的CMMB里的H264比较简单,是Baseline的profile,只含有I、P帧,而且每个图像就1个slice。

     对于H264按功能层次划分,又分为视频编码层VCL(video coding layer)和网络抽象层 NAL(Network abstraction layer).
     每个NAL单元包含:NAl头+负荷
                                 NAl的头一般为00 00 01 或者00 00 00 01,然后到下一个头之间都为这一个nalu的数据。

     所以,对于封装成ts流,只需要去读取H264的原始文件,然后找到这个nal头,再将这个nalu(包含头和负荷)当成数据打包成pes再打成ts即可。

     在打包成pes的时候,需要注意的一个问题就是pts/dts,其单位应该是系统时钟。需要在找到每个图像的起始slice的时候,在打包成pes的时候加上pts/dts。这次的CMMB中,其视频帧只含I/P图像(帧),且每个图像只有1个片,所以就在读取264原始数据时读到I_slice或P_slice的时候,一并打入pts。CMMB流正好又有现成的pts,只需读出来,按照CMMB中的换算方法,每22500就是1秒,就能得出pts的值,而不需要我自己手动的去计算添加。

      例如,视频帧率为25HZ的时候,即1秒25帧,每帧的间隔40ms。按90Khz的视频频率来算的话,其对应的系统时钟数应该是
      1/25*90000 = 3600 个clk。
     所以当分析pes时,其pts字段的33个bit算出来的话,就是按这个clk为单位的。相邻的视频帧的pts之差值为3600.

     2、pes -> ts
     h264打包成ts的时候,还需要打入pat和pmt。pmt里指定了视频的类型和pid。
     pes打包的时候,按ts协议格式封装即可。
     PCR使用和视频一样的pid,这里把pts当成pcr打进ts包。

二、处理AAC
     AAC的ts封装相对于H264来说就简单多了。这次的CMMB采用的是aac的adts格式封装,同h264类似,也有其自己的分界符,然后两分界符之间就是数据,分界符为1111 1111 1111,即FFF。依次读取AAC的原始文件,遇到一个FFF时,将其后的数据连同这个FFF的头看成整体,作为负载封入PES。同样的,每帧都需要打入pts。CMMB流中也附有AAC的pts,读取后计算写入PES即可。也省了我去根据采样率,每帧的样本率去计算pts。

三、合并H264和AAC的ts流
     做完了上两步工作,最后一步就更简单了。
     先是打入PAT、PMT。PMT需要稍微修改,因为加入了音频,所以要在其中指明音频的类型和pid。
     接着就是视频、音频的ts包依次打。打的时候稍微注意要判断下视频和音频的pts值,保持同步。

对于H264更底层的东西,就没研究下去了。对于H264的SPS获取PTS,也没整太明白。还有PCR这方面,都还不太清楚。后面再找时间慢慢研究吧~


==========================================================================================


TS文件格式详解 

转自  http://blog.chinaunix.net/uid-24922718-id-3686257.html


 最近彻底研究分析了ts文件格式,这里做下学习总结:
简单的来说,ts文件中的信息其实就是通过负载类型字段来找,找到后把数据从负载中提取出来,ts中可以有很多媒体类型数据,比如说可以同时又音频和视频数据,
可是要如何区分ts文件中的数据是音频还是视频呢?这就需要动用ts文件中的PSI描述说明了。

PSI:

在MPEG-II中定义了节目特定信息(PSI),PSI用来描述传送流的组成结构,在MPEG-II系统中担任极其重要的角色,在多路复用中尤为重要的是PAT表和PMT表。PAT表给出了一路MPEG-II码流中有多少套节目,以及它与PMT表PID之间的对应关系;PMT表给出了一套节目的具体组成情况与其视频、音频等PID对应关系。PSI提供了使接收机能够自动配置的信息,用于对复用流中的不同节目流进行解复用和解码。PSI信息由以下几种类型表组成:

◆ 节目关联表(PAT Program Association Table)

     PAT表用MPEG指定的PID(00)标明,通常用PID=0表示。它的主要作用是针对复用的每一路传输流,提供传输流中包含哪些节目、节目的编号以及对应节目的节目映射表(PMT)的位置,即PMT的TS包的包标识符(PID)的值,同时还提供网络信息表(NIT)的位置,即NIT的TS包的包标识符(PID)的值。

◆ 条件接收表(CAT Conditional Access Table)

     CAT表用MPEG指定的PID(01)标明,通常用PID=1表示。它提供了在复用流中条件接收系统的有关信息,指定CA系统与它们相应的授权管理信息(EMM))之间的联系,指定EMM的PID,以及相关的参数。

◆ 节目映射表(PMT Program Map Table)

     节目映射表指明该节目包含的内容,即该节目由哪些流组成,这些流的类型(音频、视频、数据),以及组成该节目的流的位置,即对应的TS包的PID值,每路节目的节目时钟参考(PCR)字段的位置。

◆ 网络信息表(NIT Nerwork Information Table)

     网络信息表提供关于多组传输流和传输网络相关的信息,其中包含传输流描述符、通道频率、卫星发射器号码、调制特性等信息。

◆ 传输流描述表(TSDT Transport Stream Description Table)

    传输流描述表由PID为2的TS包传送,提供传输流的一些主要参数。

◆ 专用段(private_section)

     MPEG-2还定义了一种专用段用于传送用户自己定义的专用数据。

◆ 描述符(Descripter)

     除了上述的表述之外,MPEG-2还定义了许多描述符,这些描述符提供关于视频流、音频流、语言、层次、系统时钟、码率等多方面的信息,在PSI的表中可以灵活的采用这些描述符进一步为接收机提供更多的信息。

     在解码时,接收机首先根据PID值找到PAT表,找出相应节目的PMT表的PID,再由该PID找到该PMT表,再在PMT表中找到相应的码流,然后开始解码。

总下简单的说就是,解析ts的过程就是通过找到PAT表,从PAT表中找出对应存在的节目的id,按照这些id找到这些节目的PMT表,从中获到这些节目总的相对的媒体数据id,然后通过这些id,再从ts文件中找到这些文件的es数据,来完成解码或者别的什么操作。
  如图:

ts文件,将每个数据包分成188个字节来发送。ts文件格式如图:
   
 从图中可以看到 ts文件头分为包头和负载两部分,现在我们详细看下包头结构:
 
sync_btye固定为0x47 ,说明从这个字节后的188个字节都属于一个ts包。 比较重要的是PID这个字段,共13位,表示了这个ts包负载数据的类型,如果没有这个信息,无法再后续寻找我们想要的数据。调整字段的作用稍后会看到。这里先跳过介绍吧,其他的字段对于ts的学习可以先不研究,不是很重要。

    现在我们看看,PAT表的结构:
    
   
     TS的解析工作,一般都是从找PAT表开始,所以,要先找到负载中头个字节是0x00的,就说明找到PAT表了。section_length表示从这个字段开始后有几个字节,如果不满188个字节,就用0xff填满。可以发现去掉最后4位的crc校验位从section_number之后的5个字节开始,就是这个ts文件中缩有的节目了,每两个字节代表一个节目,从中很容易获取到节目的ID信息。
    获取到ID之后,就可以开始查找关于这个id的PMT表了。
    PMT:
    
      
        PMT表中 多数字段含义和PAT表类似,值得注意的是对于对应节目中的媒体数都是5个字节表示,音频数据或视屏数据。所以,从中可以发现当前节目有多少的音视频相关信息。从stream_type可以通过查表来得知是音频数据还是视频数据等信息,这个就靠大家自己在网上查阅了。
         之后就可以通过得到的elementary_pid来查找对应的音视频信息了。从而从中获取出es流。


==============================================================================================


TS格式解析

转自 http://www.cnblogs.com/aHuner/archive/2013/05/27/3102432.html


1.TS格式介绍

   TS:全称为MPEG2-TS。TS即"Transport Stream"的缩写。它是分包发送的,每一个包长为188字节(还有192和204个字节的包)。包的结构为,包头为4个字节(第一个字节为0x47),负载为184个字节。在TS流里可以填入很多类型的数据,如视频、音频、自定义信息等。MPEG2-TS主要应用于实时传送的节目,比如实时广播的电视节目。MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。简单地说,将DVD上的VOB文件的前面一截cut掉(或者是数据损坏数据)就会导致整个文件无法解码,而电视节目是任何时候打开电视机都能解码(收看)的。

   TS解析需要参考:ISO/IEC 13818-1的2.4 Transport Stream bitstream requirements

2.TS流包含的内容

    一段TS流,必须包含PAT包、PMT包、多个音频包、多个视频包、多个PCR包、以及其他信息包。

    解析TS流数据的流程:查找PID为0x0的包,解析PAT,PAT包中的program_map_PID表示PMT的PID;查找PMT,PMT包中的elementary_PID表示音视频包的PID,PMT包中的PCR_PID表示PCR的PID,有的时候PCR的PID跟音频或者视频的PID相同,说明PCR会融进音视频的包,注意解析,有的时候PCR是自己单独的包;CAT、NIT、SDT、EIT的PID分别为: 0x01、0x10、0x11、0x12。

3.TS包头解析

    TS包头有4个字节

//Transport Stream header
typedef  struct  TS_header
{
          unsigned sync_byte                    :8;      //同步字节,固定为0x47 ,表示后面的是一个TS分组,当然,后面包中的数据是不会出现0x47的
          unsigned transport_error_indicator       :1;      //传输错误标志位,一般传输错误的话就不会处理这个包了
          unsigned payload_unit_start_indicator    :1;      //有效负载的开始标志,根据后面有效负载的内容不同功能也不同
          // payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。
          unsigned transport_priority              :1;      //传输优先级位,1表示高优先级
          unsigned PID                          :13;     //有效负载数据的类型
          unsigned transport_scrambling_control     :2;      //加密标志位,00表示未加密
          unsigned adaption_field_control          :2;      //调整字段控制,。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。
          unsigned continuity_counter              :4;      //一个4bit的计数器,范围0-15
} TS_header;
     //特殊参数说明:
    //sync_byte:0x47
    //payload_unit_start_indicator:0x01表示含有PSI或者PES头
    //PID:0x0表示后面负载内容为PAT,不同的PID表示不同的负载
    //adaption_field_control:
         // 0x0: // reserved for future use by ISO/IEC
         // 0x1: // 无调整字段,仅含有效负载  
         // 0x2: // 仅含调整字段,无有效负载
         // 0x3: // 调整字段后含有效负载
 
// Parse TS header
int  Parse_TS_header(unsigned char  *pTSBuf, TS_header *pheader)
{
     pheader->sync_byte                                     = pTSBuf[0];
     if  (pheader->sync_byte != 0x47)
         return  -1;
     pheader->transport_error_indicator       = pTSBuf[1] >> 7;
     pheader->payload_unit_start_indicator    = pTSBuf[1] >> 6 & 0x01;
     pheader->transport_priority             = pTSBuf[1] >> 5 & 0x01;
     pheader->PID                         = (pTSBuf[1] & 0x1F) << 8 | pTSBuf[2];
     pheader->transport_scrambling_control   = pTSBuf[3] >> 6;
     pheader->adaption_field_control         = pTSBuf[3] >> 4 & 0x03;
     pheader->continuity_counter            = pTSBuf[3] & 0x0F;
     return  0;
}

      TS包头解析需要参考:ISO/IEC 13818-1的2.4.3.2 Transport Stream packet layer

4.TS负载格式解析

4.1 PAT解析

   TS_header包头中的PID值为0x0,表示当前负载为PAT(Program Association Table)。PAT数据的信息可以理解为整个TS流包含的节目信息。

// Program Association Table
typedef  struct  PAT_Packet_tag
{
     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; //该传输流的ID,区别于一个网络中其它多路复用的流
     unsigned reserved_2                      : 2; // 保留位
     unsigned version_number                  : 5; //范围0-31,表示PAT的版本号
     unsigned current_next_indicator          : 1; //发送的PAT是当前有效还是下一个PAT有效
     unsigned section_number                  : 8; //分段的号码。PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段
     unsigned last_section_number             : 8; //最后一个分段的号码
     // for(i=0; i<N; i++)
     // {
     unsigned program_number                  : 16;
     unsigned reserved_3                      : 3;
     unsigned network_PID                     : 16;  // 或者program_map_PID
     unsigned CRC_32                          : 32;
     // }
} PAT_Packet;
 
// Parse PAT
int  Parse_PAT(unsigned char  *pTSBuf, PAT_Packet *packet)
{
     TS_header TSheader;
     if  (Parse_TS_packet_header(pTSBuf, &TSheader) != 0)
         return  -1;
     if  (TSheader.payload_unit_start_indicator == 0x01) // 表示含有PSI或者PES头
     {
         if  (TSheader.PID == 0x0)  // 表示PAT
         {
              int  iBeginlen = 4;
              int  adaptation_field_length = pTSBuf[4];
              switch (TSheader.adaption_field_control)
              {
              case  0x0:                                    // reserved for future use by ISO/IEC
                   return  -1;
              case  0x1:                                    // 无调整字段,仅含有效负载      
                   iBeginlen += pTSBuf[iBeginlen] + 1;  // + pointer_field
                   break ;
              case  0x2:                                     // 仅含调整字段,无有效负载
                   return  -1;
              case  0x3: // 调整字段后含有效负载
                  if  (adaptation_field_length > 0)
                  {
                       iBeginlen += 1;                   // adaptation_field_length占8位
                       iBeginlen += adaptation_field_length; // + adaptation_field_length
                  }
                  else
                  {
                       iBeginlen += 1;                       // adaptation_field_length占8位
                  }
                  iBeginlen += pTSBuf[iBeginlen] + 1;           // + pointer_field
                  break ;
             default :
                  break ;
             }
             unsigned char  *pPAT = pTSBuf + iBeginlen;
             packet->table_id                    = pTSBuf[0];
             packet->section_syntax_indicator    = pTSBuf[1] >> 7;
             packet->zero                        = pTSBuf[1] >> 6 & 0x1;
             packet->reserved_1                  = pTSBuf[1] >> 4 & 0x3;
             packet->section_length              = (pTSBuf[1] & 0x0F) << 8 | pTSBuf[2];
             packet->transport_stream_id         = pTSBuf[3] << 8 | pTSBuf[4];
             packet->reserved_2                  = pTSBuf[5] >> 6;
             packet->version_number              = pTSBuf[5] >> 1 &  0x1F;
             packet->current_next_indicator      = (pTSBuf[5] << 7) >> 7;
             packet->section_number              = pTSBuf[6];
             packet->last_section_number         = pTSBuf[7];
             int  len = 0;
             len = 3 + packet->section_length;
             packet->CRC_32                      = (pTSBuf[len-4] & 0x000000FF) << 24
                                                 | (pTSBuf[len-3] & 0x000000FF) << 16
                                                 | (pTSBuf[len-2] & 0x000000FF) << 8
                                                 | (pTSBuf[len-1] & 0x000000FF);
 
             int  n = 0;
             for  ( n = 0; n < (packet->section_length - 12); n += 4 )
             {
                  packet->program_number = pTSBuf[8 + n ] << 8 | pTSBuf[9 + n ];
                  packet->reserved_3                = pTSBuf[10 + n ] >> 5;
                  if  ( packet->program_number == 0x00)
                  {
                      packet->network_PID = (pTSBuf[10 + n ] & 0x1F) << 8 | pTSBuf[11 + n ];
                  }
                  else
                  {
                      // 有效的PMT的PID,然后通过这个PID值去查找PMT包
                      program_map_PID = (pTSBuf[10 + n] & 0x1F) << 8 | pTSBuf[11 + n];
                  }
             }
             return  0;
          }
     }
     return  -1;
}

  PAT数据解析需要参考:ISO/IEC 13818-1的2.4.4.3 Program Association Table

4.2 PMT解析

    由PAT包中的program_map_PID可以确定PMT(Program Map Table)的PID。PMT数据的信息可以理解为这个节目包含的音频和视频信息。 

   

// Program Map Table
typedef  struct  PMT_Packet_tag
{
      unsigned table_id                        : 8;
      unsigned section_syntax_indicator        : 1;
      unsigned zero                            : 1;
      unsigned reserved_1                      : 2;
      unsigned section_length                  : 12;
      unsigned program_number                  : 16;
      unsigned reserved_2                      : 2;
      unsigned version_number                  : 5;
      unsigned current_next_indicator          : 1;
      unsigned section_number                  : 8;
      unsigned last_section_number             : 8;
      unsigned reserved_3                      : 3;
      unsigned PCR_PID                         : 13;
      unsigned reserved_4                      : 4;
      unsigned program_info_length             : 12;
      // for(i=0; i<N; i++)
      // {
      unsigned stream_type                     : 8;
      unsigned reserved_5                      : 3;
      unsigned elementary_PID                  : 13;
      unsigned reserved_6                      : 4;
      unsigned ES_info_length                  : 12;
      // }
      unsigned CRC_32                          : 32;
} PMT_Packet;
    // Parse PMT
int  Parse_PMT(unsigned char  *pTSBuf, PMT_Packet *packet)
{
     // 参考Parse_PAT()来做就行了
     // ...
     
     return  0;
}

  PMT数据解析需要参考:ISO/IEC 13818-1的2.4.4.8 Program Map Table

4.3 PES解析

    根据文档参考PAT、PMT的解析流程就能完成PES的解析了。

    需要注意的是PES中PTS的解析,一般来说在90 kHz 中,PTS/9000的值为秒单位。

    

unsigned long  long  Parse_PTS(unsigned *pBuf)
{
      unsigned long  long  llpts = (((unsigned long  long )(pBuf[0] & 0x0E)) << 29)
          | (unsigned long  long )(pBuf[1] << 22)
          | (((unsigned long  long )(pBuf[2] & 0xFE)) << 14)
          | (unsigned long  long )(pBuf[3] << 7)
          | (unsigned long  long )(pBuf[4] >> 1);
      return  llpts;
}

     PES数据解析需要参考:2.5.5.1 Syntax of the PES packet syntax for Program Stream directory

5.码流分析工具

5.1 Elecard Stream Analyzer

5.2 EasyICE


========================================================================================


TS流的解码过程-ES-PES-DTS-PTS-PCR

转自 http://blog.csdn.net/godspirits/article/details/5653381

TS 流解码过程:

1. 获取TS中的PAT

2. 获取TS中的PMT

3. 根据PMT可以知道当前网络中传输的视频(音频)类型(H264),相应的PID,PCR的PID等信息。

4. 设置demux 模块的视频Filter 为相应视频的PID和stream type等。

5. 从视频Demux Filter 后得到的TS数据包中的payload 数据就是 one piece of PES,在TS header中有一些关于此 payload属于哪个 PES的 第多少个数据包。 因此软件中应该将此payload中的数据copy到PES的buffer中,用于拼接一个PES包。

6. 拼接好的PES包的包头会有 PTS,DTS信息,去掉PES的header就是 ES。

7. 直接将 被拔掉 PES包头的ES包送给decoder就可以进行解码。解码出来的数据就是一帧一帧的视频数据,这些数据至少应当与PES中的PTS关联一下,以便进行视音频同步。

8. I,B,B,P 信息是在ES中的。

 

ES 是直接从编码器出来的数据流,可以是编码过的视频数据流,音频数据流,或其他编码数据流的统称。 ES 流经过PES 打包器之后,被转换成 PES 包。 PES 包由包头和 payload 组成.


 PES 层,主要是在 PES 包头信息中加入 PTS( 显示时间标签 )  DTS (解码时间标签)用于视频、音频同步。 其实, Mpeg-2 用于视音频同步以及系统时钟恢复的时间标签分别在 ES  PES  TS  3 个层次中。在 ES 层,与同步有关的主要是视频缓冲验证 VBV  Video Buffer Verifier ),用以防止解码器的缓冲器出现上溢或下溢;在 PES 层,主要是在 PES 头信息里出现的显示时间标签 PTS  Presentation Time Stamp )和解码时间标签 DTS  Decoding Time Stamp );在 TS 层中, TS 头信息包含了节目时钟参考 PCR  Program Clock Reference ),用于恢复出与编码端一致的系统时序时钟 STC  System Time Clock )。

基本流程如下:首先 MPEG-2 压缩编码得到的 ES 基本流,这个数据流很大,并且只是 I  P  B 的这些视频帧或音频取样信息,然后加入一些同步信息,打包成长度可变长度的数据包 PES ,原来是流的格式,现在成了数据包的分割形式。同时要注意的是, ES 是只包含一种内容的数据流,如只含视频,或只含音频等,打包之后的 PES 也是只含一种性质的 ES, 如只含视频 ES  PES, 只含音频 ES  PES 等。可以知道, ES 是编码视频数据流或音频数据流,每个 ES 都由若干个存取单元( AU )组成,每个视频 AU 或音频 AU 都是由头部和编码数据两部分组成, 1  AU 相当于编码的 1幅视频图像或 1 个音频帧,也可以说,每个 AU 实际上是编码数据流的显示单元,即相当于解码的 1 幅视频图像或 1 个音频帧的取样。 PEG-2 对视频的压缩产生 I 帧、 P 帧、 B 帧。把帧顺序 I1,P4,B2,B3,P7,B5,B6 帧的编码 ES ,通过打包并在每个帧中插入 PTS/DTS 标志,变成 PES 。在插入 PTS/DTS 标志时,由于在 B  PTS  DTS 相等,所以无须在B 帧多插入 DTS 。而对于 I   P 帧,由于经过复用后数据包的顺序会发生变化,显示前一定要存储于视频解码器的从新排序缓存器中,经过从新排序后再显示,所以一定要同时插入 PTS  DTS 作为从新排序的依据。

 

其中,有否 PTS/DTS 标志,是解决视音频同步显示、防止解码器输入缓存器上溢或下溢的关键所在。 PTS 表明显示单元出现在系统目标解码器( STD- System Target Decoder )的时间 , DTS 表明将存取单元全部字节从 STD ES 解码缓存器移走的时刻。 视频编码图像帧次序为 I1,P4,B2,B3,P7,B5,B6,I10,B8,B9  ES ,加入 PTS/DTS后,打包成一个个视频 PES 包。每个 PES 包都有一个包头,用于定义 PES 内的数据内容,提供定时资料。每个 I  P B 帧的包头都有一个 PTS  DTS ,但 PTS  DTS  B 帧都是一样的,无须标出 B 帧的 DTS 。对 I 帧和 P 帧,显示前一定要存储于视频解码器的重新排序缓存器中,经过延迟(重新排序)后再显示,一定要分别标明 PTS  DTS 。例如,解码器输入的图像帧次序为 I1,P4,B2,B3,P7,B5,B6,I10,B8,B9 ,依解码器输出的帧次序,应该 P4  B2  B3 在先,但显示时 P4 一定要比 B2  B3 在后,即 P4 要在提前插入数据流中的时间标志指引下,经过缓存器重新排序,以重建编码前视频帧次序 I1,B2,B3,P4,B5,B6,P7,B8,B9,I10 。显然, PTS/DTS 标志表明对确定事件或确定信息解码的专用时标的存在,依靠专用时标解码器,可知道该确定事件或确定信息开始解码或显示的时刻。例如, PTS/DTS 标志可用于确定编码、多路复用、解码、重建的时间。


       PCR   

PCR 是 TS 里面的,即 TS packet 的 header 里面可能会有,他用来指定所期望的该 ts packet 到达 decoder 的时间,他的作用于 SCR 类似。


DTS, PTS

对于一个 ES 来说,比如视频,他有许多 I,P,B 帧,而 P, B 帧都是以 I , P 帧作为参考。由于 B 帧是前向后向参考,因此要对 B 帧作 decode 的话,就必须先 decode 该 B 帧后面的 帧( P, 或者 I 帧),于是, decode 的时间与帧的真正的 present 的时间就不一致了,按照 DTS 一次对各个帧进行 decode ,然后再按照 PTS 对各个帧进行展现。

有时候 PES 包头里面也会有 DTS , PTS ,对于 PTS 来说,他代表了这个 PES 包得 payload 里面的第一个完整地 audio access unit 或者 video access unit 的 PTS 时间(并不是每个 audio/video access unit 都带有 PTS/DTS ,因此,你可以在 PES 里面指定一个,作为开始)。

PES 包头的 DTS 也是这个原理,需要注意的是:对于 video 来说他的 DTS 和 PTS 是可以不一样的,因为 B 帧的存在使其顺序可以倒置。而对于 audio 来说, audio 没有双向的预测,他的 DTS 和 PTS 可以看成是一个顺序的,因此可一直采用一个,即可只采用 PTS。



============================================================================================


MEPG2 -TS小结


转自 http://blog.csdn.net/wanggp_2007/article/details/5735244


应该说真正了解TS,还是看了朋友推荐的《数字电视业务信息及其编码》一书之后,MPEG2 TS和数字电视是紧密不可分割的,值得总结一下其中的一些关系。

ISO/IEC-13818-1:系统部分;
ISO/IEC-13818-2:视频;
ISO/IEC-13818-3:音频;
ISO/IEC-13818-4:一致性测试;
ISO/IEC-13818-5:软件部分;
ISO/IEC-13818-6:数字存储媒体命令与控制;
ISO/IEC-13818-7:高级音频编码;
ISO/IEC-13818-8:系统解码实时接口;

MPEG2系统任务包括:
1. 规定以包传输数据的协议;
2. 规定收发两端数据流同步的协议;
3. 提供多个数据流的复用和解复用协议;
4. 提供数据流加密的协议。以包形式存储和传送数据流是MPEG2系统之要点。

ES是直接从编码器出来的数据流,可以是编码过的视频数据流,音频数据流,或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。PES包由包头和payload组成,具体格式摘录如下:

 

可以看到PTS/DTS是打在PES包里面的,这两个parameters是解决视音频同步显示,防止解码器输入缓存上溢或下溢的关键。PTS表示 显示单元出现在系统目标解码器(STD: system target decoder)的时间,DTS表示将存取单元全部字节从STD的ES解码缓存器移走的时刻。每个IPB帧的包头都有一个PTSDTS,但PTSDTSB帧都是一样的,无须标出B帧的DTS。对I帧和P帧,显示前一定要存储于视频解码器的重新排序缓存器中,经过延迟(重新排序)后再显示,一定要分别标明PTSDTS

        上面介绍过,ES首先需打包成PES流包,然后PES根据需要打包成PS或TS包进行存储或传输。其每路ES只包含一路信源的编码数据流,所以每路PES也只包含相对应信源的数据流。

对PS流而言,每个PES包头含有PTS和DTS,流识别码,用于区别不同性质ES。然后通过PS复用器将PES包复用成PS包。实际上是将PES 包分解为更细小的PS包。在解码的时候,解复用器将PS分解成一个个PES包,拆包器然后将PES包拆成视频和音频的ES,最后输入至各自解码器进行解 码。一个问题是:各个ES在解码时,如何保证视音频的同步呢?除了PTS和DTS的配合工作外,还有一个重要的参数是SCR(system clock reference)。在编码的时候,PTS,DTS和SCR都是由STC(system time clock)生成的,在解码时,STC会再生,并通过锁相环路(PLL-phase lock loop),用本地SCR相位与输入的瞬时SCR相位锁相比较,以确定解码过程是否同步,若不同步,则用这个瞬时SCR调整27MHz的本地时钟频率。最 后,PTS,DTS和SCR一起配合,解决视音频同步播放的问题。PS格式摘录如下:

 

PS包的长度比较长且可变,主要用于无误码环境里,因为越长的话,同步越困难,且在丢包的情况下,重组也越困难。所以,PS适合于节目信息的编辑和本地内容应用的application。


TS流也是由一个或多个PES组合而来的,他们可以具有相同的时间基准,也可以不同。其基本的复用思想是,对具有相同时间基准的多个PES现进行节目复用,然后再对相互有独立时间基准的各个PS进行传输复用,最终产生出TS

TS包由包头和包数据2部分组成,其中包头还可以包括扩展的自适用区。包头长度占4bytes,自使用区和包数据共占184bytes,整个TS包长度相当于4个ATM包长。TS包的包头由如下图摘录所示的同步字节、传输误码指示符、有效载荷单元起始指示符、传输优先、包识别(PID-Packet Identification)、传输加扰控制、自适应区控制和连续计数器8个部分组成。

其中,可用同步字节位串的自动相关特性,检测数据流中的包限制,建立包同步;传输误码指示符,是指有不能消除误码时,采用误码校正解码器可表示1bit 的误码,但无法校正;有效载荷单元起始指示符,表示该数据包是否存在确定的起始信息;传输优先,是给TS包分配优先权;PID值是由用户确定的,解码器根据PID将TS上从不同ES来的TS包区别出来,以重建原来的ES;传输加扰控制,可指示数据包内容是否加扰,但包头和自适应区永远不加扰;自适应区控制,用2 bit表示有否自适应区,即(01)表示有有用信息无自适应区,(10)表示无有用信息有自适应区,(11)表示有有用信息有自适应区,(00)无定义;连续计数器可对PID包传送顺序计数,据计数器读数,接收端可判断是否有包丢失及包传送顺序错误。显然,包头对TS包具有同步、识别、检错及加密功能。

    TS包自适应区由自适应区长、各种标志指示符、与插入标志有关的信息和填充数据4部分组成。其中标志部分由间断指示符、随机存取指示符、ES优化指示符、PCR标志、接点标志、传输专用数据标志、原始PCR标志、自适应区扩展标志8个部分组成。重要的是标志部分的PCR字段,可给编解码器的27MHz时钟提供同步资料,进行同步。其过程是,通过PLL,用解码时本地用PCR相位与输入的瞬时PCR相位锁相比较,确定解码过程是否同步,若不同步,则用这个瞬时PCR调整时钟频率。因为,数字图像采用了复杂而不同的压缩编码算法,造成每幅图像的数据各不相同,使直接从压缩编码图像数据的开始部分获取时钟信息成为不可能。为此,选择了某些(而非全部)TS包的自适应区来传送定时信息。于是,被选中的TS包的自适应区,可用于测定包信息的控制bit和重要的控制信息。自适应区无须伴随每个包都发送,发送多少主要由选中的TS包的传输专用时标参数决定。标志中的随机存取指示符和接点标志,在节目变动时,为随机进入I帧压缩的数据流提供随机进入点,也为插入当地节目提供方便。自适应区中的填充数据是由于PES包长不可能正好转为TS包的整数倍,最后的TS包保留一小部分有用容量,通过填充字节加以填补,这样可以防止缓存器下溢,保持总码率恒定不变。



前面3节总结了MPEG2 TS的基本格式,其中包括PES,PS和TS,以及相关字段的介绍。那么作为一种传输流,TS将内容进行打包/复用,让其媒体内容变成TS传输,并最终在解码端解码。简单来看,TS是一个传输层的协议栈,它可以承载各种内容的传输,比如MPEG,WMV,H264,甚至是IP,那么其中的传输规范是如何定义的呢?这个即是PSI(节目特定信息)要做的事情。

PSI由四张表构成:PAT,PMT,CAT和NIT,这四张表分别描述了一个TS所包括的所有ES流的传输结构。首先的一个概念是,TS是以包形式传播,在编解码端都需要以一定的包ID来标识TS流里承载的内容,比如,PAT表会存在于一个或多个TS包里,所以要用一个特别的包ID来表示,另外,不同的ES流也需要不同的包ID来标识。我们有了PAT和PMT这两种表,解码器就可以根据 PID,将TS上从不同ES来的TS包区分出来进行解码。

TS的解码分两步进行,其一,是从PID为0 的TS包里,解析出PAT表,然后从PAT表里找到各个节目源的PID,一般此类节目源都由若干个ES流组成,并描述在PMT表里面,然后通过节目源的 PID,就可以在PMT表里检索到各个ES的PID。其二,解码器根据PMT表里的ES流的PID,将TS流上的包进行区分,并按不同的ES流进行解码。所以,TS是经过节目复用和传输复用两层完成的,即在节目复用时,加入了PMT,在传输复用时,加入了PAT。同样在节目解复用时,可以得到PMT,在传输解复用时,可以得到PAT。下图很好地概述了其思想

 

 

 





TS是支持多路复用的,所以它可用来传输经复用后的多层节目。在复用过程中,要注意的是,解码过程中所需要面对的时间参考和同步问题,因为解复用是需要各种信息同步进行的,所以在复用过程中,就需要插入相关的时间信息:PTS,DTS,PCR。

TS形成过程中,PTS和DTS是在ES打包成PES时,根据STC的参考,将其时钟信息注入PES包中的,而之后在PES切成TS时,再将 PID和PCR信息注入到TS包中,当多路TS再进行复用的时候,各路TS的PCR将会被提取出来,再进行分析,然后再根据统一的STC参考,将新的 PCR生成并注入到TS中去,最后,因为原来PAT表信息不在适用,所以新的PAT表需要再生成,并附加到新的TS流中去。经过这多层的复用之后,新的 TS流即可以进入调制,传输阶段。过程可参见下图:

解码过程要面对的问题是:解复用,视音频的同步,解码缓存器无上下溢。解复用即是将TS在同一信道里不同时序进行传输的节目分离出来;视音频同步由 DTS, PTS和PCR三者协调完成,并且PCR是重建系统时间基准的绝对时标,而DTS和PTS是解码和重现时刻的相对时标;对解码缓存器无上下溢的问题,必须 借助于系统目标解码器(STD)模型来对其进行实现,基本思想如下:

  1. TS流进入解码器后,首先由换向器,按照一定的时序关系,将各种ES流分解出来(其中也包括PSI信息流)。
  2. 分解过后的ES流会进入各自的传输缓存器,通过之后,其PES流进入各自的主存储器,注意的是:PSI信息流会进入系统缓存器,最后也到达主存储器。
  3. 最后,解码器根据DTS信息,从各个主存储器分别提取媒体或系统信息,进行解码,并根据PTS信息,将媒体内容进行显示处理。

其过程可参见下图:

 




====================================================================================


浅析多节目传输流(MPTS)的产生

转自  http://sinoabrs.com/NewsDetails.aspx?CID=79


当前在数字电视的播出上主要有三大标准:美国标准ATSC(Advanced Television System Committee先进电视制式委员会)、欧洲标准DVB(Digital Video Broadcasting 数字视频广播)和日本标准ISDB(Integrated Services Digital Broadcasting 综合业务数字广播)。三大标准的系统层均采用了MPEG II系统层,而视频压缩均采用MPEG II视频压缩标准。音频普遍采用MPEG 1音频的Layer II。

在MPEG-II标准中,为了将一个或更多的音频、视频或其他的基本数据流合成单个或多个数据流,以适应于存储和传送,必须对其重新进行打包编码,在码流中还需插入各种时间标记、系统控制等信息,最后送到信道编码与调制器。这样可以形成两种数据流——传输流(TS)和节目流(PS),节目流(Program stream),主要用于DVD等交互式多媒体领域;而传输流(Transport stream),主要用于数字电视广播及视频通讯等领域。表1将给出TS流和PS流的区别:

 06.jpg

表1 TS流和PS流的区别

  传输流(TS)由一道或多道节目组成,每道节目由一个或多个原始流和一些其他流复合在一起,只有一路节目的TS流,被称为SPTSSingle-program transport stream,单节目传输流)。复用了多路节目、多种信息的TS流,我们称之为MPTSMulti-program transport stream,多节目传输流)。每个MPTS流包括视频流、音频流、节目特殊信息流(PSI)和其他数据包,各种数据之间通过PSI (Program Special Info.节目特殊信息)组织起来。PSI信息中的PAT(Program Association Table节目关联表)PMT(Program Map Table节目映射表)组成一个树状索引,通过PIDPacket ID包标识)来唯一标识每一种TS帧,如图1所示。

07.JPG

图1 PSI表结构

无论是SPTS还是MPTS,其组成的分组长度都是固定的188字节,包括“分组首部”和“有效负载”,前4个字节是分组首部,包含了这个分组的一些信息。有些情况下需要更多的信息时,需在后面添加“调整字段(adaption field)”。PES分组和TS分组之间的关系是:PES分组是插入到TS分组中的,每个PES分组首部的第一字节就是TS分组有效负载的第一字节。结构图如图2所示。

   08.JPG

图2 TS流结构图

当多个TS流通过节目复用器后,即可形成MPTS流。节目复用器采用的是统计复用的方法,即当被复用的各个节目传送的码率非恒定时,各个节目之间将实行按图像复杂程度分配码率的原则。使用统计复用技术可以提高压缩效率,改进图像质量,便于在1个频道中传输多套节目,节约传输成本。



==========================================================================================


PSI/SI 之PMT表之 CA_Descriptor 查询


转自  http://www.ebhou.com/post/pmtget.html


PSI/SI PMT表之 CA_Descriptor 查询

节目映射表(PMT)提供节目号码与组成的原始码流之间的映射关系,这种映射表是一个TS流中所有节目的集合。此表在包中传送,其中PID值是由编码器或则PAT选择的。如果需要的话,可以使用多个PID值。在映射表插入到TS包之前,此映射表将按一定的语法分成一个或多个分段,由节目号码字段program_number 识别,整个PMT表的语法结构如下图所示:

 

PMT包含了与单路节目复用有关的控制信息。而单路节目的TS是有具有相同时基的多路PES流复用构成的,典型的构成包括一路视频打包的基本码流(PES)。多路音频PES 以及一路或多路辅助数据,各路PES被分配了唯一PID,PES与被分配的PID值之间的关系构成了一张PMTPMT完整的描述了一路节目是有那些PES组成的,他们的PID分别是什么等。

关于PMT基本介绍就到这里,我们今天的主要目的是从PMT表中查找对应节目的CA_Descriptor.在具体解析PMT表之前我们搞明白PMT表到底是怎么个东西再说.

PMT表分段信息:

 

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. TS_program_map_section{  
  2.     Table_id                // 8bit ,表字段ID号,对于PMT表来说该字段为0x2.   
  3.   
  4.         Section_syntax_indicator  // 1bit ,对于PMT,改字段置1    
  5.         ‘0’                    //  1bit   
  6.         Reserved               //2 bit   
  7.         Secton_length            //12bit  表示改字段的字节数,包括CRC字段。   
  8.         Program_number        //16bit   该节目对应于可应用的program_map_PID。一个节目定义仅含有一个TS的program_map_section,因此一个节目的定义长度不超过127byte   
  9.         Reserved                     //2 bit   
  10.         Version_number         //5bit    指示当前TS流中program_map_secton 的版本号。当字段有关信息发生变化时,版本号将以32为摸加1.版本号是关于节目的定义,因此版本号是关于单一段的定义。   
  11.         Current_next_indicator    //1bit   当该字段为1时表示当前传送的program_map_section可用,当该字段为0时,表示当前传送的program_map_section不可用,下一个TS的program_map_section有效。   
  12.         Section_number          //8bit   该字段总置0x00   
  13.         Last_section_number      //8 bit   该字段总为0x00   
  14.         Reserved                //3bit   
  15.         PCR_PID               // 13bit   该字段指示TS包得Pid值。该TS包含有PCR字段,而该PCR值对应于由节目号指定的节目。如果对于私有数据流的节目定义与PCR无关,该字段的值将为0xffff.   
  16.         Reserved                // 4bit   
  17.         Program_info_length       //12bit  该字段指出跟随其后的节目信息描述的字节数。注意:该长度为节目信息描述的字节数,而非 Program_info_length之后的字节到CRC_32的长度。   
  18.         for(i=0;i<N;i++){  
  19.             Descriptor(); 节目信息描述段,program_info_length 的长度就是指该字段的长度。  
  20.         }  
  21.         for(i=0;i<N1;i++){  单元流分段  
  22.             stream_type           //8bit   节目元素包的类型。即它定义了在TS包中得PES流的类型,   
  23.             如下表1:该处的PID由elementary_PID 指定,如果该字段为0x2  
  24.             时,表示ITU-T REC H 262|ISO/IEC1318-2 视频或者ISO/IEC   11172-2 带约束参数的视频流。  
  25.   
  26.             reserved              // 3 bit       
  27.             elementary_PID        //13 bit    TS包得PID值。   
  28.             reserved              //4bit   
  29.             ES_info_length        //12  指示跟随在其后的节目单元刘的私有数据长度。   
  30.             for(j=0;j<N2;j++){  
  31.   
  32.                 Descriptor()           //描述信息    
  33.             }  
  34.   
  35.         }  
  36.         CRC_32                        //32   
  37. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. TS_program_map_section{  
  2.     Table_id                // 8bit ,表字段ID号,对于PMT表来说该字段为0x2.  
  3.   
  4.         Section_syntax_indicator  // 1bit ,对于PMT,改字段置1   
  5.         ‘0’                    //  1bit  
  6.         Reserved               //2 bit  
  7.         Secton_length            //12bit  表示改字段的字节数,包括CRC字段。  
  8.         Program_number        //16bit   该节目对应于可应用的program_map_PID。一个节目定义仅含有一个TS的program_map_section,因此一个节目的定义长度不超过127byte  
  9.         Reserved                     //2 bit  
  10.         Version_number         //5bit    指示当前TS流中program_map_secton 的版本号。当字段有关信息发生变化时,版本号将以32为摸加1.版本号是关于节目的定义,因此版本号是关于单一段的定义。  
  11.         Current_next_indicator    //1bit   当该字段为1时表示当前传送的program_map_section可用,当该字段为0时,表示当前传送的program_map_section不可用,下一个TS的program_map_section有效。  
  12.         Section_number          //8bit   该字段总置0x00  
  13.         Last_section_number      //8 bit   该字段总为0x00  
  14.         Reserved                //3bit  
  15.         PCR_PID               // 13bit   该字段指示TS包得Pid值。该TS包含有PCR字段,而该PCR值对应于由节目号指定的节目。如果对于私有数据流的节目定义与PCR无关,该字段的值将为0xffff.  
  16.         Reserved                // 4bit  
  17.         Program_info_length       //12bit  该字段指出跟随其后的节目信息描述的字节数。注意:该长度为节目信息描述的字节数,而非 Program_info_length之后的字节到CRC_32的长度。  
  18.         for(i=0;i<N;i++){  
  19.             Descriptor(); 节目信息描述段,program_info_length 的长度就是指该字段的长度。  
  20.         }  
  21.         for(i=0;i<N1;i++){  单元流分段  
  22.             stream_type           //8bit   节目元素包的类型。即它定义了在TS包中得PES流的类型,  
  23.             如下表1:该处的PID由elementary_PID 指定,如果该字段为0x2  
  24.             时,表示ITU-T REC H 262|ISO/IEC1318-2 视频或者ISO/IEC   11172-2 带约束参数的视频流。  
  25.   
  26.             reserved              // 3 bit      
  27.             elementary_PID        //13 bit    TS包得PID值。  
  28.             reserved              //4bit  
  29.             ES_info_length        //12  指示跟随在其后的节目单元刘的私有数据长度。  
  30.             for(j=0;j<N2;j++){  
  31.   
  32.                 Descriptor()           //描述信息   
  33.             }  
  34.   
  35.         }  
  36.         CRC_32                        //32  
  37. }  

 

表一:流类型对应表


下面是亚太5号138卫星上一节目的pmt表:


Pmt表信息分布。


其对应的码流。

    从上表我们可以看出我们需要查找的CA_descriptor 在节目描述字段。所以我们需要先从码流中把节目描述子段提取出来。参考函数如下。

 

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. typedef struct  
  2. {  
  3.     uint8_t         tag;  
  4.     const uint8_t*  start;  
  5.     uint16_t        length; //!< Total descriptor length incl. tag and length fields.   
  6.   
  7. } PSISI_DESC_S;  
  8. *******************************************************************************  
  9. **  
  10. ** \brief   在节目子描述字段中查找CA_descriptor  
  11. ** \param   pLoop       PMT码流buf  
  12. ** \param   loopLen     节目描述子段总长度  
  13. ** \param   tagToFind   需要查找的描述字段头  
  14. ** \param   pInfo       节目描述子段结构体  
  15. **  
  16. ** \return  One of the following status codes:  
  17. **          - #FAPI_OK if successful  
  18. **          - #SMARTGO_SYS_ERR_DATA_NOT_AVL  
  19. **  
  20. *******************************************************************************  
  21. */  
  22. int32_t PSISI_DescFind(const uint8_t * pLoop, uint16_t loopLen,  
  23.                        uint8_t tagToFind, PSISI_DESC_S * pInfo)  
  24. {  
  25.     uint16_t            loopPos = 0;  
  26.     uint8_t             tag;  
  27.     uint16_t            length;  
  28.     while((uint16_t) (loopPos + 1) < loopLen)  
  29.     {  
  30.         tag = pLoop[loopPos];  
  31.         length = (uint16_t) (pLoop[loopPos + 1]) + 2;  
  32.         if(tag == tagToFind)  
  33.         {  
  34.             if(pInfo != NULL)  
  35.             {  
  36.                 pInfo->tag = tag;  
  37.                 pInfo->start = pLoop + loopPos;  
  38.                 pInfo->length = length;  
  39.             }  
  40.             return FAPI_OK;  
  41.         }  
  42.         loopPos += length;  
  43.     }  
  44.     return SMARTGO_SYS_ERR_DATA_NOT_AVL;  
  45. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. typedef struct  
  2. {  
  3.     uint8_t         tag;  
  4.     const uint8_t*  start;  
  5.     uint16_t        length; //!< Total descriptor length incl. tag and length fields.  
  6.   
  7. } PSISI_DESC_S;  
  8. *******************************************************************************  
  9. **  
  10. ** \brief   在节目子描述字段中查找CA_descriptor  
  11. ** \param   pLoop       PMT码流buf  
  12. ** \param   loopLen     节目描述子段总长度  
  13. ** \param   tagToFind   需要查找的描述字段头  
  14. ** \param   pInfo       节目描述子段结构体  
  15. **  
  16. ** \return  One of the following status codes:  
  17. **          - #FAPI_OK if successful  
  18. **          - #SMARTGO_SYS_ERR_DATA_NOT_AVL  
  19. **  
  20. *******************************************************************************  
  21. */  
  22. int32_t PSISI_DescFind(const uint8_t * pLoop, uint16_t loopLen,  
  23.                        uint8_t tagToFind, PSISI_DESC_S * pInfo)  
  24. {  
  25.     uint16_t            loopPos = 0;  
  26.     uint8_t             tag;  
  27.     uint16_t            length;  
  28.     while((uint16_t) (loopPos + 1) < loopLen)  
  29.     {  
  30.         tag = pLoop[loopPos];  
  31.         length = (uint16_t) (pLoop[loopPos + 1]) + 2;  
  32.         if(tag == tagToFind)  
  33.         {  
  34.             if(pInfo != NULL)  
  35.             {  
  36.                 pInfo->tag = tag;  
  37.                 pInfo->start = pLoop + loopPos;  
  38.                 pInfo->length = length;  
  39.             }  
  40.             return FAPI_OK;  
  41.         }  
  42.         loopPos += length;  
  43.     }  
  44.     return SMARTGO_SYS_ERR_DATA_NOT_AVL;  
  45. }  


以上节目的CA_ descriptor出现在节目描述子段中。但是有时有PMT表中并没有节目描述子段。这个时候如果有CA_ descriptor的情况下,一般会吧CA_ descriptor放在PMT中单元子流中(如下节目PMT)那么在这种情况下我们首先应该把单元流解析出来。


没有节目描述子段的PMT

对应的码流

 

对应的单元描述子流解析函数:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. typedef struct  
  2. {  
  3.     fbool_t         available;  
  4.     uint16_t        pid;  
  5.     uint16_t        program_number;  
  6.     uint8_t         version_number;  
  7.     uint8_t         buf[PMT_SECTSIZE_MAX]; //!< PMTsection buffer   
  8.     uint16_t        bufSize;  
  9.   
  10. } PSISI_PMT_S;  
  11. 注意:这里PMT结构体中得buf是指整个节目的PMT 从table_id到SRC_32的总长度。  
  12. typedef struct  
  13. {  
  14.     uint8_t         type;  
  15.     uint16_t        pid;  
  16.     const uint8_t*  esInfoStart;  
  17.     uint16_t        esInfoLen;  
  18.   
  19. } PSISI_PMT_STREAM_S;  
  20.   
  21. /*! 
  22. **************************************************************************** 
  23. ** \brief   TODO 
  24. ** \param   pPmt    TODO 
  25. ** \param   pmtPos  TODO 
  26. ** \param   pInfo   TODO 
  27. ** \return  One of the following status codes: 
  28. **          - #FAPI_OK if successful 
  29. **          - #SMARTGO_SYS_ERR_DATA_NOT_AVL 
  30. ***************************************************************************/  
  31. int32_t PSISI_PMT_StreamParseGetNext(const PSISI_PMT_S* pPmt,  
  32.                                      uint16_t* pmtPos,  
  33.                                      PSISI_PMT_STREAM_S* pInfo)  
  34. {  
  35.     if((uint16_t) (*pmtPos + 5 + CRC_LEN) > pPmt->bufSize)  
  36.     {  
  37.         return SMARTGO_SYS_ERR_DATA_NOT_AVL;  
  38.     }  
  39.   
  40.     pInfo->type        = pPmt->buf[*pmtPos];  
  41.     pInfo->pid         = LD_PID(pPmt->buf + *pmtPos + 1);  
  42.   
  43.     pInfo->esInfoLen   = LD_LEN(pPmt->buf + *pmtPos + 3);  
  44.     pInfo->esInfoStart =        pPmt->buf + *pmtPos + 5;  
  45.   
  46.     *pmtPos += 5 + pInfo->esInfoLen;  
  47.   
  48.     return FAPI_OK;  
  49. }  
  50.   
  51. 综上我们要从PMT表中获取到节目的CA_descrptor 解析函数必须要分成有节目描述子流的和没有节目描述子流的情况。而且一定要把每个流查找完。  
  52.   
  53. 具体提取函数  
  54. int32_t PSISI_ _GetCATFromPMT(uint8_t tsdIdx,)  
  55. {  
  56.   
  57.   
  58.     PSISI_TSD_S        *pTsd = psiDat->tsd + tsdIdx;  
  59.     PSISI_PMT_STREAM_S  stream;  
  60.     uint16_t            pmtPos = 0;  
  61.     int32_t             retVal;  
  62.     uint16_t                 progam_info_Len;  
  63.     PSISI_DESC_S           descr;  
  64.     uint16_t                 loopPos=0;  
  65.     int size;  
  66.   
  67.     DBG_Assert(tsdIdx < PSI_USED_TSDS);  
  68.     if(tsdIdx >= PSI_USED_TSDS)  
  69.     {  
  70.         return APPL_SMARTGO_ERR_BAD_PARAMETER;  
  71.     }  
  72.   
  73.     progam_info_Len = LD_LEN(pTsd->pmt.buf+10);  
  74.     size =0;  
  75.     if(progam_info_Len > 0)  
  76.     {  
  77.         pmtPos =12;  
  78.         while(PSISI_DescFind(pTsd->pmt.buf+pmtPos,progam_info_Len, CA_DESCRIPTOR, &descr)== FAPI_OK)  
  79.         {  
  80.             if(descr.tag == CA_DESCRIPTOR)  
  81.             {  
  82.                 printf("\n+++++++ca find\n");  
  83.                 return FAPI_OK;  
  84.             }  
  85.         }  
  86.         pmtPos =12+progam_info_Len;  
  87.   
  88.   
  89.     }      
  90.   
  91.   
  92.     while ( PSISI_PMT_StreamParseGetNext(&(pTsd->pmt), &pmtPos, &stream) == FAPI_OK )  
  93.     {  
  94.         while(PSISI_DescFind(stream.esInfoStart, stream.esInfoLen, CA_DESCRIPTOR, &descr)== FAPI_OK)  
  95.         {  
  96.             if(descr.tag == CA_DESCRIPTOR)  
  97.             {  
  98.                 printf("\n+++++++ca find\n");  
  99.                 return FAPI_OK;  
  100.             }  
  101.         }  
  102.   
  103.     }  
  104.     return FAPI_OK;  
  105. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. typedef struct  
  2. {  
  3.     fbool_t         available;  
  4.     uint16_t        pid;  
  5.     uint16_t        program_number;  
  6.     uint8_t         version_number;  
  7.     uint8_t         buf[PMT_SECTSIZE_MAX]; //!< PMTsection buffer  
  8.     uint16_t        bufSize;  
  9.   
  10. } PSISI_PMT_S;  
  11. 注意:这里PMT结构体中得buf是指整个节目的PMT 从table_id到SRC_32的总长度。  
  12. typedef struct  
  13. {  
  14.     uint8_t         type;  
  15.     uint16_t        pid;  
  16.     const uint8_t*  esInfoStart;  
  17.     uint16_t        esInfoLen;  
  18.   
  19. } PSISI_PMT_STREAM_S;  
  20.   
  21. /*! 
  22. **************************************************************************** 
  23. ** \brief   TODO 
  24. ** \param   pPmt    TODO 
  25. ** \param   pmtPos  TODO 
  26. ** \param   pInfo   TODO 
  27. ** \return  One of the following status codes: 
  28. **          - #FAPI_OK if successful 
  29. **          - #SMARTGO_SYS_ERR_DATA_NOT_AVL 
  30. ***************************************************************************/  
  31. int32_t PSISI_PMT_StreamParseGetNext(const PSISI_PMT_S* pPmt,  
  32.                                      uint16_t* pmtPos,  
  33.                                      PSISI_PMT_STREAM_S* pInfo)  
  34. {  
  35.     if((uint16_t) (*pmtPos + 5 + CRC_LEN) > pPmt->bufSize)  
  36.     {  
  37.         return SMARTGO_SYS_ERR_DATA_NOT_AVL;  
  38.     }  
  39.   
  40.     pInfo->type        = pPmt->buf[*pmtPos];  
  41.     pInfo->pid         = LD_PID(pPmt->buf + *pmtPos + 1);  
  42.   
  43.     pInfo->esInfoLen   = LD_LEN(pPmt->buf + *pmtPos + 3);  
  44.     pInfo->esInfoStart =        pPmt->buf + *pmtPos + 5;  
  45.   
  46.     *pmtPos += 5 + pInfo->esInfoLen;  
  47.   
  48.     return FAPI_OK;  
  49. }  
  50.   
  51. 综上我们要从PMT表中获取到节目的CA_descrptor 解析函数必须要分成有节目描述子流的和没有节目描述子流的情况。而且一定要把每个流查找完。  
  52.   
  53. 具体提取函数  
  54. int32_t PSISI_ _GetCATFromPMT(uint8_t tsdIdx,)  
  55. {  
  56.   
  57.   
  58.     PSISI_TSD_S        *pTsd = psiDat->tsd + tsdIdx;  
  59.     PSISI_PMT_STREAM_S  stream;  
  60.     uint16_t            pmtPos = 0;  
  61.     int32_t             retVal;  
  62.     uint16_t                 progam_info_Len;  
  63.     PSISI_DESC_S           descr;  
  64.     uint16_t                 loopPos=0;  
  65.     int size;  
  66.   
  67.     DBG_Assert(tsdIdx < PSI_USED_TSDS);  
  68.     if(tsdIdx >= PSI_USED_TSDS)  
  69.     {  
  70.         return APPL_SMARTGO_ERR_BAD_PARAMETER;  
  71.     }  
  72.   
  73.     progam_info_Len = LD_LEN(pTsd->pmt.buf+10);  
  74.     size =0;  
  75.     if(progam_info_Len > 0)  
  76.     {  
  77.         pmtPos =12;  
  78.         while(PSISI_DescFind(pTsd->pmt.buf+pmtPos,progam_info_Len, CA_DESCRIPTOR, &descr)== FAPI_OK)  
  79.         {  
  80.             if(descr.tag == CA_DESCRIPTOR)  
  81.             {  
  82.                 printf("\n+++++++ca find\n");  
  83.                 return FAPI_OK;  
  84.             }  
  85.         }  
  86.         pmtPos =12+progam_info_Len;  
  87.   
  88.   
  89.     }      
  90.   
  91.   
  92.     while ( PSISI_PMT_StreamParseGetNext(&(pTsd->pmt), &pmtPos, &stream) == FAPI_OK )  
  93.     {  
  94.         while(PSISI_DescFind(stream.esInfoStart, stream.esInfoLen, CA_DESCRIPTOR, &descr)== FAPI_OK)  
  95.         {  
  96.             if(descr.tag == CA_DESCRIPTOR)  
  97.             {  
  98.                 printf("\n+++++++ca find\n");  
  99.                 return FAPI_OK;  
  100.             }  
  101.         }  
  102.   
  103.     }  
  104.     return FAPI_OK;  
  105. }  

总结:PMT表的解析以及PMT表中所有子段的解析大概就这么多内容,虽然这里只是简单的介绍了CA_descritpor 的提取。在PMT表中还有可能找得的信息主要包括:mosaic_descriptor,stream_identifier_descriptor,subtitling_descriptor,private_date_specifier_descriptor,service_move_descriptor,CA_system_descriptor和data_broadcast_id_descriptor.。如果想了解一下可以自己试着去解一下吧。



=================================================================================================

MPEG-2 TS/PS同步原理


转自 http://blog.csdn.net/sz_liao/article/details/13665107


一、引言
MPEG2系统用于视音频同步以及系统时钟恢复的时间标签分别在ES,PES和TS这3个层次中。
  在TS 层, TS头信息包含了节目时钟参考PCR(Program Clock Reference),
               用于恢复出与编码端一致的系统时序时钟STC(System Time Clock)。     
  在PES层, 在PES头信息里包含有表示时间戳PTS(Presentation Time Stamp)和
               解码时间戳DTS(Decoding Time Stamp);
  在ES 层, 与同步有关的主要是视频缓冲验证VBV(Video Buffer Verifier),
              用以防止解码器的缓冲器出现上溢或者下溢;

标准规定在原始音频和视频流中,
  PTS的间隔不能超过0.7s,
  出现在TS包头的PCR间隔不能超过0.1s。




图1 从ES到PES的示意图

MPEG-2对视频的压缩产生I帧、P帧、B帧.
将上图所示的帧顺序 "I1-P4-B2-B3-P7-B5-B6" 表示的ES帧,
通过打包并在每个帧中插入PTS/DTS标志,组成PES.

在插入PTS/DTS标志时,
对于B帧,        由于在B帧PTS和DTS是相等的,所以无须在B帧插入DTS(参见图1).
对于I帧和P帧, 由于经过复用后, 数据包的顺序会发生变化,
                   显示前一定要存储于视频解码器的排序缓存器中,经过从新排序后再显示,
                   所以一定要同时插入PTS和DTS作为从新排序的依据.

二、同步机制
编码器
系统时钟STC:
  编码器中有一个系统时钟(其频率是27MHz),
  此时钟用来产生指示音视频的正确显示和解码的时间戳,
  同时可用来指示在采样过程中系统时钟本身的瞬时值。

PCR(Program Clock Reference):
  指示系统时钟本身的瞬时值的时间标签称为节目参考时钟标签(PCR)。

  PCR的插入必须在PCR字段的最后离开复用器的那一时刻,
  同时把27MHz系统时钟的采样瞬时值作为PCR字段插入到相应的PCR域。
  它是放在TS包头的自适应区中传送.

  27MHz的系统时钟STC经波形整理后分成两路:
    PCR_ext (9bits ),   由27MHz脉冲直接触发计数器生成扩展域.
    PCR_base(33bits), 经300分频器分频成90kHz脉冲送入一个33位计数器生成90kHz基值,
                              用于和PTS/DTS比较,产生解码和显示所需要的同步信号.
  这两部分被置入PCR域,共同组成42位的PCR.

Table 2-2 Transport packet of the Recommendation|International Standard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
==============================================================================
      syntax                                  No.of bits       Mnemonic
==============================================================================
transport_packet(){
   sync_byte
     ...
   adaptation_field_control                     2               bslbf
   continuity_counter                           4               uimsbf
   if (adaptation_field_control == '10' ||
      adaptation_field_control == '11' ){
     adaptation_field()
   }
   ...
}
==============================================================================

Table 2-6 Transport Stream adaptation field
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
==============================================================================
      syntax                                  No.of bits       Mnemonic
==============================================================================
adaptation_field(){
   adaptation_field_length                      8               uimsbf
   if (adaptation_field_length>0){
     ...
     PCR_flag                                   1               bslbf
     ...
     if (PCR_flag == '1' ){
       program_clock_reference_base             33              uimsbf
       Reserved                                 6               bslbf
       program_clock_reference_extension        9               uimsbf
       ...
     }
   }
}
==============================================================================

输入到T-STD解码器的第i个字节的PCR值:
  PCR(i) = PCR_base(i)*300 + PCR_ext(i) 
i: 包含program_clock_reference_base域的最后一个比特的字节号.

  PCR_base(i) = ((system_clock_frequency * t(i)) / 300) % 2^33
  PCR_ext(i)   = ((system_clock_frequency * t(i)) / 1   ) % 300

t(i): 字节i的编码时间.


例如:
  时间"03:02:29.012"的PCR计算如下:

  03:02:29.012 = ((3 * 60) + 2) * 60 + 29.012 = 10949.012s


  PCR_base = ((27 000 000 * 10949.012) / 300) % 2^33 = 98 541 080
  PCR_ext   = ((27 000 000 * 10949.012) / 1  ) % 300  = 0 
  PCR = 98 541 080 * 300 + 0 = 295 623 324 000

PCR-base的作用:
  a. 与PTS和DTS作比较, 当二者相同时, 相应的单元被显示或者解码.
  b. 在解码器切换节目时,提供对解码器PCR计数器的初始值,
     以让该PCR值与PTS、DTS最大可能地达到相同的时间起点.
PCR-ext的作用:
  通过解码器端的锁相环路修正解码器的系统时钟, 使其达到和编码器一致的27MHz.

PTS(Presentation Time Stamp):
  指示音视频显示时间的时间戳称为显示时间戳(PTS);

  PTS域为33bits, 是对系统时钟的300分频的时钟的计数值.
  它被编码成为3个独立的字段:
     PTS[32..30][29..15][14..0]
  表示此分组中第一个访问单元在系统目标解码器中的预定显示时间.

PTS值为:
  PTS(k) = ((system_clock_frequency * TPn(k)) / 300) % 2^33
TPn(k): 表示单元Pn(k)的表示时间.

DTS(Decoding Time Stamp):
  指示音视频的解码时间戳称为解码时间戳(DTS),
  
  DTS域为33bits,编码成为3个独立的字段:
     DTS[32..30][29..15][14..0]
  表示此分组中第一个访问单元在系统目标解码器中的预定解码时间.

DTS值为:
  DTS(j) = ((system_clock_frequency * TDn(j)) / 300) % 2^33
TDn(j): 第n个ES流的第j个存取单元An(j)的解码时间.
   
  DTS就视频来说,因为视频编码的时候用到了双向预测,
  一个图像单元被解出,并非马上就被显示,可能在存储器中留一段时间,作为其余图像单元的解码参考,
  在被参考完毕后,才被显示.

音频PTS:
  针对音频和视频的同步显示,MPEG提出了一个音频PTS.
  由于声音没有用到双向预测,它的解码次序就是它的显示次序,故它只有PTS.

Table 2-21 PES packet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
==============================================================================
      syntax                                  No.of bits       Mnemonic
==============================================================================
PES_packet(){
   packet_start_code_prefix                       24             bslbf
   stream_id                                      8              uimsbf
   PES_packet_length                              16             uimsbf
   if (stream_id != program_stream_map
   && stream_id != padding_stream
   && stream_id != private_stream_2
   && stream_id != ECM
   && stream_id != EMM
   && stream_id != program_stream_directory
   && stream_id != DSMCC_stream
   && stream_id != ITU-T REc.H.222.1 type E stream){
     '10'                                         2              bslbf
     ...
     PTS_DTS_flags                                2              bslbf
     ...
     if (PTS_DTS_flags == '10' ){
       '0010'                                     4              bslbf
       PTS[32..30]                                3              bslbf
       marker_bit                                 1              bslbf
       PTS[29..15]                                15             bslbf
       marker_bit                                 1              bslbf
       PTS[14..0]                                 15             bslbf
       marker_bit                                 1              bslbf
     }
     if (PTS_DTS_flag == '11' ){
       '0011'
       PTS[32..30]                                3              bslbf
       marker_bit                                 1              bslbf
       PTS[29..15]                                15             bslbf
       marker_bit                                 1              bslbf
       PTS[14..0]                                 15             bslbf
       marker_bit                                 1              bslbf
       '0001'                                     4              bslbf
       DTS[32..30]                                3              bslbf
       marker_bit                                 1              bslbf
       DTS[29..15]                                15             bslbf
       marker_bit                                 1              bslbf
       DTS[14..0]                                 15             bslbf
       marker_bit                                 1              bslbf
     }
     ...
   }
   ...
}
==============================================================================

VBV_delay:
  视频流延时值,
  在解码时利用视频流缓冲区把视频流缓存到相应的vbv_delay时间后,
  再启动解码器解码、显示、实现音视频的同步.
  VBV_delay存在于视频ES的头部,长度为16bit.

解码器
首先,  解析PCR, 重建和编码器同步的27MHz系统时钟, 恢复27MHz系统时钟后;
再,     通过VBV_delay(视频流延时值)的数值来确定解码的开始;
之后,  利用PES流中解码时间戳(DTS)和显示时间戳(PTS)来确定解码和显示的次序.
         用PCR来对系统时钟进行修正.

解码器同步算法总结如下:
(1).  解码器从输入码流的包头中解出时间信息PCR送入到系统时间时钟恢复电路;
      系统时间时钟恢复电路在接收到每一个新的PCR时,进行本地系统时间时钟恢复和锁相。
(2).  解复用器后,从PES包头中解出显示时间标签PTS和解码时间标签DTS,并送入到基本流解码器中。
(3).  基本流解码器在接收到新的PTS/DTS后,存入对应的FIFO(先进先处存储器)中进行管理;
      对于没有PTS/DTS的显示单元,需要对其时间标签进行插值,并送入到FIFO中管理。
(4).  每一显示单元开始解码前,用其对应的DTS与STC进行比较,当STC与DTS相等时开始解码;
(5).  每一显示单元开始显示前,用其对应的PTS与STC进行比较,当STC与PTS相等时开始显示。

三、失同步处理
27 MHz系统时钟经过300分频后,得到本地的33 bits PCR_Base, 该时钟与寄存器中当前图像的PTS/DTS进行比较,
系统软件根据比较结果做出相应的处理:
(1).  若当前的PTS/DTS比PCR计数器的值小于半帧以上,即PTS_Base≤-ΔPTS/2,
      此时说明系统解码过慢,解码器处于失步状态,应根据该帧的结构做出相应的同步调整;
(2).  若当前的PTS/DTS比PCR计数器的值在半帧时间以内,
      我们认为此时系统解码正常,立即显示/解码当前帧;
(3).  若当前的PTS/DTS大于PCR计数器的值,则此时解码器稍快,
      在这种情况下,只需等到PCR与PTS/DTS相等时,就可显示/解码。

附注: 
上面讲的都是解码器的同步机制,

对于转码来说,如ffmpeg等并不是这么做的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
本地视频TS编码推送是一种将本地视频文件转换为TS格式,并通过推送方式进行传输的技术。下面是一个简单的步骤指南来实现本地视频TS编码推送: 1. 准备本地视频文件:首先,需要准备要推送的本地视频文件。确保该文件在计算机上可访问。 2. 选择编码器:为了将视频文件转换为TS格式,需要选择一个合适的编码器。常见的编码器有FFmpeg、Handbrake等。这些编码器能够将视频文件转换为TS格式,并进行一些参数配置。 3. 安装和配置编码器:根据选择的编码器,下载并安装相应的软件。然后,根据需要的配置,设置相关参数。这些参数括视频分辨率、码率、帧率等。你也可以选择添加音频或字幕。 4. 开始编码和推送:使用选定的编码器,将本地视频文件转换为TS格式。指定输出路径和文件名,并启动编码过程。编码器会将视频文件转换为TS格式,并将其推送到指定的目标地址。 5. 配置推送目标:在编码器中,需要指定要推送到的目标地址。这可以是一个IP地址、一个统一资源定位符(URL)或其他可以识别的网络地址。确保正确地配置推送目标,并检查相关的网络设置。 6. 启动推送:一切准备就绪后,启动推送过程。编码器会开始将TS推送到指定的目标地址。在推送过程中,你可以观察到实时的推送状态和进度。 通过以上步骤,你可以将本地视频文件转换为TS格式,并实现编码推送到目标地址。这种推送方式通常用于视频直播、视频点播等应用场景中,以实现高效的视频传输和播放。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值