当前位置: 首页 > news >正文

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的死循环。

http://www.jsqmd.com/news/1389489/

相关文章:

  • 折叠屏手机选购指南:铰链、屏幕与软件生态的深度解析
  • HTTP请求全解析:从结构到实战,解决502、401等常见错误
  • 数学建模竞赛解题:从思路到Python代码的完整实现指南
  • APMCM数学建模竞赛C题:煤矿巷道位移预测建模实战全解析
  • 多波束测深数据处理与海底地形建模全流程解析
  • 绿色采购新趋势:全流程无纸化智能编标如何为企业降本增效—逐光智标
  • AI赋能数据可视化:智能推荐引擎如何重塑大屏开发体验
  • 通信电子考研高效复习:如何利用结构化工具构建知识体系
  • 揭秘漳州最具口碑的网站建设:为何本地企业都在悄悄选择这一路径
  • MathorCup数学建模竞赛:从系统备赛到72小时实战的完整指南
  • 数学建模实战:多波束测线覆盖优化问题解析与算法实现
  • 基于QProc与FFmpeg的批量视频抽帧自动化方案
  • Firecomms光纤收发器在高频变压器中的技术方案设计
  • 郑州网站建设哪家公司便宜:揭秘行业内幕与避坑指南
  • Python运算符全解析:从基础语法到量化交易实战应用
  • 数学建模竞赛资源包深度解析:从VRP模型构建到代码实现与论文撰写
  • 早期移动端Hybrid应用架构解析:以掌上百度浏览器为例的技术考古与逆向工程实践
  • 《Phigros》顶级自制谱设计解析:从Lv.15谱面看音游创作与玩家进阶
  • Docker容器化部署PDF翻译工具:从Dockerfile到docker-compose
  • 为什么你的网站只看不买?揭秘营销型网站建设菲凡网如何让流量变留量
  • 数学建模国赛实战指南:从选题拆解到论文写作的全流程解析
  • 别再四处找激活工具了:KMS_VL_ALL_AIO 一个脚本搞定 Windows 和 Office 智能激活
  • MCP协议实战指南:从零开发Claude AI工具集成服务端
  • Python模块替换陷阱揭秘
  • 从红外干涉光谱反演薄膜厚度:数学建模与数值求解实战
  • systemd服务管理实战:从核心概念到Java应用部署与排错
  • CAS 5.3 单点登录(SSO)从零部署与核心配置实战指南
  • Spring Boot连接Oracle数据库:从驱动配置到生产环境调优实战
  • 数学建模竞赛实战指南:赛题解析、时间规划与论文写作
  • 从AI编程助手到AI AutoDev Team:构建全栈智能体研发团队的实践与思考