这一篇讲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
标明这个tag
是sequence 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
并没有对这种情况做兼容.