
目录一、整体数据流二、完整可运行C代码FFmpeg 6.x三、关键 API 与坑位1️. RTSP 输入选项2️. 输出必须 flv3️. no_duration_filesize是直播命门4️. 时间戳处理比文件推流更麻烦5️. 为什么不 av_usleep限速6️. 音频兼容性四、运行与验证五、生产级增强六、一句话总结觉得有用就请您帮忙点赞转发收藏吧您的鼓励是我创作的动力多谢看官。由于能力水平有限文中的错误或不严谨的地方在所难免还请批评指正。FFmpeg 6 拉 RTSP 推 RTMP TCP 拉实时流 → 不解码 → FLV 封包 → 时间戳归一 → 网络直吐。1.avformat_open_input rtsp_transporttcp拉摄像头禁用 UDP 防花屏2.avformat_find_stream_info拿音视频流H264/AAC 直接 avcodec_parameters_copy建输出流3.avformat_alloc_output_context2(..., flv, rtmp_url)强制 FLVRTMP 线格式4.flvflagsno_duration_filesize rtmp_livelive禁止写 trailer metadata保直播不断流5.主循环 av_read_frame拿 AVPacket用 av_packet_rescale_ts把 RTSP 时间戳重定基到 FLV time_base首帧 DTS 归零消负偏移6.av_interleaved_write_frame直接写 RTMPavio走 TCPRTSP 本身实时流无需 -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_transporttcpUDP 在弱网必花屏RTMP 输出avformat_alloc_output_context2(oc, NULL, flv, url)第三个参数必须是flv直播推流flvflagsno_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_transporttcp防 UDP 丢包花屏监控场景唯一选择stimeout5000000微秒socket 读超时避免av_read_frame永久阻塞max_delay包排队上限太大延迟高太小易报max delay reached2️. 输出必须flvRTMP 线格式 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:pass192.168.1.64:554/Streaming/Channels/101 \ rtmp://127.0.0.1:1935/live/cam1另开终端ffplay rtmp://127.0.0.1:1935/live/cam1五、生产级增强需求做法断线重连avformat_close_input后重open_input保留 oc 重新 write_header 或重建 oc非 AAC 音频拆音视频视频 copy音频 swrlibx264/aac 重编码H265 over RTMPFFmpeg 6 native 支持 Enhanced RTMPSRS 需enhanced_rtmp on低延迟fflagsnobuffer,max_delay调小, SRS 关wait_key多路摄像头每路独立 oc 独立线程或teemuxer 一路分多推六、一句话总结RTSP→RTMP 网关 TCP 拉流 FLV 封包 时间戳搬家 不写 duration代码 200 行内可跑难点全在超时/重连/时间戳校正不在 API 本身。