news 2026/8/14 4:44:46

FFmpeg6对本地文件进行RTMP推流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FFmpeg6对本地文件进行RTMP推流

目录

一、RTMP 推流的「三层本质」

二、完整可运行C代码(FFmpeg6)

三、关键 API 逐行讲解(高分博客该讲的坑)

1️.为什么用 avformat_alloc_output_context2(..., "flv", ...)

2️.flvflags=no_duration_filesize是直播命门

3️.av_packet_rescale_ts不是可选

4️.为什么自己 av_usleep

5️.av_interleaved_write_framevs av_write_frame

四、运行与验证

五、生产级增强

六、一句话总结


觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。

由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。

FFmpeg 6 本地文件 RTMP 推流 = 解封装 → 复制流参数 → FLV 封装 → 按时间戳限速发送,不重编码时就是“文件读帧、时间戳搬家、网络吐包”的死循环。
1.avformat_open_input + avformat_find_stream_info打开本地文件,只解封装不解码;
2.avformat_alloc_output_context2(..., "flv", rtmp_url)强制 FLV 封装(RTMP 线格式);
3.为每个输入流 avformat_new_stream并 avcodec_parameters_copy,保持 H264/AAC 原样;
4.av_dict_set("flvflags","no_duration_filesize")+ avformat_write_header,禁止写 duration/filesize(直播必开);
5.循环 av_read_frame拿 AVPacket,用 av_packet_rescale_ts把 DTS/PTS 从输入 time_base 转成 FLV time_base;
6.按 pkt->dts用 av_gettime()+ av_usleep做 -re限速,防止瞬间发爆服务器;
7.av_interleaved_write_frame写 RTMP,avio底层走 TCP 推流;
8.EOF 后 av_write_trailer关流,释放 context。


一、RTMP 推流的「三层本质」

RTMP 本身不是容器,线格式是FLV tag stream,所以 FFmpeg 里永远是:

URL: rtmp://... └── protocol : rtmpproto (native) └── muxer : flv ← 必须显式指定

三个铁律:

  1. avformat_alloc_output_context2(&oc, NULL, "flv", url)—— 第三个参数必须是"flv"

  2. 直播推流必须带flvflags=no_duration_filesize,否则写 trailer 时刷 duration/filesize 导致服务器断流

  3. 输入是文件时必须按时间戳限速(自己 sleep),否则 1 秒发完 10 分钟视频


二、完整可运行C代码(FFmpeg6)

编译(Linux/macOS):

g++ push_rtmp.cpp -o push_rtmp \ $(pkg-config --cflags --libs libavformat libavcodec libavutil)

Windows(MSVC/MinGW)链接avformat avcodec avutil swresample swscale

// push_rtmp.cpp #include <iostream> extern "C" { #include <libavformat/avformat.h> #include <libavutil/time.h> #include <libavutil/timestamp.h> } static int open_input(const char* file, AVFormatContext** ic) { if (avformat_open_input(ic, file, nullptr, nullptr) < 0) { std::cerr << "avformat_open_input failed: " << file << "\n"; return -1; } if (avformat_find_stream_info(*ic, nullptr) < 0) { std::cerr << "avformat_find_stream_info failed\n"; return -1; } av_dump_format(*ic, 0, file, 0); return 0; } static int setup_output(const char* rtmp_url, AVFormatContext* ic, AVFormat64Context** oc_out, AVDictionary** write_opts) { AVFormatContext* oc = nullptr; // RTMP 必须指定 flv 封装 if (avformat_alloc_output_context2(&oc, nullptr, "flv", rtmp_url) < 0) { std::cerr << "alloc output context failed\n"; return -1; } // 复制输入流到输出(stream copy,不解码) for (unsigned i = 0; i < ic->nb_streams; i++) { AVStream* in_st = ic->streams[i]; AVStream* out_st = avformat_new_stream(oc, nullptr); if (!out_st) return -1; avcodec_parameters_copy(out_st->codecpar, in_st->codecpar); out_st->codecpar->codec_tag = 0; out_st->time_base = in_st->time_base; } av_dump_format(oc, 0, rtmp_url, 1); // 直播关键参数 AVDictionary* opt = nullptr; av_dict_set(&opt, "flvflags", "no_duration_filesize", 0); av_dict_set(&opt, "rtmp_live", "live", 0); // 纯直播语义 av_dict_set(&opt, "buffer_size", "1024000", 0); if (!(oc->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&oc->pb, rtmp_url, AVIO_FLAG_WRITE) < 0) { std::cerr << "avio_open failed: " << rtmp_url << "\n"; return -1; } } if (avformat_write_header(oc, &opt) < 0) { std::cerr << "write header failed\n"; return -1; } av_dict_free(&opt); *oc_out = oc; return 0; } int main(int argc, char** argv) { if (argc < 3) { std::cerr << "usage: " << argv[0] << " <input.mp4> <rtmp://host:1935/live/stream>\n"; return 1; } const char* in_file = argv[1]; const char* rtmp_url = argv[2]; avformat_network_init(); AVFormatContext* ic = nullptr; AVFormatContext* oc = nullptr; if (open_input(in_file, &ic) < 0) goto fail; if (setup_output(rtmp_url, ic, &oc, nullptr) < 0) goto fail; AVPacket* pkt = av_packet_alloc(); int64_t start_time = av_gettime(); int frame_cnt = 0; while (true) { int ret = av_read_frame(ic, pkt); if (ret == AVERROR_EOF) break; if (ret < 0) { std::cerr << "read_frame error\n"; break; } AVStream* in_st = ic->streams[pkt->stream_index]; AVStream* out_st = oc->streams[pkt->stream_index]; // 时间戳重定基(输入 time_base → 输出 time_base) av_packet_rescale_ts(pkt, in_st->time_base, out_st->time_base); // 按真实时间戳限速(文件推流核心) if (pkt->dts != AV_NOPTS_VALUE) { AVRational tb = out_st->time_base; int64_t pts_us = av_rescale_q(pkt->dts, tb, {1, AV_TIME_BASE}); int64_t now_us = av_gettime() - start_time; if (pts_us > now_us) av_usleep(pts_us - now_us); } pkt->pos = -1; ret = av_interleaved_write_frame(oc, pkt); if (ret < 0) { char err[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, err, sizeof(err)); std::cerr << "write_frame failed: " << err << "\n"; av_packet_unref(pkt); break; } av_packet_unref(pkt); if ((frame_cnt++ & 0x1F) == 0) std::cout << "pushed packets: " << frame_cnt << "\n"; } av_packet_free(&pkt); av_write_trailer(oc); fail: if (oc) { if (!(oc->oformat->flags & AVFMT_NOFILE)) avio_closep(&oc->pb); avformat_free_context(oc); } if (ic) avformat_close_input(&ic); return 0; }

三、关键 API 逐行讲解(高分博客该讲的坑)

1️.为什么用avformat_alloc_output_context2(..., "flv", ...)

RTMP URL 本身不带封装信息,FFmpeg 不能反推用 FLV,必须强制指定。

不写这个 →Failed to find a suitable output format

2️.flvflags=no_duration_filesize是直播命门

FLV muxer 默认在write_trailer回写durationfilesize到 header。

直播流没有“文件尾”,服务器收到 trailer 直接断。

设了这个 flag → 不回写,流可以一直活。

3️.av_packet_rescale_ts不是可选

输入可能是{1,1000}(ms),输出 FLV 视频轨通常是{1,1000}但音频{1,44100}

不 rescale → 播放端音画快进/倒放。

4️.为什么自己av_usleep

命令行ffmpeg -re做的事就是按pkt->dts节流。

API 层没人帮你做,不 sleep → 瞬间把文件发完,RTMP 服务器当垃圾包丢弃。

5️.av_interleaved_write_framevsav_write_frame

  • interleaved:muxer 内部做 DTS 排序,多音视频轨必用

  • 单轨 copy 用av_write_frame也行,但 interleaved 更安全


四、运行与验证

# 假设本地有 test.mp4,SRS / nginx-rtmp 在 1935 ./push_rtmp test.mp4 rtmp://127.0.0.1:1935/live/room1

拉流验证:

ffplay rtmp://127.0.0.1:1935/live/room1

五、生产级增强

需求

做法

断线重连

avio_open失败 → 指数退避重连,重write_header

循环推流

av_seek_frame(ic, -1, 0, SEEK_SET)回到开头

H265 over RTMP

FFmpeg 6 native 支持 Enhanced RTMP,但 SRS 需开enhanced_rtmp on

动态码率

必须走 encode 流程(libx264),不能 stream copy

多路输出

teemuxer 或自己多开 oc

日志回调

av_log_set_callback接 Qt 的 qDebug


六、一句话总结

RTMP 推流 =flvmuxer +no_duration_filesize+ 时间戳 rescale + 按 DTS 限速,剩下就是av_read_frame → rescale → interleaved_write_frame的死循环。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 4:44:44

基于QProc与FFmpeg的批量视频抽帧自动化方案

这次我们来看一个基于 QProc 调用 FFmpeg 实现批量截取视频图片的实用工具。对于需要从大量视频中快速提取关键帧、制作预览图或进行内容分析的用户来说&#xff0c;手动操作费时费力。这个方案的核心价值在于自动化&#xff0c;它通过脚本或程序化方式&#xff0c;将 FFmpeg 强…

作者头像 李华
网站建设 2026/8/14 4:44:35

信号与系统考研核心:第一章概念、系统性质与解题框架全解析

最近在辅导考研学生时&#xff0c;发现很多同学面对《信号与系统》这门课&#xff0c;尤其是像“小马哥960”这类综合性强、题目灵活的习题集时&#xff0c;常常感到无从下手。第一章作为整个知识体系的基石&#xff0c;概念多、公式杂&#xff0c;如果基础不牢&#xff0c;后续…

作者头像 李华
网站建设 2026/8/14 4:43:06

AI助手工程化避坑指南:从意图识别到安全过滤的健壮架构设计

如果你正在开发或计划集成AI助手到你的产品中&#xff0c;金尼药房的案例是一个必须研究的“反面教材”。这家连锁药店因数百起客户投诉&#xff0c;最终下架了其AI手机助手“Burt”。这不仅仅是一个商业新闻&#xff0c;更是给所有技术决策者、产品经理和开发者敲响的一记警钟…

作者头像 李华
网站建设 2026/8/14 4:43:05

AI增强RAW细节:Lightroom AI技术原理与五大实战场景解析

1. 项目概述&#xff1a;当Lightroom遇上AI&#xff0c;你的RAW文件“醒”了如果你和我一样&#xff0c;是个常年和RAW文件打交道的摄影爱好者或职业摄影师&#xff0c;那你肯定对Lightroom Classic&#xff08;以下简称LR&#xff09;里那个“增强细节”按钮不陌生。以前点开它…

作者头像 李华
网站建设 2026/8/14 4:42:25

郑州网站建设哪家公司便宜:揭秘行业内幕与避坑指南

经常有不少创业者或者中小企业的老板在后台私信我,问的一个问题非常直接,甚至带着一点焦虑:“郑州网站建设哪家公司便宜?我想找最划算的那一家。”每当看到这类问题,我通常都会沉默几秒钟。不是因为我没回答,而是因为在这个看似简单的问题背后,藏着无数人因为追求“低价…

作者头像 李华
网站建设 2026/8/14 4:41:18

为什么企业级AI数据平台必须拥抱多模态?

在当前的企业级人工智能浪潮中&#xff0c;大模型技术已在文本生成、代码编写等通用领域展现出惊艳实力。然而&#xff0c;当企业试图将大模型引入经营分析、智能体调度等核心业务场景时&#xff0c;高达70%至95%的AI项目未能达到预期或宣告失败。究其根本&#xff0c;AI数据平…

作者头像 李华