这一篇讲h264码流AVCC格式转到annexb格式过程中sps pps变化的情况.

h264 bitstream 格式

h264码流在封装容器中通常有两种存在形式,AVCC和annexb,flv,mp4采用前者,ts用于后者.

转封装时,要根据不同容器格式进行h264的bitstream转换.

flv中sps pps变化

flv封装h264采用的是AVCC格式,sps pps数据封装到flv tag中,通过tag header中的AVPacketType标明这个tagsequence header.

当h264视频编码发生变化时,flv格式的流媒体如http-flv, rtmp一般都是再次封装一个sequence header类型的tag,但并不是所有的客户端都能支持正常处理这样的flv,例如vlc,就无法播放sps pps变化后的flv码流.

ffmpeg转封装

ffmpeg针对这种sps pps变化的flv流做转封装处理,比如转成ts,同样也是有问题的, 它使用了一个bitstreamfilter,h264_mp4toannexb, 顾名思义,就是将AVCC格式转成annexb格式的bitstream filter.

这是一个ffmpeg转封装命令,基于ffmpeg3.4版本,早期版本需要显式地插入-bsf:v h264_mp4toannexb,现在的ffmpeg版本会在需要的时候自动插入这个bitstream filter.

1
ffmpeg -i xx.flv -c copy xx.ts

通过源码来分析一下上面的命令行转封装过程为什么不支持flv码流中h264的sps pps变化的情况.

调试查看libavcodec/h264_mp4toannexb_bsf.c

 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
/* if this is a new IDR picture following an IDR picture, reset the idr flag.
 * Just check first_mb_in_slice to be 0 as this is the simplest solution.
 * This could be checking idr_pic_id instead, but would complexify the parsing. */
if (!s->new_idr && unit_type == 5 && (buf[1] & 0x80))
    s->new_idr = 1;

/* prepend only to the first type 5 NAL unit of an IDR picture, if no sps/pps are already present */
if (s->new_idr && unit_type == 5 && !s->idr_sps_seen && !s->idr_pps_seen) {
    if ((ret=alloc_and_copy(out,
                       ctx->par_out->extradata, ctx->par_out->extradata_size, sps_pps: ctx->par_out->extradata sps_pps_size: ctx->par_out->extradata_size
                       buf, nal_size)) < 0) in: buf in_size: nal_size
        goto fail;
    s->new_idr = 0;
/* if only SPS has been seen, also insert PPS */
} else if (s->new_idr && unit_type == 5 && s->idr_sps_seen && !s->idr_pps_seen) {
    if (s->pps_offset == -1) {
        av_log(ctx, AV_LOG_WARNING, "PPS not present in the stream, nor in AVCC, stream may be unreadable\n"); avcl: ctx level: AV_LOG_WARNING fmt: "PPS not present in the stream, nor in AVCC, stream
        if ((ret = alloc_and_copy(out, NULL, 0, buf, nal_size)) < 0) sps_pps: NULL sps_pps_size: 0 in: buf in_size: nal_size
            goto fail;
    } else if ((ret = alloc_and_copy(out,
                                ctx->par_out->extradata + s->pps_offset, ctx->par_out->extradata_size - s->pps_offset, sps_pps: ctx->par_out->extradata + s->pps_offset sps_pps_size: ctx->par_out->extr
                                buf, nal_size)) < 0) in: buf in_size: nal_size
        goto fail;
} else {
    if ((ret=alloc_and_copy(out, NULL, 0, buf, nal_size)) < 0) sps_pps: NULL sps_pps_size: 0 in: buf in_size: nal_size
        goto fail;
    if (!s->new_idr && unit_type == 1) {
        s->new_idr = 1;
        s->idr_sps_seen = 0;
        s->idr_pps_seen = 0;
    }
}

它的转换逻辑很简单,就是在key帧前插入sps pps,将AVCC格式转为annexb格式,每次插入使用的sps pps都是从H264BSFContext中取出的, 那为什么这种转换不支持sps pps变化的情况呢?原因就是H264BSFContext只在初始化时填入sps pps信息,后续是不再更新了,所以使用的都是 第一个sps pps,这样后续发生变化时就不对了.

那使用ffmpeg是否能探测到这种变化呢,当然可以的,ffmpeg中的结构体AVPacket有个成员AVPacketSideData,这个结构是ffmpeg抽象出来用来描述 一帧压缩后视频也就是AVPacket属性的,其中的AV_PKT_DATA_NEW_EXTRADATA类型对h264比特流来说就是表示这一帧附带的sps pps信息.使用ffmpeg 库开发可以通过检测这个属性来判断extradata的变化.事实上ffmpeg做flv to flv, flv to rtmp的转封装时是可以正常处理sps pps变化的情况的, 就是利用了AVPacketSideData这个结构,但是h264_mp4toannex bitstream filter并没有对这种情况做兼容.