C++直接调用FFmpeg API推流比popen命令行更可控低延迟,但需手动处理AVFormatContext初始化、时间戳校准、音视频同步和错误恢复,否则易卡顿花屏秒断。

直接用 C++ 调用 FFmpeg API 推流 RTMP,比 fork ffmpeg 子进程更可控、延迟更低、资源更干净——但必须手动处理 AVFormatContext 初始化、时间戳校准、音视频同步和错误恢复,否则推流会卡顿、花屏或秒断。
为什么不能只靠 popen("ffmpeg -i - ...") 管道推流
这种“外包给命令行”的做法看似简单,实际在生产环境里问题密集:
-
ffmpeg进程崩溃后父进程难感知,容易变成僵尸流 - OpenCV 读帧是 BGR,而 libx264 编码要求 YUV420P,不手动转换就会花屏
- 管道写入无背压控制:如果编码慢于采集,
fwrite()会阻塞或丢帧,导致 PTS 跳变、播放卡顿 - 无法动态调整 GOP、码率或插入 SEI 帧(比如打时间水印)
- Windows 下
popen对二进制流支持差,fflush()行为不稳定
avformat_write_header() 失败的常见原因
这是 C++ 推流初始化阶段最常卡住的点,失败时 avformat_write_header() 返回负值,但错误信息藏在 av_strerror() 里。典型诱因有:
- RTMP URL 格式错误:
rtmp://localhost:1935/live少了流名(如/stream),服务器拒绝创建流 - 未设置
AVOutputFormat->flags & AVFMT_NOFILE:RTMP 是网络协议,必须显式告知 FFmpeg “不用打开本地文件” -
AVCodecParameters缺少关键字段:比如codecpar->codec_tag = 0x00000007(H.264 的 FLV tag)、codecpar->level未设(某些服务器校验严格) - 时间基(
time_base)设错:视频流常用{1, 30},但若采集帧率是 29.97,硬设{1,30}会导致 PTS 累积漂移
关键帧(I帧)和 AVPacket->flags 必须对齐
RTMP 协议依赖关键帧做随机访问和首屏加载,推流端必须确保每个 I 帧的 AVPacket->flags 包含 AV_PKT_FLAG_KEY,否则播放器可能黑屏或无法 seek。
- libx264 编码器本身会在 GOP 头生成 I 帧,但需确认
AVCodecContext->gop_size已设(如ctx->gop_size = 60) - 手动送包前检查:
if (pkt->flags & AV_PKT_FLAG_KEY) { /* 可安全用于关键帧逻辑 */ } - 别依赖
av_interleaved_write_frame()自动补关键帧——它不会改flags,只负责按 DTS 排序 - 若用硬件编码(如 NVENC),需开启
ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER并在写 header 前调用avcodec_parameters_from_context()
延迟炸了?先盯死 AVPacket->pts 和 AVPacket->dts
端到端延迟超 3 秒,八成是时间戳没归一化或未重映射。FFmpeg 推流要求所有流的时间基统一,且 PTS/DTS 必须是单调递增的整数序列。
- 采集帧时间戳(如 OpenCV 的
cv::getTickCount())不能直接当 PTS:要转成以AVStream->time_base为单位的值,公式是pkt->pts = av_rescale_q(frame_ts, AV_TIME_BASE_Q, st->time_base) - 音视频流 time_base 必须一致:哪怕音频采样率是 44100,也要强制设为
{1, 1000}或与视频流对齐,否则av_interleaved_write_frame()会乱序 - 首帧 PTS 不能为 0:设为
pkt->pts = pkt->dts = 0容易触发播放器缓冲策略,建议从1开始 - 用
av_packet_rescale_ts()替代手算:它自动处理 time_base 转换,避免整数溢出
真正难的不是把帧送出去,而是让每一帧的 pts、dts、flags 和服务器期望的节奏严丝合缝——漏掉任意一个细节,推流就变成“看起来在动,但播不了几秒”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











