H.264 打包 MPEG-TS 流
来源:互联网 发布:数控车床计算软件下载 编辑:程序博客网 时间:2024/06/08 17:50
H.264 打包 MPEG-TS 流
– 作者 Amour Wang
1.简要说明
本文主要介绍了H264打包成MPEG-TS 流的关键部分,及中间碰到的一些问题。至于H264 和TS 流的相关标准这边不再做详细介绍。
2.H264 打包TS 流过程
TS 流组成说明(这边针对本文例子中的情况,其他情况参照TS 标准):
TS 流以包为单位,每个包的大小为188,主要包含了几种不同类型的包
1. PAT 表:这个表定义了当前TS流中所有的节目,其PID为0x0000,它是PSI的根节点,要查寻找节目必须从PAT表开始查找。主要包含了TS 流 ID,节目频道号,PMT 的ID
2. PMT 表 : 节目映射表,包含频道中所有的PID信息,根据PID 就可以从包中过滤出对应的视频或音频数据
3. PES:简单说PES 就是对H264 的描述的一些信息的标准
打包过程:
1. TS header: 所有包都是以4 个字节的包头开始的,针对比较难理解的部分说明一下
负载单元开始:这个文档上写的很官方,其实就是当一段数据超过一个包的大小必须分成几个包,第一个包的这个标志为1,其他包为0.
PID: 用来表示这个包的类型,这个好理解,但是是重要的标志位,所以强调一下
自适应区域控制:目前只用到0x3 和 0x1,分别表示有和没有自适应区域
连续计数器:这个地方要注意一下,各个类型的包的计数是各自计数的
2. PAT构造,这个部分大部分数据都是比较固定的,自己定义一下PMT 的ID ,其他基本就按照标准进行填写就行,这边PAT 比较简单所以一个包负载就够了.
int ts_pat_header(char *buf) { BITS_BUFFER_S bits; if (!buf) { return 0; } bits_initwrite(&bits, 32, (unsigned char *) buf); bits_write(&bits, 8, 0x00); // table id, 固定为0x00 bits_write(&bits, 1, 1); // section syntax indicator, 固定为1 bits_write(&bits, 1, 0); // zero, 0 bits_write(&bits, 2, 0x03); // reserved1, 固定为0x03 bits_write(&bits, 12, 0x0D); // section length, 表示这个字节后面有用的字节数, 包括CRC32 bits_write(&bits, 16, 0x0001); // transport stream id, 用来区别其他的TS流 bits_write(&bits, 2, 0x03); // reserved2, 固定为0x03 bits_write(&bits, 5, 0x00); // version number, 范围0-31 bits_write(&bits, 1, 1); // current next indicator, 0 下一个表有效, 1当前传送的PAT表可以使用 bits_write(&bits, 8, 0x00); // section number, PAT可能分为多段传输,第一段为00 bits_write(&bits, 8, 0x00); // last section number bits_write(&bits, 16, 0x0001); // program number bits_write(&bits, 3, 0x07); // reserved3和pmt_pid是一组,共有几个频道由program number指示 bits_write(&bits, 13, TS_PID_PMT); // pmt of pid in ts head 这边可以自己定义PMT 的ID char * crc = CRC32(buf); //计算CRC bits_write(&bits, 8, crc[0]); bits_write(&bits, 8, crc[1]); bits_write(&bits, 8, crc[2]); bits_write(&bits, 8, crc[3]); bits_align(&bits); return bits.i_data;}
3.PMT 构造:这部分也是没有多大问题,大部分按照标准填写都没有问题,自己定义一下 video 的PID 即可.
这边还有一个问题,并不是每一帧H264 开头都必须有PAT 和PMT ,因为每个H264 的帧数据差别非常多,可能有的才1包数据就够了,有的需要几百包,而PAT 和PMT 是隔段时间就出现在ts 流中的.
所以我这边参照FFMPEG 中的方式,每隔40个TS 包插入一个PAT 和PMT.
int ts_pmt_header(char *buf) { BITS_BUFFER_S bits; if (!buf) { return 0; } bits_initwrite(&bits, 32, (unsigned char *) buf); bits_write(&bits, 8, 0x02); // table id, 固定为0x02 bits_write(&bits, 1, 1); // section syntax indicator, 固定为1 bits_write(&bits, 1, 0); // zero, 0 bits_write(&bits, 2, 0x03); // reserved1, 固定为0x03 bits_write(&bits, 12, 0x12); // section length, 表示这个字节后面有用的字节数, 包括CRC32 bits_write(&bits, 16, 0x0001); // program number, 表示当前的PMT关联到的频道号码 bits_write(&bits, 2, 0x03); // reserved2, 固定为0x03 bits_write(&bits, 5, 0x00); // version number, 范围0-31 bits_write(&bits, 1, 1); // current next indicator, 0 下一个表有效, 1当前传送的PAT表可以使用 bits_write(&bits, 8, 0x00); // section number, PAT可能分为多段传输,第一段为00 bits_write(&bits, 8, 0x00); // last section number bits_write(&bits, 3, 0x07); // reserved3, 固定为0x07 bits_write(&bits, 13, TS_PID_VIDEO); // pcr of pid in ts head, 如果对于私有数据流的节目定义与PCR无关,这个域的值将为0x1FFF bits_write(&bits, 4, 0x0F); // reserved4, 固定为0x0F bits_write(&bits, 12, 0x00); // program info length, 前两位bit为00 bits_write(&bits, 8, TS_PMT_STREAMTYPE_H264_VIDEO); // stream type, 标志是Video还是Audio还是其他数据 bits_write(&bits, 3, 0x07); // reserved, 固定为0x07 bits_write(&bits, 13, TS_PID_VIDEO); // elementary of pid in ts head,这边可以定义 bits_write(&bits, 4, 0x0F); // reserved, 固定为0x0F bits_write(&bits, 12, 0x00); // elementary stream info length, 前两位bit为00 char * crc = CRC32(buf); //计算CRC bits_write(&bits, 8, crc[0]); bits_write(&bits, 8, crc[1]); bits_write(&bits, 8, crc[2]); bits_write(&bits, 8, crc[3]); bits_align(&bits); return bits.i_data;}
4.PES 构造:这部分是最麻烦的,也是坑最多的地方.
<1> PES 数据并不是自己一个包,后面不要填FF,而是PES所在的包,后面直接跟H264数据
<2> 当H264 数据不足一个包,或者末端数据不足一个包的时候,不是先填数据再补0xFF,而是先补0xFF到剩下刚好足够填剩下数据的空位.
<3>PCR /PTS 换算的问题: 由于PCR和PTS 有33个位组成,如果直接使用int 类型只有4个字节不够,而long 有8个字节, 需要注意大小端的问题, 在arm 下测试是小端模式,所以需要进行转换
<4>PTS 计算方法:
static uint32_t video_frame_rate = 30;static uint32_t video_pts_increment = 90000 / video_frame_rate; //用一秒钟除以帧率,得到每一帧应该耗时是多少,单位是 timescale单位static uint64_t video_pts = 0;
<5>
int mk_pes_packet(char *buf, int bVideo, int length, int bDtsEn, unsigned int pts, unsigned int dts) { PES_HEAD_S pesHead; //pes 头 PES_OPTION_S pesOption; //标志位 PES_PTS_S pesPts; //pts PES_PTS_S pesDts; //dts if (!buf) { return 0; } memset(&pesHead, 0, sizeof(pesHead)); memset(&pesOption, 0, sizeof(pesOption)); memset(&pesPts, 0, sizeof(pesPts)); memset(&pesDts, 0, sizeof(pesDts)); pesHead.startcode = htonl(0x000001) >> 8; //大小端问题 pesHead.stream_id = bVideo ? 0xE0 : 0xC0; //如果是video 则为E0 //if (PES_MAX_SIZE < length) { pesHead.pack_len = 0; //这边本来应该需要填写PES 的长度,超过的话可以填0,但是参照FFMPEG 是直接填的0,具体测试播放器也都可以兼容 /* } else { pesHead.pack_len = htons( length + sizeof(pesOption) + sizeof(pesPts) + (bDtsEn ? sizeof(pesDts) : 0)); }*/ pesOption.fixed = 0x02; pesOption.pts_dts = bDtsEn ? 0x03 : 0x02; pesOption.head_len = sizeof(pesPts) + (bDtsEn ? sizeof(pesDts) : 0); //PTS 和 DTS 的填充,注意大小端的问题 pesPts.fixed2 = pesPts.fixed3 = pesPts.fixed4 = 0x01; pesPts.fixed1 = bDtsEn ? 0x03 : 0x02; pesPts.ts1 = (pts >> 30) & 0x07; pesPts.ts2 = (pts >> 22) & 0xFF; pesPts.ts3 = (pts >> 15) & 0x7F; pesPts.ts4 = (pts >> 7) & 0xFF; pesPts.ts5 = pts & 0x7F; pesDts.fixed1 = pesDts.fixed2 = pesDts.fixed3 = pesDts.fixed4 = 0x01; pesDts.ts1 = (dts >> 30) & 0x07; pesDts.ts2 = (dts >> 22) & 0xFF; pesDts.ts3 = (dts >> 15) & 0x7F; pesDts.ts4 = (dts >> 7) & 0xFF; pesDts.ts5 = dts & 0x7F; char *head = buf; memcpy(head, &pesHead, sizeof(pesHead)); head += sizeof(pesHead); memcpy(head, &pesOption, sizeof(pesOption)); head += sizeof(pesOption); memcpy(head, &pesPts, sizeof(pesPts)); head += sizeof(pesPts); if (bDtsEn) { memcpy(head, &pesDts, sizeof(pesDts)); head += sizeof(pesPts); } return (head - buf);}
参考文献:
http://www.cnblogs.com/lifan3a/articles/6214419.html
http://blog.csdn.net/u013354805/article/details/51591229
http://blog.csdn.net/u013354805/article/details/51578457
http://blog.csdn.net/u013354805/article/details/51586086
- H.264 打包 MPEG-TS 流
- MPEG-2 TS流分析
- H.264/MPEG-4
- MPEG-2 TS码流分析
- MPEG-2 TS码流分析
- MPEG-2 TS码流分析
- MPEG-2 TS码流分析
- MPEG-2 TS码流分析
- 【MPEG】ES、TS、PS流区别
- TS流打包总结
- MPEG-2 TS学习(二)MPEG-2 TS码流分析
- PES,TS,PS,mpeg-ts,mpeg-ps
- H.264/MPEG-4 AVC
- H.264/MPEG-4 AVC
- H.264/MPEG-4 AVC
- H.264/MPEG-4 AVC
- H.264/MPEG-4 AVC
- H.264和MPEG-4
- Android Jni 利用OpenCV 实现图像尺寸缩放(一)
- Could not write JSON document: (was java.lang.NullPointerException) (through reference chain
- C语言基础与提高5
- JPA Specification常用查询+排序
- 你可能不知道的RoR 5点技巧
- H.264 打包 MPEG-TS 流
- wps自动备份的文件,备份到本地哪里了?
- 【097】Nginx实现一台服务器,两个域名发布不同的前端项目,并且两个域名都用80端口。
- 数据结构上机——哈夫曼树 线索二叉树
- 【第三方类库】Java Guava , Google Guava
- C#个人重构之充值
- C# 静态构造函数
- python︱imagehash中的四种图像哈希方式(phash/ahash/dhash/小波hash)
- 【云计算的1024种玩法】手把手教你如何编译升级 OpenResty