欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

H.264 打包 MPEG-TS 流

程序员文章站 2022-07-14 18:37:25
...

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 的描述的一些信息的标准

打包过程:

H.264 打包 MPEG-TS 流
1. TS header: 所有包都是以4 个字节的包头开始的,针对比较难理解的部分说明一下
H.264 打包 MPEG-TS 流
负载单元开始:这个文档上写的很官方,其实就是当一段数据超过一个包的大小必须分成几个包,第一个包的这个标志为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 mpegTS