1. 背景了解

ISOIEC 13818-1标准,MPEG-2中定义了两种复合信息流:传送流(TS:TransportStream)和节目流(PS:ProgramStream)
现在主要用于安防和广电领域,苹果全系列产品也在使用(HLS)。
PS主要应用在安防领域,因为GB28181-11规定了码流必须是RTP+PS,而且有存储属性;
TS主要应用在广电领域,因为TS的易于恢复的特性,苹果HLS协议的码流封装格式也是TS

2. 主要区别

  • TS流的包结构是固定的,而PS流是可变的;所以,TS可以在任意位置恢复播放,PS必须保证完整性
  • TS用于数据流传输,PS既有存储属性也有实时流传输的属性
  • PS支持在多个层次加入私有数据,方便解码,拖动和减少延时

3. 封装格式

TS和PS流都是基于PES包进行的二次封装,总体封装结构如下:

3.1 TS层结构

在这里插入图片描述

3.2 PS层结构

在这里插入图片描述

PS层主要由PS header,PS system header,PS system map加上PES packets组成。

对于包含IDR帧的PS包的内容为:“PS包起始码(0x00 00 00 01 ba)” + “系统头” + “PSM” +“PES header” + “PES payload”
对于包含非IDR帧的PS包的内容为:“PS包起始码(0x00 00 00 01 ba)” +“PES header” + “PES payload”

解封装思路:
在这里插入图片描述

  1. PS header:字段含义如下:

    在这里插入图片描述

    • system_clock_reference_base 和 system_clock_reference_extension
      这两个字段暂时没太明白,iso13818-1介绍如下,猜测是为了同步编解码两端的时钟频率而设置的字段,可能是历史上的应用场景,但到今天作用已经不大了(有清楚的朋友希望可以讲解一下,感谢。):

      在这里插入图片描述

      查阅了ffmpeg代码,发现组包时system_clock_reference_extension直接被设置为0;而system_clock_reference_base是以采样间隔为单位逐步累加的。

      gastatic int put_pack_header(AVFormatContext *ctx, uint8_t *buf, int64_t timestamp)
      {
          MpegMuxContext *s = ctx->priv_data;
          PutBitContext pb;
      
          init_put_bits(&pb, buf, 128);
      
          put_bits32(&pb, PACK_START_CODE);
          if (s->is_mpeg2)
              put_bits(&pb, 2, 0x1);
          else
              put_bits(&pb, 4, 0x2);
          put_bits(&pb,  3, (uint32_t)((timestamp >> 30) & 0x07));
          put_bits(&pb,  1, 1);
          put_bits(&pb, 15, (uint32_t)((timestamp >> 15) & 0x7fff));
          put_bits(&pb,  1, 1);
          put_bits(&pb, 15, (uint32_t)((timestamp)       & 0x7fff));
          put_bits(&pb,  1, 1);
          if (s->is_mpeg2)
              /* clock extension */
              put_bits(&pb, 9, 0); // 此处直接设置为0
          put_bits(&pb, 1, 1);
          put_bits(&pb, 22, s->mux_rate);
          put_bits(&pb, 1, 1);
          if (s->is_mpeg2) {
              put_bits(&pb, 1, 1);
              put_bits(&pb, 5, 0x1f); /* reserved */
              put_bits(&pb, 3, 0); /* stuffing length,默认不填充stuffing byte */
          }
          flush_put_bits(&pb);
          return put_bits_ptr(&pb) - pb.buf;
      }
      
         // scr base计算方式如下,是一个以采样间隔为单位的值,感觉ffmpeg跟iso13818-1文档所说的计算方式并不一样,可能今天的应用场景改变已经偏大
         scr        += s->packet_size * 90000LL / (s->mux_rate * 50LL);
         size        = put_pack_header(ctx, buf_ptr, scr);
         s->last_scr = scr;
      
    • program_mux_rate

      表示当前码率的字段,单位是50 bytes/second,不能为0。

    • pack_stuffing_length

      A 3 bit integer specifying the number of stuffing bytes which follow this field

    • stuffing_byte (pack_stuffing_length大于0才存在)

      pack_stuffing_length字段指定的填充字节,固定为0xFF

  2. PS system header:用来描述VCD和DVD信息的,在今天作用已经不大,不做详细解释,组包流程可以参考ffmpeg代码(put_system_header(…))

    在这里插入图片描述

  3. PSM(PS system map):在固定包头和系统头之后,只有关键帧的时候,才会存在,提供了es流的描述信息,以及各es流之间的关系

    在这里插入图片描述

    在这里插入图片描述

    主要字段名称 字段长度(bits) 功能描述
    program_stream_map_length 16 该字段之后,PSM还有多长,单位byte。 最大值1018 (0x3FA)
    program_stream_info_length 16 该字段之后,自定义复合流的长度,一般是0。 但可以用这个字段定制私有数据。
    海康用该字段 定义了BASIC信息、DEVICE信息、加密信息
    elementary_stream_info_length 16 该字段之后,视频流信息、音频流信息、私有数据信息三者的总长度
    CRC_32 32 循环冗余校验

    私有数据部分不多做解释,私有数据解析参考:海康PSM流解析 关于elementary_stream_info,标准流一般只解析音视频类型等信息,参考ffmpeg代码:

    static long mpegps_psm_parse(MpegDemuxContext *m, AVIOContext *pb)
    {
        int psm_length, ps_info_length, es_map_length;
    
        psm_length = avio_rb16(pb);
        avio_r8(pb);
        avio_r8(pb);
        ps_info_length = avio_rb16(pb);
    
        /* skip program_stream_info 无私有数据,直接跳过 */
        avio_skip(pb, ps_info_length);
        /*es_map_length = */avio_rb16(pb);
        /* Ignore es_map_length, trust psm_length */
        es_map_length = psm_length - ps_info_length - 10;
    
        /* at least one es available? */
        while (es_map_length >= 4) {
            unsigned char type      = avio_r8(pb);
            unsigned char es_id     = avio_r8(pb);
            uint16_t es_info_length = avio_rb16(pb);
    
            /* remember mapping from stream id to stream type */
            m->psm_es_type[es_id] = type;
            /* skip program_stream_info */
            avio_skip(pb, es_info_length); 	// 只解析上边三个信息, 剩下的不解析直接跳过(默认无私有数据)
            es_map_length -= 4 + es_info_length;
        }
        avio_rb32(pb); /* crc32 */
        return 2 + psm_length;
    }
    

3.3 PES层结构

  • TS流和PS流都是由PES包经过某种逻辑结构组织而成

  • PES包可以被用来完成从TS流转换到PS流的转换

  • PES包的大小有时也可以大于TS包(一个PES包可能会被分装在多个TS包中)

    在这里插入图片描述

如上图所示,PES包由3部分组成:

  1. 固定头
    • packet_start_code_prefix:PES起始码固定为0x000001
    • stream_id:规定了基本流的号码和类型。0x(c0-df)指音频,0x(e0-ef)为视频,0xbf 私有,0xbe 填充
    • PES Packet length:说明了从固定头之后,还有多少个字节属于本PES包(TS流该字段可能为0,但PS不可能)
  2. 可选头**(stream_id是特定值的话,才会有这个字段)**
    • 从固定头信息中只能知道PES包的开始和总长度,但PES header长度是可变的,怎么知道header和payload的分界呢

      在这里插入图片描述

    • optional fields:可选填充域,TS流要固定包大小,需要这个字段。

  3. 负载(即ES流)

3.4 ES流如何封装为PES

没有特别严格的方式,一般就是一帧音频或者视频数据封装为一个PES packet,然后N个PES packet封装为一个PS packet,用工具解析一下萤石的录像文件:

在这里插入图片描述

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐