将H264与AAC打包Ipad可播放的TS流的总结
转自:http://www.cnblogs.com/wangqiguo/archive/2013/03/29/2987949.html
由于要在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流
==========================================================================================
转自 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数据,来完成解码或者别的什么操作。
![](https://i-blog.csdnimg.cn/blog_migrate/d0d451be9601d57987a79626ce8b7d62.jpeg)
ts文件,将每个数据包分成188个字节来发送。ts文件格式如图:
![](https://i-blog.csdnimg.cn/blog_migrate/a27c3cc3e47d8ca511628fd5db57b139.jpeg)
从图中可以看到 ts文件头分为包头和负载两部分,现在我们详细看下包头结构:
![](https://i-blog.csdnimg.cn/blog_migrate/7f8d6e0521a12fc63b700e85be4ad17e.jpeg)
sync_btye固定为0x47 ,说明从这个字节后的188个字节都属于一个ts包。 比较重要的是PID这个字段,共13位,表示了这个ts包负载数据的类型,如果没有这个信息,无法再后续寻找我们想要的数据。调整字段的作用稍后会看到。这里先跳过介绍吧,其他的字段对于ts的学习可以先不研究,不是很重要。
现在我们看看,PAT表的结构:
![](https://i-blog.csdnimg.cn/blog_migrate/fd107c41666341f2e57cd5dfd6af269f.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/234aadfadb11a76b3e13ebed9662d094.jpeg)
TS的解析工作,一般都是从找PAT表开始,所以,要先找到负载中头个字节是0x00的,就说明找到PAT表了。section_length表示从这个字段开始后有几个字节,如果不满188个字节,就用0xff填满。可以发现去掉最后4位的crc校验位从section_number之后的5个字节开始,就是这个ts文件中缩有的节目了,每两个字节代表一个节目,从中很容易获取到节目的ID信息。
获取到ID之后,就可以开始查找关于这个id的PMT表了。
PMT:
![](https://i-blog.csdnimg.cn/blog_migrate/d9ca568e85233a07dff6366c9632f87f.jpeg)
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
typedef
struct
TS_header
{
unsigned
//同步字节,固定为0x47
unsigned
//传输错误标志位,一般传输错误的话就不会处理这个包了
unsigned
//有效负载的开始标志,根据后面有效负载的内容不同功能也不同
//
unsigned
//传输优先级位,1表示高优先级
unsigned
//有效负载数据的类型
unsigned
//加密标志位,00表示未加密
unsigned
//调整字段控制,。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。
unsigned
//一个4bit的计数器,范围0-15
}
//特殊参数说明:
//sync_byte:0x47
//payload_unit_start_indicator:0x01表示含有PSI或者PES头
//PID:0x0表示后面负载内容为PAT,不同的PID表示不同的负载
//adaption_field_control:
//
//
//
//
//
int
Parse_TS_header(unsigned
char
*pTSBuf,
{
pheader->sync_byte
if
(pheader->sync_byte
return
-1;
pheader->transport_error_indicator
pheader->payload_unit_start_indicator
pheader->transport_priority
pheader->PID
pheader->transport_scrambling_control
pheader->adaption_field_control
pheader->continuity_counter
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流包含的节目信息。
//
typedef
struct
PAT_Packet_tag
{
unsigned
//固定为0x00
unsigned
//段语法标志位,固定为1
unsigned
//0
unsigned
//
unsigned
//表示这个字节后面有用的字节数,包括CRC32
unsigned
//该传输流的ID,区别于一个网络中其它多路复用的流
unsigned
//
unsigned
//范围0-31,表示PAT的版本号
unsigned
//发送的PAT是当前有效还是下一个PAT有效
unsigned
//分段的号码。PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段
unsigned
//最后一个分段的号码
//
//
unsigned
unsigned
unsigned
//
unsigned
//
}
//
int
Parse_PAT(unsigned
char
*pTSBuf,
{
TS_header
if
(Parse_TS_packet_header(pTSBuf,
return
-1;
if
(TSheader.payload_unit_start_indicator
//
{
if
(TSheader.PID
//
{
int
iBeginlen
int
adaptation_field_length
switch
(TSheader.adaption_field_control)
{
case
0x0:
//
return
-1;
case
0x1:
//
iBeginlen
//
break
;
case
0x2:
//
return
-1;
case
0x3:
//
if
(adaptation_field_length
{
iBeginlen
//
iBeginlen
//
}
else
{
iBeginlen
//
}
iBeginlen
//
break
;
default
:
break
;
}
unsigned
char
*pPAT
packet->table_id
packet->section_syntax_indicator
packet->zero
packet->reserved_1
packet->section_length
packet->transport_stream_id
packet->reserved_2
packet->version_number
packet->current_next_indicator
packet->section_number
packet->last_section_number
int
len
len
packet->CRC_32
|
|
|
int
n
for
(
{
packet->program_number
packet->reserved_3
if
(
{
packet->network_PID
}
else
{
//
program_map_PID
}
}
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数据的信息可以理解为这个节目包含的音频和视频信息。
//
typedef
struct
PMT_Packet_tag
{
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
//
//
unsigned
unsigned
unsigned
unsigned
unsigned
//
unsigned
}
//
int
Parse_PMT(unsigned
char
*pTSBuf,
{
//
//
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
{
unsigned
long
long
llpts
long
long
)(pBuf[0]
|
long
long
)(pBuf[1]
|
long
long
)(pBuf[2]
|
long
long
)(pBuf[3]
|
long
long
)(pBuf[4]
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:系统部分; MPEG2系统任务包括: ES是直接从编码器出来的数据流,可以是编码过的视频数据流,音频数据流,或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。PES包由包头和payload组成,具体格式摘录如下:
可以看到PTS/DTS是打在PES包里面的,这两个parameters是解决视音频同步显示,防止解码器输入缓存上溢或下溢的关键。PTS表示 显示单元出现在系统目标解码器(STD: system target decoder)的时间,DTS表示将存取单元全部字节从STD的ES解码缓存器移走的时刻。每个I、P、B帧的包头都有一个PTS和DTS,但PTS与DTS对B帧都是一样的,无须标出B帧的DTS。对I帧和P帧,显示前一定要存储于视频解码器的重新排序缓存器中,经过延迟(重新排序)后再显示,一定要分别标明PTS和DTS。 上面介绍过,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。
其中,可用同步字节位串的自动相关特性,检测数据流中的包限制,建立包同步;传输误码指示符,是指有不能消除误码时,采用误码校正解码器可表示1bit 的误码,但无法校正;有效载荷单元起始指示符,表示该数据包是否存在确定的起始信息;传输优先,是给TS包分配优先权;PID值是由用户确定的,解码器根据PID将TS上从不同ES来的TS包区别出来,以重建原来的ES;传输加扰控制,可指示数据包内容是否加扰,但包头和自适应区永远不加扰;自适应区控制,用2 bit表示有否自适应区,即(01)表示有有用信息无自适应区,(10)表示无有用信息有自适应区,(11)表示有有用信息有自适应区,(00)无定义;连续计数器可对PID包传送顺序计数,据计数器读数,接收端可判断是否有包丢失及包传送顺序错误。显然,包头对TS包具有同步、识别、检错及加密功能。
![]() 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)模型来对其进行实现,基本思想如下:
其过程可参见下图:
|
![](http://hi.csdn.net/attachment/201007/14/0_12791033459wdW.gif)
====================================================================================
浅析多节目传输流(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流的区别:
表1 TS流和PS流的区别
传输流(TS)由一道或多道节目组成,每道节目由一个或多个原始流和一些其他流复合在一起,只有一路节目的TS流,被称为SPTS(Single-program transport stream,单节目传输流)。复用了多路节目、多种信息的TS流,我们称之为MPTS(Multi-program transport stream,多节目传输流)。每个MPTS流包括视频流、音频流、节目特殊信息流(PSI)和其他数据包,各种数据之间通过PSI (Program Special Info.节目特殊信息)组织起来。PSI信息中的PAT(Program Association Table节目关联表)和PMT(Program Map Table节目映射表)组成一个树状索引,通过PID(Packet ID包标识)来唯一标识每一种TS帧,如图1所示。
图1 PSI表结构
无论是SPTS还是MPTS,其组成的分组长度都是固定的188字节,包括“分组首部”和“有效负载”,前4个字节是分组首部,包含了这个分组的一些信息。有些情况下需要更多的信息时,需在后面添加“调整字段(adaption field)”。PES分组和TS分组之间的关系是:PES分组是插入到TS分组中的,每个PES分组首部的第一字节就是TS分组有效负载的第一字节。结构图如图2所示。
图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值之间的关系构成了一张PMT。PMT完整的描述了一路节目是有那些PES组成的,他们的PID分别是什么等。
关于PMT基本介绍就到这里,我们今天的主要目的是从PMT表中查找对应节目的CA_Descriptor.在具体解析PMT表之前我们搞明白PMT表到底是怎么个东西再说.
PMT表分段信息:
- TS_program_map_section{
- Table_id // 8bit ,表字段ID号,对于PMT表来说该字段为0x2.
- Section_syntax_indicator // 1bit ,对于PMT,改字段置1
- ‘0’ // 1bit
- Reserved //2 bit
- Secton_length //12bit 表示改字段的字节数,包括CRC字段。
- Program_number //16bit 该节目对应于可应用的program_map_PID。一个节目定义仅含有一个TS的program_map_section,因此一个节目的定义长度不超过127byte
- Reserved //2 bit
- Version_number //5bit 指示当前TS流中program_map_secton 的版本号。当字段有关信息发生变化时,版本号将以32为摸加1.版本号是关于节目的定义,因此版本号是关于单一段的定义。
- Current_next_indicator //1bit 当该字段为1时表示当前传送的program_map_section可用,当该字段为0时,表示当前传送的program_map_section不可用,下一个TS的program_map_section有效。
- Section_number //8bit 该字段总置0x00
- Last_section_number //8 bit 该字段总为0x00
- Reserved //3bit
- PCR_PID // 13bit 该字段指示TS包得Pid值。该TS包含有PCR字段,而该PCR值对应于由节目号指定的节目。如果对于私有数据流的节目定义与PCR无关,该字段的值将为0xffff.
- Reserved // 4bit
- Program_info_length //12bit 该字段指出跟随其后的节目信息描述的字节数。注意:该长度为节目信息描述的字节数,而非 Program_info_length之后的字节到CRC_32的长度。
- for(i=0;i<N;i++){
- Descriptor(); 节目信息描述段,program_info_length 的长度就是指该字段的长度。
- }
- for(i=0;i<N1;i++){ 单元流分段
- stream_type //8bit 节目元素包的类型。即它定义了在TS包中得PES流的类型,
- 如下表1:该处的PID由elementary_PID 指定,如果该字段为0x2
- 时,表示ITU-T REC H 262|ISO/IEC1318-2 视频或者ISO/IEC 11172-2 带约束参数的视频流。
- reserved // 3 bit
- elementary_PID //13 bit TS包得PID值。
- reserved //4bit
- ES_info_length //12 指示跟随在其后的节目单元刘的私有数据长度。
- for(j=0;j<N2;j++){
- Descriptor() //描述信息
- }
- }
- CRC_32 //32
- }
- TS_program_map_section{
- Table_id // 8bit ,表字段ID号,对于PMT表来说该字段为0x2.
- Section_syntax_indicator // 1bit ,对于PMT,改字段置1
- ‘0’ // 1bit
- Reserved //2 bit
- Secton_length //12bit 表示改字段的字节数,包括CRC字段。
- Program_number //16bit 该节目对应于可应用的program_map_PID。一个节目定义仅含有一个TS的program_map_section,因此一个节目的定义长度不超过127byte
- Reserved //2 bit
- Version_number //5bit 指示当前TS流中program_map_secton 的版本号。当字段有关信息发生变化时,版本号将以32为摸加1.版本号是关于节目的定义,因此版本号是关于单一段的定义。
- Current_next_indicator //1bit 当该字段为1时表示当前传送的program_map_section可用,当该字段为0时,表示当前传送的program_map_section不可用,下一个TS的program_map_section有效。
- Section_number //8bit 该字段总置0x00
- Last_section_number //8 bit 该字段总为0x00
- Reserved //3bit
- PCR_PID // 13bit 该字段指示TS包得Pid值。该TS包含有PCR字段,而该PCR值对应于由节目号指定的节目。如果对于私有数据流的节目定义与PCR无关,该字段的值将为0xffff.
- Reserved // 4bit
- Program_info_length //12bit 该字段指出跟随其后的节目信息描述的字节数。注意:该长度为节目信息描述的字节数,而非 Program_info_length之后的字节到CRC_32的长度。
- for(i=0;i<N;i++){
- Descriptor(); 节目信息描述段,program_info_length 的长度就是指该字段的长度。
- }
- for(i=0;i<N1;i++){ 单元流分段
- stream_type //8bit 节目元素包的类型。即它定义了在TS包中得PES流的类型,
- 如下表1:该处的PID由elementary_PID 指定,如果该字段为0x2
- 时,表示ITU-T REC H 262|ISO/IEC1318-2 视频或者ISO/IEC 11172-2 带约束参数的视频流。
- reserved // 3 bit
- elementary_PID //13 bit TS包得PID值。
- reserved //4bit
- ES_info_length //12 指示跟随在其后的节目单元刘的私有数据长度。
- for(j=0;j<N2;j++){
- Descriptor() //描述信息
- }
- }
- CRC_32 //32
- }
TS_program_map_section{
Table_id // 8bit ,表字段ID号,对于PMT表来说该字段为0x2.
Section_syntax_indicator // 1bit ,对于PMT,改字段置1
‘0’ // 1bit
Reserved //2 bit
Secton_length //12bit 表示改字段的字节数,包括CRC字段。
Program_number //16bit 该节目对应于可应用的program_map_PID。一个节目定义仅含有一个TS的program_map_section,因此一个节目的定义长度不超过127byte
Reserved //2 bit
Version_number //5bit 指示当前TS流中program_map_secton 的版本号。当字段有关信息发生变化时,版本号将以32为摸加1.版本号是关于节目的定义,因此版本号是关于单一段的定义。
Current_next_indicator //1bit 当该字段为1时表示当前传送的program_map_section可用,当该字段为0时,表示当前传送的program_map_section不可用,下一个TS的program_map_section有效。
Section_number //8bit 该字段总置0x00
Last_section_number //8 bit 该字段总为0x00
Reserved //3bit
PCR_PID // 13bit 该字段指示TS包得Pid值。该TS包含有PCR字段,而该PCR值对应于由节目号指定的节目。如果对于私有数据流的节目定义与PCR无关,该字段的值将为0xffff.
Reserved // 4bit
Program_info_length //12bit 该字段指出跟随其后的节目信息描述的字节数。注意:该长度为节目信息描述的字节数,而非 Program_info_length之后的字节到CRC_32的长度。
for(i=0;i<N;i++){
Descriptor(); 节目信息描述段,program_info_length 的长度就是指该字段的长度。
}
for(i=0;i<N1;i++){ 单元流分段
stream_type //8bit 节目元素包的类型。即它定义了在TS包中得PES流的类型,
如下表1:该处的PID由elementary_PID 指定,如果该字段为0x2
时,表示ITU-T REC H 262|ISO/IEC1318-2 视频或者ISO/IEC 11172-2 带约束参数的视频流。
reserved // 3 bit
elementary_PID //13 bit TS包得PID值。
reserved //4bit
ES_info_length //12 指示跟随在其后的节目单元刘的私有数据长度。
for(j=0;j<N2;j++){
Descriptor() //描述信息
}
}
CRC_32 //32
}
表一:流类型对应表
下面是亚太5号138卫星上一节目的pmt表:
Pmt表信息分布。
其对应的码流。
从上表我们可以看出我们需要查找的CA_descriptor 在节目描述字段。所以我们需要先从码流中把节目描述子段提取出来。参考函数如下。
- typedef struct
- {
- uint8_t tag;
- const uint8_t* start;
- uint16_t length; //!< Total descriptor length incl. tag and length fields.
- } PSISI_DESC_S;
- *******************************************************************************
- **
- ** \brief 在节目子描述字段中查找CA_descriptor
- ** \param pLoop PMT码流buf
- ** \param loopLen 节目描述子段总长度
- ** \param tagToFind 需要查找的描述字段头
- ** \param pInfo 节目描述子段结构体
- **
- ** \return One of the following status codes:
- ** - #FAPI_OK if successful
- ** - #SMARTGO_SYS_ERR_DATA_NOT_AVL
- **
- *******************************************************************************
- */
- int32_t PSISI_DescFind(const uint8_t * pLoop, uint16_t loopLen,
- uint8_t tagToFind, PSISI_DESC_S * pInfo)
- {
- uint16_t loopPos = 0;
- uint8_t tag;
- uint16_t length;
- while((uint16_t) (loopPos + 1) < loopLen)
- {
- tag = pLoop[loopPos];
- length = (uint16_t) (pLoop[loopPos + 1]) + 2;
- if(tag == tagToFind)
- {
- if(pInfo != NULL)
- {
- pInfo->tag = tag;
- pInfo->start = pLoop + loopPos;
- pInfo->length = length;
- }
- return FAPI_OK;
- }
- loopPos += length;
- }
- return SMARTGO_SYS_ERR_DATA_NOT_AVL;
- }
- typedef struct
- {
- uint8_t tag;
- const uint8_t* start;
- uint16_t length; //!< Total descriptor length incl. tag and length fields.
- } PSISI_DESC_S;
- *******************************************************************************
- **
- ** \brief 在节目子描述字段中查找CA_descriptor
- ** \param pLoop PMT码流buf
- ** \param loopLen 节目描述子段总长度
- ** \param tagToFind 需要查找的描述字段头
- ** \param pInfo 节目描述子段结构体
- **
- ** \return One of the following status codes:
- ** - #FAPI_OK if successful
- ** - #SMARTGO_SYS_ERR_DATA_NOT_AVL
- **
- *******************************************************************************
- */
- int32_t PSISI_DescFind(const uint8_t * pLoop, uint16_t loopLen,
- uint8_t tagToFind, PSISI_DESC_S * pInfo)
- {
- uint16_t loopPos = 0;
- uint8_t tag;
- uint16_t length;
- while((uint16_t) (loopPos + 1) < loopLen)
- {
- tag = pLoop[loopPos];
- length = (uint16_t) (pLoop[loopPos + 1]) + 2;
- if(tag == tagToFind)
- {
- if(pInfo != NULL)
- {
- pInfo->tag = tag;
- pInfo->start = pLoop + loopPos;
- pInfo->length = length;
- }
- return FAPI_OK;
- }
- loopPos += length;
- }
- return SMARTGO_SYS_ERR_DATA_NOT_AVL;
- }
typedef struct
{
uint8_t tag;
const uint8_t* start;
uint16_t length; //!< Total descriptor length incl. tag and length fields.
} PSISI_DESC_S;
*******************************************************************************
**
** \brief 在节目子描述字段中查找CA_descriptor
** \param pLoop PMT码流buf
** \param loopLen 节目描述子段总长度
** \param tagToFind 需要查找的描述字段头
** \param pInfo 节目描述子段结构体
**
** \return One of the following status codes:
** - #FAPI_OK if successful
** - #SMARTGO_SYS_ERR_DATA_NOT_AVL
**
*******************************************************************************
*/
int32_t PSISI_DescFind(const uint8_t * pLoop, uint16_t loopLen,
uint8_t tagToFind, PSISI_DESC_S * pInfo)
{
uint16_t loopPos = 0;
uint8_t tag;
uint16_t length;
while((uint16_t) (loopPos + 1) < loopLen)
{
tag = pLoop[loopPos];
length = (uint16_t) (pLoop[loopPos + 1]) + 2;
if(tag == tagToFind)
{
if(pInfo != NULL)
{
pInfo->tag = tag;
pInfo->start = pLoop + loopPos;
pInfo->length = length;
}
return FAPI_OK;
}
loopPos += length;
}
return SMARTGO_SYS_ERR_DATA_NOT_AVL;
}
以上节目的CA_ descriptor出现在节目描述子段中。但是有时有PMT表中并没有节目描述子段。这个时候如果有CA_ descriptor的情况下,一般会吧CA_ descriptor放在PMT中单元子流中(如下节目PMT)那么在这种情况下我们首先应该把单元流解析出来。
没有节目描述子段的PMT
对应的码流
对应的单元描述子流解析函数:
- typedef struct
- {
- fbool_t available;
- uint16_t pid;
- uint16_t program_number;
- uint8_t version_number;
- uint8_t buf[PMT_SECTSIZE_MAX]; //!< PMTsection buffer
- uint16_t bufSize;
- } PSISI_PMT_S;
- 注意:这里PMT结构体中得buf是指整个节目的PMT 从table_id到SRC_32的总长度。
- typedef struct
- {
- uint8_t type;
- uint16_t pid;
- const uint8_t* esInfoStart;
- uint16_t esInfoLen;
- } PSISI_PMT_STREAM_S;
- /*!
- ****************************************************************************
- ** \brief TODO
- ** \param pPmt TODO
- ** \param pmtPos TODO
- ** \param pInfo TODO
- ** \return One of the following status codes:
- ** - #FAPI_OK if successful
- ** - #SMARTGO_SYS_ERR_DATA_NOT_AVL
- ***************************************************************************/
- int32_t PSISI_PMT_StreamParseGetNext(const PSISI_PMT_S* pPmt,
- uint16_t* pmtPos,
- PSISI_PMT_STREAM_S* pInfo)
- {
- if((uint16_t) (*pmtPos + 5 + CRC_LEN) > pPmt->bufSize)
- {
- return SMARTGO_SYS_ERR_DATA_NOT_AVL;
- }
- pInfo->type = pPmt->buf[*pmtPos];
- pInfo->pid = LD_PID(pPmt->buf + *pmtPos + 1);
- pInfo->esInfoLen = LD_LEN(pPmt->buf + *pmtPos + 3);
- pInfo->esInfoStart = pPmt->buf + *pmtPos + 5;
- *pmtPos += 5 + pInfo->esInfoLen;
- return FAPI_OK;
- }
- 综上我们要从PMT表中获取到节目的CA_descrptor 解析函数必须要分成有节目描述子流的和没有节目描述子流的情况。而且一定要把每个流查找完。
- 具体提取函数
- int32_t PSISI_ _GetCATFromPMT(uint8_t tsdIdx,)
- {
- PSISI_TSD_S *pTsd = psiDat->tsd + tsdIdx;
- PSISI_PMT_STREAM_S stream;
- uint16_t pmtPos = 0;
- int32_t retVal;
- uint16_t progam_info_Len;
- PSISI_DESC_S descr;
- uint16_t loopPos=0;
- int size;
- DBG_Assert(tsdIdx < PSI_USED_TSDS);
- if(tsdIdx >= PSI_USED_TSDS)
- {
- return APPL_SMARTGO_ERR_BAD_PARAMETER;
- }
- progam_info_Len = LD_LEN(pTsd->pmt.buf+10);
- size =0;
- if(progam_info_Len > 0)
- {
- pmtPos =12;
- while(PSISI_DescFind(pTsd->pmt.buf+pmtPos,progam_info_Len, CA_DESCRIPTOR, &descr)== FAPI_OK)
- {
- if(descr.tag == CA_DESCRIPTOR)
- {
- printf("\n+++++++ca find\n");
- return FAPI_OK;
- }
- }
- pmtPos =12+progam_info_Len;
- }
- while ( PSISI_PMT_StreamParseGetNext(&(pTsd->pmt), &pmtPos, &stream) == FAPI_OK )
- {
- while(PSISI_DescFind(stream.esInfoStart, stream.esInfoLen, CA_DESCRIPTOR, &descr)== FAPI_OK)
- {
- if(descr.tag == CA_DESCRIPTOR)
- {
- printf("\n+++++++ca find\n");
- return FAPI_OK;
- }
- }
- }
- return FAPI_OK;
- }
- typedef struct
- {
- fbool_t available;
- uint16_t pid;
- uint16_t program_number;
- uint8_t version_number;
- uint8_t buf[PMT_SECTSIZE_MAX]; //!< PMTsection buffer
- uint16_t bufSize;
- } PSISI_PMT_S;
- 注意:这里PMT结构体中得buf是指整个节目的PMT 从table_id到SRC_32的总长度。
- typedef struct
- {
- uint8_t type;
- uint16_t pid;
- const uint8_t* esInfoStart;
- uint16_t esInfoLen;
- } PSISI_PMT_STREAM_S;
- /*!
- ****************************************************************************
- ** \brief TODO
- ** \param pPmt TODO
- ** \param pmtPos TODO
- ** \param pInfo TODO
- ** \return One of the following status codes:
- ** - #FAPI_OK if successful
- ** - #SMARTGO_SYS_ERR_DATA_NOT_AVL
- ***************************************************************************/
- int32_t PSISI_PMT_StreamParseGetNext(const PSISI_PMT_S* pPmt,
- uint16_t* pmtPos,
- PSISI_PMT_STREAM_S* pInfo)
- {
- if((uint16_t) (*pmtPos + 5 + CRC_LEN) > pPmt->bufSize)
- {
- return SMARTGO_SYS_ERR_DATA_NOT_AVL;
- }
- pInfo->type = pPmt->buf[*pmtPos];
- pInfo->pid = LD_PID(pPmt->buf + *pmtPos + 1);
- pInfo->esInfoLen = LD_LEN(pPmt->buf + *pmtPos + 3);
- pInfo->esInfoStart = pPmt->buf + *pmtPos + 5;
- *pmtPos += 5 + pInfo->esInfoLen;
- return FAPI_OK;
- }
- 综上我们要从PMT表中获取到节目的CA_descrptor 解析函数必须要分成有节目描述子流的和没有节目描述子流的情况。而且一定要把每个流查找完。
- 具体提取函数
- int32_t PSISI_ _GetCATFromPMT(uint8_t tsdIdx,)
- {
- PSISI_TSD_S *pTsd = psiDat->tsd + tsdIdx;
- PSISI_PMT_STREAM_S stream;
- uint16_t pmtPos = 0;
- int32_t retVal;
- uint16_t progam_info_Len;
- PSISI_DESC_S descr;
- uint16_t loopPos=0;
- int size;
- DBG_Assert(tsdIdx < PSI_USED_TSDS);
- if(tsdIdx >= PSI_USED_TSDS)
- {
- return APPL_SMARTGO_ERR_BAD_PARAMETER;
- }
- progam_info_Len = LD_LEN(pTsd->pmt.buf+10);
- size =0;
- if(progam_info_Len > 0)
- {
- pmtPos =12;
- while(PSISI_DescFind(pTsd->pmt.buf+pmtPos,progam_info_Len, CA_DESCRIPTOR, &descr)== FAPI_OK)
- {
- if(descr.tag == CA_DESCRIPTOR)
- {
- printf("\n+++++++ca find\n");
- return FAPI_OK;
- }
- }
- pmtPos =12+progam_info_Len;
- }
- while ( PSISI_PMT_StreamParseGetNext(&(pTsd->pmt), &pmtPos, &stream) == FAPI_OK )
- {
- while(PSISI_DescFind(stream.esInfoStart, stream.esInfoLen, CA_DESCRIPTOR, &descr)== FAPI_OK)
- {
- if(descr.tag == CA_DESCRIPTOR)
- {
- printf("\n+++++++ca find\n");
- return FAPI_OK;
- }
- }
- }
- return FAPI_OK;
- }
typedef struct
{
fbool_t available;
uint16_t pid;
uint16_t program_number;
uint8_t version_number;
uint8_t buf[PMT_SECTSIZE_MAX]; //!< PMTsection buffer
uint16_t bufSize;
} PSISI_PMT_S;
注意:这里PMT结构体中得buf是指整个节目的PMT 从table_id到SRC_32的总长度。
typedef struct
{
uint8_t type;
uint16_t pid;
const uint8_t* esInfoStart;
uint16_t esInfoLen;
} PSISI_PMT_STREAM_S;
/*!
****************************************************************************
** \brief TODO
** \param pPmt TODO
** \param pmtPos TODO
** \param pInfo TODO
** \return One of the following status codes:
** - #FAPI_OK if successful
** - #SMARTGO_SYS_ERR_DATA_NOT_AVL
***************************************************************************/
int32_t PSISI_PMT_StreamParseGetNext(const PSISI_PMT_S* pPmt,
uint16_t* pmtPos,
PSISI_PMT_STREAM_S* pInfo)
{
if((uint16_t) (*pmtPos + 5 + CRC_LEN) > pPmt->bufSize)
{
return SMARTGO_SYS_ERR_DATA_NOT_AVL;
}
pInfo->type = pPmt->buf[*pmtPos];
pInfo->pid = LD_PID(pPmt->buf + *pmtPos + 1);
pInfo->esInfoLen = LD_LEN(pPmt->buf + *pmtPos + 3);
pInfo->esInfoStart = pPmt->buf + *pmtPos + 5;
*pmtPos += 5 + pInfo->esInfoLen;
return FAPI_OK;
}
综上我们要从PMT表中获取到节目的CA_descrptor 解析函数必须要分成有节目描述子流的和没有节目描述子流的情况。而且一定要把每个流查找完。
具体提取函数
int32_t PSISI_ _GetCATFromPMT(uint8_t tsdIdx,)
{
PSISI_TSD_S *pTsd = psiDat->tsd + tsdIdx;
PSISI_PMT_STREAM_S stream;
uint16_t pmtPos = 0;
int32_t retVal;
uint16_t progam_info_Len;
PSISI_DESC_S descr;
uint16_t loopPos=0;
int size;
DBG_Assert(tsdIdx < PSI_USED_TSDS);
if(tsdIdx >= PSI_USED_TSDS)
{
return APPL_SMARTGO_ERR_BAD_PARAMETER;
}
progam_info_Len = LD_LEN(pTsd->pmt.buf+10);
size =0;
if(progam_info_Len > 0)
{
pmtPos =12;
while(PSISI_DescFind(pTsd->pmt.buf+pmtPos,progam_info_Len, CA_DESCRIPTOR, &descr)== FAPI_OK)
{
if(descr.tag == CA_DESCRIPTOR)
{
printf("\n+++++++ca find\n");
return FAPI_OK;
}
}
pmtPos =12+progam_info_Len;
}
while ( PSISI_PMT_StreamParseGetNext(&(pTsd->pmt), &pmtPos, &stream) == FAPI_OK )
{
while(PSISI_DescFind(stream.esInfoStart, stream.esInfoLen, CA_DESCRIPTOR, &descr)== FAPI_OK)
{
if(descr.tag == CA_DESCRIPTOR)
{
printf("\n+++++++ca find\n");
return FAPI_OK;
}
}
}
return FAPI_OK;
}
总结: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
==============================================================================
transport_packet(){
sync_byte
...
adaptation_field_control
continuity_counter
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
==============================================================================
adaptation_field(){
adaptation_field_length
if
(adaptation_field_length>0){
...
PCR_flag
...
if
(PCR_flag
'1'
){
program_clock_reference_base
Reserved
program_clock_reference_extension
...
}
}
}
==============================================================================
|
输入到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
==============================================================================
PES_packet(){
packet_start_code_prefix
stream_id
PES_packet_length
if
(stream_id
&&
&&
&&
&&
&&
&&
&&
'10'
2
...
PTS_DTS_flags
...
if
(PTS_DTS_flags
'10'
){
'0010'
4
PTS[32..30]
marker_bit
PTS[29..15]
marker_bit
PTS[14..0]
marker_bit
}
if
(PTS_DTS_flag
'11'
){
'0011'
PTS[32..30]
marker_bit
PTS[29..15]
marker_bit
PTS[14..0]
marker_bit
'0001'
4
DTS[32..30]
marker_bit
DTS[29..15]
marker_bit
DTS[14..0]
marker_bit
}
...
}
...
}
==============================================================================
|
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等并不是这么做的。