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

原创粉丝点击