FFmpeg6先拉取RTSP流再进行RTMP推流,为什么不用av_usleep限速?
目录
一、整体数据流
二、完整可运行C代码(FFmpeg 6.x)
三、关键 API 与坑位
1️. RTSP 输入选项
2️. 输出必须 "flv"
3️. no_duration_filesize是直播命门
4️. 时间戳处理(比文件推流更麻烦)
5️. 为什么不 av_usleep限速
6️. 音频兼容性
四、运行与验证
五、生产级增强
六、一句话总结
觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。
由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。
FFmpeg 6 拉 RTSP 推 RTMP = TCP 拉实时流 → 不解码 → FLV 封包 → 时间戳归一 → 网络直吐。
1.avformat_open_input+ rtsp_transport=tcp拉摄像头,禁用 UDP 防花屏;
2.avformat_find_stream_info拿音视频流,H264/AAC 直接 avcodec_parameters_copy建输出流;
3.avformat_alloc_output_context2(..., "flv", rtmp_url)强制 FLV(RTMP 线格式);
4.flvflags=no_duration_filesize + rtmp_live=live,禁止写 trailer metadata,保直播不断流;
5.主循环 av_read_frame拿 AVPacket,用 av_packet_rescale_ts把 RTSP 时间戳重定基到 FLV time_base,首帧 DTS 归零消负偏移;
6.av_interleaved_write_frame直接写 RTMP,avio走 TCP,RTSP 本身实时流无需 -re限速;
7.EOF/IO 错时重建 input 或退出,close 前 av_write_trailer。
本质:RTSP 负责“实时拿帧”,RTMP 负责“FLV tag over TCP 持续吐”,API 层只做 流复制 + 时间戳搬家
一、整体数据流
IPC摄像头 └─ RTSP/TCP (avformat_open_input) ↓ av_read_frame (AVPacket, 不解码) AVFormatContext(ic) ↓ av_packet_rescale_ts (input tb → FLV tb) av_interleaved_write_frame ↓ RTMP/FLV (avio_open + write_header) ↓ SRS / nginx-rtmp / MediaMTX三个铁律:
RTSP 输入必须
rtsp_transport=tcp,UDP 在弱网必花屏RTMP 输出
avformat_alloc_output_context2(&oc, NULL, "flv", url),第三个参数必须是"flv"直播推流
flvflags=no_duration_filesize,否则 trailer 回写 duration 断流
二、完整可运行C代码(FFmpeg 6.x)
编译:
g++ rtsp_to_rtmp.cpp -o push \ $(pkg-config --cflags --libs libavformat libavutil)#include <iostream> extern "C" { #include <libavformat/avformat.h> #include <libavutil/time.h> #include <libavutil/timestamp.h> } static void log_err(int ret) { char buf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, buf, sizeof(buf)); std::cerr << "err: " << buf << "\n"; } int main(int argc, char** argv) { if (argc < 3) { std::cerr << "usage: " << argv[0] << " <rtsp://...> <rtmp://host:1935/live/stream>\n"; return 1; } const char* rtsp_url = argv[1]; const char* rtmp_url = argv[2]; avformat_network_init(); AVFormatContext* ic = nullptr; AVFormatContext* oc = nullptr; AVDictionary* in_opt = nullptr; // ── 1. RTSP 输入(TCP + 超时)── av_dict_set(&in_opt, "rtsp_transport", "tcp", 0); av_dict_set(&in_opt, "stimeout", "5000000", 0); // 5s socket 超时 av_dict_set(&in_opt, "buffer_size", "4096000", 0); av_dict_set(&in_opt, "max_delay", "500000", 0); // 500ms if (avformat_open_input(&ic, rtsp_url, nullptr, &in_opt) < 0) { std::cerr << "open rtsp failed\n"; goto fail; } av_dict_free(&in_opt); if (avformat_find_stream_info(ic, nullptr) < 0) { std::cerr << "find stream info failed\n"; goto fail; } av_dump_format(ic, 0, rtsp_url, 0); // ── 2. RTMP/FLV 输出 ── if (avformat_alloc_output_context2(&oc, nullptr, "flv", rtmp_url) < 0) { std::cerr << "alloc output(flv) failed\n"; goto fail; } // 复制流(不解码) 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) goto fail; 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* out_opt = nullptr; av_dict_set(&out_opt, "flvflags", "no_duration_filesize", 0); av_dict_set(&out_opt, "rtmp_live", "live", 0); if (!(oc->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&oc->pb, rtmp_url, AVIO_FLAG_WRITE) < 0) { std::cerr << "avio_open rtmp failed\n"; goto fail; } } if (avformat_write_header(oc, &out_opt) < 0) { std::cerr << "write_header failed\n"; goto fail; } av_dict_free(&out_opt); // ── 3. 主循环:读 RTSP → 重定基 → 写 RTMP ── AVPacket* pkt = av_packet_alloc(); int64_t start_us = av_gettime(); int64_t base_dts = AV_NOPTS_VALUE; int count = 0; while (true) { int ret = av_read_frame(ic, pkt); if (ret == AVERROR_EOF) { std::cerr << "rtsp eof, retry after 2s...\n"; av_usleep(2000000); // 生产环境这里应重连 ic,示例简化为退出 break; } if (ret < 0) { log_err(ret); av_usleep(500000); continue; } AVStream* in_st = ic->streams[pkt->stream_index]; AVStream* out_st = oc->streams[pkt->stream_index]; // 时间戳重定基(核心) av_packet_rescale_ts(pkt, in_st->time_base, out_st->time_base); // 首帧把基准 DTS 归零,避免 FLV 负时间戳 if (base_dts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) { base_dts = pkt->dts; } if (pkt->dts != AV_NOPTS_VALUE) pkt->dts -= base_dts; if (pkt->pts != AV_NOPTS_VALUE) pkt->pts -= base_dts; pkt->pos = -1; // RTSP 是实时流,不需要像文件那样 sleep 限速; // 但如果服务器要求严格单调 DTS,可在此做单调校正 ret = av_interleaved_write_frame(oc, pkt); av_packet_unref(pkt); if (ret < 0) { log_err(ret); std::cerr << "write frame failed, abort\n"; break; } if ((count++ & 0x3F) == 0) std::cout << "pushed " << count << " packets\n"; } av_write_trailer(oc); av_packet_free(&pkt); fail: if (oc) { if (!(oc->oformat->flags & AVFMT_NOFILE) && oc->pb) avio_closep(&oc->pb); avformat_free_context(oc); } if (ic) avformat_close_input(&ic); return 0; }三、关键 API 与坑位
1️. RTSP 输入选项
rtsp_transport=tcp:防 UDP 丢包花屏,监控场景唯一选择stimeout=5000000:微秒,socket 读超时,避免av_read_frame永久阻塞max_delay:包排队上限,太大延迟高,太小易报max delay reached
2️. 输出必须"flv"
RTMP 线格式 = FLV tag。avformat_alloc_output_context2第三个参数填"flv",填nullptr让 FFmpeg 从rtmp://猜格式在部分版本会错 。
3️.no_duration_filesize是直播命门
FLV muxer 默认在write_trailer回写onMetaData.duration/filesize。直播没有结尾,SRS/NGINX-RTMP 收到 trailer 直接断开 。
4️. 时间戳处理(比文件推流更麻烦)
RTSP 常带负偏移 PTS 或DTS 非单调
必须
av_packet_rescale_ts到输出time_base首帧
base_dts归零,后续所有包减基准,消除负时间戳FLV 视频轨
time_base一般是{1,1000},音频{1,44100},不 rescale 必音画错位
5️. 为什么不av_usleep限速
RTSP 本身就是实时流,av_read_frame阻塞等帧到达;文件推流才需要按 DTS 模拟-re限速。
6️. 音频兼容性
RTSP 摄像头常出G.711/PCM 音频,RTMP(FLV) 只认AAC。本例copy模式若源不是 AAC 会推流失败——生产环境需:
视频
copy+ 音频单独libvo_aacenc/aac重编码或 SRS 开
aac_sample_rate兼容
四、运行与验证
./push "rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/101" \ "rtmp://127.0.0.1:1935/live/cam1"另开终端:
ffplay rtmp://127.0.0.1:1935/live/cam1五、生产级增强
需求 | 做法 |
|---|---|
断线重连 |
|
非 AAC 音频 | 拆音视频,视频 copy,音频 swr+libx264/aac 重编码 |
H265 over RTMP | FFmpeg 6 native 支持 Enhanced RTMP,SRS 需 |
低延迟 |
|
多路摄像头 | 每路独立 oc + 独立线程,或 |
六、一句话总结
RTSP→RTMP 网关 = TCP 拉流 + FLV 封包 + 时间戳搬家 + 不写 duration,代码 200 行内可跑,难点全在超时/重连/时间戳校正,不在 API 本身。
