当前位置: 首页 > 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.cnnetsun.cn/news/4014085.html

相关文章:

  • 基于QProc与FFmpeg的批量视频抽帧自动化方案
  • 信号与系统考研核心:第一章概念、系统性质与解题框架全解析
  • AI助手工程化避坑指南:从意图识别到安全过滤的健壮架构设计
  • AI增强RAW细节:Lightroom AI技术原理与五大实战场景解析
  • 郑州网站建设哪家公司便宜:揭秘行业内幕与避坑指南
  • 为什么企业级AI数据平台必须拥抱多模态?
  • 治愈AI幻觉的根源:构建可信的企业级AI数据平台
  • 从零搭建Dell R720服务器:RAID配置、CentOS安装与远程管理实战
  • 高性能网站建设指南 书:从底层逻辑到流量变现的实战智慧
  • 在淮安深耕数字化:淮安软件园网站建设如何助力本地企业突破增长瓶颈
  • 揭秘漳州最具口碑的网站建设:为何本地企业都在悄悄选择这一路径
  • AI编程最短路径:绕过Claude Code,掌握提示词心法与轻量工具组合
  • 姜堰区区网站建设,打造本地企业专属数字名片的终极指南:从0到1的真诚建议与实操避坑
  • FFmpeg实战:从MP4中提取PCM、YUV、AAC、H264原始数据全解析
  • 2024年最新避坑指南:明薇通网站建设哪家好,看完这篇再决定不后悔
  • 2024年泰州网站建设物美价廉指南:本地企业如何找到高性价比的靠谱团队
  • 揭秘新加坡建设局网站:从项目查询到合规指南的全方位实用指南
  • 海口网站建设就q479185700上墙
  • 上海网站建设平台补贴全攻略:初创企业如何拿到真金白银的红利与扶持
  • MoE架构实战:从万亿参数MiMo-V2.5到智能体系统构建
  • 成都网站建设选择到访率:如何用高转化率重构企业数字形象
  • 西安英文网站建设指南:如何打造国际化品牌形象并提升海外获客效率
  • 办公用品网站建设市场定位:深耕细分赛道与精准获客策略全解析
  • 数学建模竞赛解题框架:从破题到论文的实战思维与避坑指南
  • 别再四处找激活工具了:KMS_VL_ALL_AIO 一个脚本搞定 Windows 和 Office 智能激活
  • Blender复古风格渲染:从原理到实践,打造《生化危机》PS1时代视觉
  • 内蒙古网站建设公司怎么选?揭秘真正靠谱的数字营销幕后推手,助您的企业官网在草原之上扬帆起航,不再只是展示窗口而是获客引擎
  • 2026 AI视频模型选型与API集成实战:从Sora退场到Seedance 2时代
  • 万字深度解析 Agent 学习路线
  • 大模型应用中的提示词泄露难题:如何通过加密与RBAC实现双重防护?