关键不是“能不能”,而是必须补全SPS/PPS并统一加四字节起始码0x00000001;因FLV要求每个IDR帧前紧邻SPS+PPS,且所有NALU须以0x00000001开头,裸流不满足此规范。

直接封装H264裸流到FLV,关键不是“能不能”,而是av_interleaved_write_frame之前必须做两件事:补全SPS/PPS、加起始码0x00000001。跳过这两步,FLV播放器大概率卡在第一帧或报Invalid data found when processing input。
为什么H264裸流不能直接写进FLV
FLV容器要求每个视频tag的tag data以完整NALU为单位,且IDR帧前必须紧邻SPS+PPS;而裸流里SPS/PPS通常只在开头出现一次,后续IDR帧不带它们。更麻烦的是,裸流用0x000001或0x00000001分隔NALU,但FLV规范强制要求每个NALU前缀是0x00000001(四字节起始码)。
常见错误现象:
- VLC打开FLV后黑屏,日志显示
h264: SPS not present - FFmpeg命令行用
-c:v copy转封装失败,提示Could not find codec parameters - 推流到Nginx-RTMP时,客户端收不到关键帧,始终loading
必须启用h264_mp4toannexb比特流过滤器
这个BSF干两件事:把SPS/PPS注入每个IDR帧前;把所有NALU前缀统一转成0x00000001。不用它,av_write_frame写进去的数据对FLV来说就是残缺的。
立即学习“C++免费学习笔记(深入)”;
实操要点:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 调用
av_bsf_alloc时传入"h264_mp4toannexb",不是"h264"或"annexb" -
av_bsf_init必须在avformat_write_header之后、写帧之前调用 - 每个
AVPacket送入BSF前,要确保pkt->pts和pkt->dts已正确设置(裸流通常没有时间戳,得自己按帧率算) - BSF输出的
av_bsf_receive_packet结果需用av_packet_unref清理,否则内存泄漏
FLV封装时AVStream时间基必须设为1/1000
FLV的时间戳单位是毫秒,硬性规定。如果裸流时间基是1/90000(常见于H264 Annex B),直接复制会导致时间戳被放大90倍,音画飞快或卡死。
正确做法:
- 创建
AVStream后,立刻设st->time_base = {1, 1000} - 每个
AVPacket写入前,用av_rescale_q转换时间戳:pkt->pts = av_rescale_q(pkt->pts, in_tb, st->time_base) - 注意:
pkt->duration也得同步转换,否则关键帧索引(keyframe index)会错乱
别漏掉AVCodecParameters的codec_id和extradata
FLV识别H264靠两个字段:codec_id == AV_CODEC_ID_H264,且extradata必须包含SPS+PPS(合并成一个buffer,SPS在前、PPS在后,中间无分隔)。
容易踩的坑:
- 裸流文件头里的SPS/PPS可能被
0x000001截断,提取时要跳过起始码,只取NALU payload -
extradata长度必须是sizeof(uint8_t) + sps_size + sizeof(uint8_t) + pps_size(前面两个uint8_t是NALU类型字节) - 如果裸流没附带SPS/PPS(比如纯IDR帧流),必须从编码器参数里手动构造,否则
avformat_write_header会失败
最易被忽略的点:FLV不支持B帧时间戳乱序。如果裸流含B帧,必须确保pkt->dts pts,且严格按DTS顺序写入av_interleaved_write_frame——哪怕你只封装视频,这点也绕不开。

















