1. h264_mp4toannexb是什么?
h264_mp4toannexb是libavcodec bsf(bit stream filter)中的一个。ffmpeg支持的bsf列表可以通过ffmpeg -bsfs查看。
我们知道,H.264常用的码流格式有两种:annexb和avcc格式
- annexb格式是start code(0 0 0 1)加nalu的格式,来自H.264标准的annexb章节(名字源自标准的章节)。常见的TS格式在封装H.264时,采用的是annexb格式
- avcc标准是长度信息加nalu的格式,来自ISO-14496-15标准。MP4封装H.264采用avcc格式(avcc是MP4附带定义的标准)。其他用avcc格式的有FLV、MKV等。
h264_mp4toannexb的做的事,就是把avcc格式的码流,转成annexb格式。
avcc格式的流,通常采用带外传输(out-of-band)SPS/PPS,不和视频帧的packet一起传输。而annexb格式的流,一般需要带内传输(in-band)SPS/PPS,IDR帧前面带着SPS/PPS。所以,h264_mp4toannexb的另一个重要事项,是把带外的SPS/PPS转成带内,即周期性的插入到IDR帧前面。
2. 为什么需要h264_mp4toannexb?
既然h264_mp4toannexb是把avcc格式转成annexb,那么,谁需要这种转换呢?
- decoder:有些解码器只支持annexb格式作为输入
- muxer:TS muxer需要annexb格式做输入,另外直接存储H.264的裸流(ES)也是annexb格式
FFmpeg libavformat和libavcode框架层做了自动插入的支持。例如,mediacodec解码器封装配置bsf:
#if CONFIG_H264_MEDIACODEC_DECODER
DECLARE_MEDIACODEC_VDEC(h264, "H.264", AV_CODEC_ID_H264, "h264_mp4toannexb")
#endif
#if CONFIG_HEVC_MEDIACODEC_DECODER
DECLARE_MEDIACODEC_VDEC(hevc, "H.265", AV_CODEC_ID_HEVC, "hevc_mp4toannexb")
#endif
TS muxer配置bsf:
static int mpegts_check_bitstream(AVFormatContext *s, AVStream *st,
const AVPacket *pkt)
{
int ret = 1;
if (st->codecpar->codec_id == AV_CODEC_ID_H264) {
if (pkt->size >= 5 && AV_RB32(pkt->data) != 0x0000001 &&
(AV_RB24(pkt->data) != 0x000001 ||
(st->codecpar->extradata_size > 0 &&
st->codecpar->extradata[0] == 1)))
ret = ff_stream_add_bitstream_filter(st, "h264_mp4toannexb", NULL);
} else if (st->codecpar->codec_id == AV_CODEC_ID_HEVC) {
...
}
3. h264_mp4toannexb的缺陷
第一个缺陷是代码实现晦涩难懂,逻辑不清。
它考虑了部分特殊场景,包括有SPS没PPS,有PPS没SPS,但是另一些常见场景却忽略了。因为代码实现混乱,分析问题困难,发现有问题也不敢轻易去动它。比如有一行代码:
uint8_t start_code_size = ps < 0 ? 0 : *out_size == 0 || ps ? 4 : 3;
每个字都认识,但是放到一起,只想说WTF!我花了可能有十分钟时间,才知道这一行代码到底是要干啥。
第二个缺陷是不支持带外(out-of-band)参数集更新。
带外参数集更新在FFmpeg API上的体现是AVPacket带一个AV_PKT_DATA_NEW_EXTRADATA sidedata。h264_mp4toannexb完全忽略了它的存在,因为早期实现没有考虑mp4有参数集更新的事。mp4支持多参数集,但并不常见。
随着直播的火热,这事变得严重了,因为RTMP/FLV更新参数集比较普遍。比如手机RTMP直播推流,推流过程中发生了横竖屏切换,可能对应到FLV是SPS/PPS的更新。变参数集的FLV,如果做转封装成TS/HLS,或者解码播放,中间经过h264_mp4toannexb的处理,都可能发生新参数集丢失问题,导致解码错误。
一些公司采用带内方式,SPS/PPS与IDR一起传输来规避这个问题,把avcc当成了半个annexb。
修复方法:
- 检查new extradata sidedata,存起来
- IDR前插入SPS/PPS始终用最新的一组参数集
第三个缺陷,是一旦出现带内SPS/PPS,就停止插入SPS/PPS动作。
如果码流本身SPS/PPS和IDR总是一起出现,这没问题。但如果只是首帧IDR带了SPS/PPS,其后的IDR都不带,问题就严重了。如果后续封装成TS做切片,那么只有开头一个TS有SPS/PPS,其后的切片丢失参数集。
修复方法:
- 把带内参数集存起来
- 遇到IDR前丢失参数集的情况,插入最新的参数集
以上三个问题,我在FFmpeg master分支修复了第二个和第三个,第一个问题举的例子稍微有改善。
此外,还有一些小缺陷,比如插入参数集可能插在了SEI和IDR帧之间,理论上应该在SEI前面。这一缺陷导致解析SEI时打印警告信息。
TODO:h265_mp4toannexb也有类似问题待修复。
作者:quink
来源:Fun With FFmpeg
原文:https://mp.weixin.qq.com/s/oxDBUHZQuo4O-SxThJ2sZw
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。