Qt + FFmpeg 推流软件实战:从工程搭建到稳定上线的关键细节
简介这份源码是一个以Qt与FFmpeg为基础实现的推流软件面向具备C开发经验、希望深入音视频领域的读者。工程完整呈现了推流核心链路初始化FFmpeg组件、选择编码器、设定输出协议与目标地址、采集本地音视频、完成编码压缩再发送至流媒体服务器同时包含异常处理和状态监控设计可作研发实时推流工具的基础框架。压缩包内共1150个文件大小约40.76MB其中头文件1032个便于从接口层理解模块关系另外还有C源文件、工程配置文件、动态库与导入库、静态库文件、界面定义文件与Qt资源文件基本做到解压后即可在Windows环境中编译运行。当前已有64人学习下载特别适合学习音视频同步、延迟控制以及网络传输细节的开发者具备较强的工程参考价值。1. 一台设备要把画面推到服务器Qt FFmpeg 做推流端是最省事的组合很多现场都会遇到这个需求工控机上的摄像头画面或者一块屏幕上的操作过程要实时送到服务器让远端的人打开网页或播放器就能看到。设备上不能装 OBS也不能用带界面的重型软件最可靠的做法就是自己写一个推流程序。这类程序业内习惯叫 ffmpegPusher用 Qt 把控制面板、推流地址、启动停止按钮搭起来把 FFmpeg 当作推流引擎负责读取画面、编码、封装成 FLV 再推到 RTMP 服务器。整个软件解决的就是一件事在没有人工干预的环境里稳定地把一路画面推到指定地址。它适合直播推流、远程设备调试、多路画面感知这类场景也是嵌入式板卡上做音视频上传的常用方案。2. 工程骨架先立住Qt 版本怎么选FFmpeg 库怎么才能被程序找到写推流软件最忌讳一上来就写写包循环。我一般先花半小时把工程搭稳确认一条命令行能把视频推到服务器再进入 Qt 代码阶段。这一章先把 Qt 和 FFmpeg 的关系理清楚再把工程接入方式、依赖库列表讲透。2.1 为什么是“Qt 做面板、FFmpeg 做引擎”而不是用 QtMultimedia 一把梭Qt 自带的多媒体模块能做采集和播放但推流这件事它并不擅长。RTMP 封装、H.264 编码参数控制、断线重连、时间基换算这些能力 FFmpeg 体系里已经沉淀了十几年。用 QtMultimedia 去拼这些逻辑等于把 FFmpeg 已经解决过的问题重新发明一遍。常见做法是界面层完全交给 Qt把所有推流逻辑封装在一个后台线程里通过信号槽把进度和错误传回界面。FFmpeg 不感知 Qt 存在它只管拿到一帧数据编码写进网络。两边各干各的接口清晰后期不管是把推流协议从 RTMP 换成 SRT还是把编码从软编换成硬编界面层都不用动。2.2 把 FFmpeg 头文件和库接进 Qt 工程pro 文件到底要写什么如果你是 windows 环境常见做法是去 FFmpeg 官网下载 essentials 构建包解压后得到 include、lib、bin 三个目录然后把 bin 目录加进 PATH把 include 和 lib 路径写进 Qt 的 pro 文件。注意 32 位和 64 位要跟你的 Qt 套件一致这个不一致会在程序运行时才暴露后面避坑章节会专门讲。# ffmpegPusher.pro 关键片段 QT core gui greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET ffmpegPusher TEMPLATE app CONFIG console c11 CONFIG - app_bundle # FFmpeg 头文件路径按你自己的解压目录改 INCLUDEPATH D:/libs/ffmpeg/include LIBS -LD:/libs/ffmpeg/lib \ -lavformat \ -lavcodec \ -lavutil \ -lswscale \ -lswresample这段配置里CONFIG console很有用它保证程序启动时把 FFmpeg 自己的日志输出到终端出问题时能直接看到是哪一步报错。swscale和swresample在纯转封装场景下用不到但如果后面要做画面缩放或者音频重采样就得提前链进来。还有一个细节如果是 MinGW 套件FFmpeg 的库文件要选libxxx.dll.a那种导入库如果是 MSVC则要选.lib文件。两者混用会出现链接错误而且报错信息很有迷惑性。工程能编译通过算第一步接下来要确认程序运行时能不能找到 FFmpeg 的 DLL。Windows 上最常见的报错是“找不到 avformat-59.dll”。我一般把 FFmpeg 的 bin 目录里所有 DLL 直接拷到 exe 同级目录然后用 windeployqt 部署 Qt 的运行库。在这之前先手动把 bin 目录加到系统 PATH也方便后面直接跑命令行验证。2.3 先别写代码用一条命令把本机和服务器之间的通路验证掉推流软件的调试难点在于如果程序推不上去你不知道是代码问题、网络问题还是服务器问题。所以我在动手写推流循环之前一定会先具备两个条件一个可用的 RTMP 服务器一条能推通的 ffmpeg 命令行。服务器最简单的方式是用 Docker 跑一个 SRS推流和拉流都能测。# 拉取 SRS 镜像并启动默认监听 1935 端口 docker run --rm -p 1935:1935 -p 8085:8080 ossrs/srs:4 ./objs/srs -c conf/rtmp.confSRS 启动后用 ffmpeg 推一条测试视频流过去再用 ffplay 拉流确认通了。这一步跑通说明网络、服务器、端口都没问题后面写的代码就只需要聚焦在 FFmpeg 接口调用上。# 把本地 flv 文件以原始编码方式推流不加转码用来验证链路 ffmpeg -re -i test.flv -c copy -f flv rtmp://127.0.0.1/live/test # 另一个终端执行看到画面说明服务器和端口都正常 ffplay rtmp://127.0.0.1/live/test-re参数的意思是按文件原始帧率读取模拟实时推流-c copy不做重编码速度最快只验证封装和网络链路。如果 test.flv 还没有先用 FFmpeg 生成一个测试视频源ffmpeg -f lavfi -i testsrcsize1280x720:rate25 -t 10 test.flv。这一步的目的很纯粹——把所有可能出问题的外部因素先排除掉后面写推流代码的时候出问题就只在代码里找原因。3. 推流主链路手写一遍从输出上下文到写包收尾接口逐个过链路验证通了下面进入核心。这一章我会把推流主流程按顺序拆成四段代码创建输出上下文、给输出流设置参数、写封装头、循环写包最后收尾释放。这套流程无论是读一个本地 FLV 转而推流还是接收编码器输出的 AVPacket 再推送骨架都一样。我把每一段代码的关键参数和常见取舍写清楚。3.1 创建输出上下文avformat_alloc_output_context2 和 avio_open第一步是拿到一个 AVFormatContext。这个结构体代表“你要输出成什么格式”指定 flv 就是封装成 FLV指定 mpegts 就是封装成 TS。推 RTMP 地址时格式可以显式传 flv也可以让它从 URL 后缀推断但显式传更稳。// 创建推流上下文输出格式指定为 flv AVFormatContext *fmt_ctx nullptr; const char *rtmp_url rtmp://127.0.0.1/live/test; int ret avformat_alloc_output_context2(fmt_ctx, nullptr, flv, rtmp_url); if (ret 0 || !fmt_ctx) { // 用 av_strerror 把负数错误码转成可读信息 char errbuf[AV_ERROR_MAX_STRING_SIZE] {0}; av_strerror(ret, errbuf, sizeof(errbuf)); qCritical() 创建输出上下文失败: errbuf; return; } // 打开网络输出通道 ret avio_open(fmt_ctx-pb, rtmp_url, AVIO_FLAG_WRITE); if (ret 0) { char errbuf[AV_ERROR_MAX_STRING_SIZE] {0}; av_strerror(ret, errbuf, sizeof(errbuf)); qCritical() 打开 RTMP 输出失败: errbuf; return; }avformat_alloc_output_context2 的第三个参数传 flv第四个参数传 rtmp 地址这个组合在 FFmpeg 内部会找到对应的 RTMP 协议处理器和 FLV 封装器。avio_open 打开的是网络 IORTMP 握手在这个阶段发生。如果这一步失败大概率是地址写错、端口没监听或者服务器回包异常。错误码是负数比如 -5 表示 I/O 错误-1094995529 这种看着像乱码的值其实是 AVERROR 系列用 av_strerror 转一下就知道具体原因了。3.2 给输出流设置参数codecpar、time_base、gop_size 一个都不能省输出上下文创建完要往里添加一条或多条流。最常见的推流软件只有视频流但直播场景通常需要音频所以代码里用两个 avformat_new_stream 分别建视频和音频流。这里的关键是把 AVCodecParameters 填对FFmpeg 新版里不再直接操作 AVStream 的 codec 字段全部改为通过 codecpar 传递参数。// 如果是从文件转封装直接复制输入流参数是最稳的写法 AVStream *in_stream ifmt_ctx-streams[video_stream_index]; AVStream *out_stream avformat_new_stream(fmt_ctx, nullptr); avcodec_parameters_copy(out_stream-codecpar, in_stream-codecpar); out_stream-time_base in_stream-time_base; // 如果是自己编码后的流需要手动逐项设置 out_stream-codecpar-codec_type AVMEDIA_TYPE_VIDEO; out_stream-codecpar-codec_id AV_CODEC_ID_H264; out_stream-codecpar-width 1280; out_stream-codecpar-height 720; out_stream-codecpar-format AV_PIX_FMT_YUV420P; out_stream-codecpar-bit_rate 2500000; out_stream-time_base (AVRational){1, 90000};video_stream_index 是从输入上下文里遍历出来的遍历时判断in_stream-codecpar-codec_type AVMEDIA_TYPE_VIDEO。转封装场景直接用 avcodec_parameters_copy这样 SPS、PPS、音频采样率这些信息全部原样带过去最不容易出错。手动设置参数时time_base 这里写成 1/90000 是遵循 H.264 的 90k 时间基惯例避免 dts/pts 精度不够导致跳帧。width 和 height 必须是偶数FFmpeg 对奇数分辨率会报错这是一个隐藏很深的坑。如果推出去的流要兼容大多数播放器视频流还需要在写头之前把 extradata 准备好。H.264 的 SPS/PPS 有两种携带方式一种是放在 avcC 容器里即写在 extradata 中另一种是放在每个关键帧 NALU 前面。RTMP/FLV 要求的是前者。如果你用的是转封装方式extradata 已经通过 avcodec_parameters_copy 复制过去了如果是编码器刚编码完的流需要在编码器初始化时设置AV_CODEC_FLAG_GLOBAL_HEADER使编码器把 SPS/PPS 放进 extradata否则写头时 FLV 封装器找不到参数集流就推不出去。3.3 写封装头与写包循环pts/dts 换算和 av_interleaved_write_frame所有流参数设置完成调用 avformat_write_header 写入 FLV 头然后进入推流循环。推流循环的核心是拿 AVPacket调整时间基交给 av_interleaved_write_frame 写出去。这个过程对新手来说最容易写错的是时间基换算。// 写入 FLV 封装头 ret avformat_write_header(fmt_ctx, nullptr); if (ret 0) { char errbuf[AV_ERROR_MAX_STRING_SIZE] {0}; av_strerror(ret, errbuf, sizeof(errbuf)); qCritical() 写入封装头失败: errbuf; avio_closep(fmt_ctx-pb); return; } // 推流循环从输入读包或者从编码器收包 AVPacket pkt; while (running) { // 以转封装为例从输入文件循环读包 ret av_read_frame(ifmt_ctx, pkt); if (ret 0) break; AVStream *in_st ifmt_ctx-streams[pkt.stream_index]; AVStream *out_st fmt_ctx-streams[pkt.stream_index]; // 时间基换算把输入流的时间戳转换为输出流的时间基 av_packet_rescale_ts(pkt, in_st-time_base, out_st-time_base); pkt.pos -1; // av_interleaved_write_frame 内部会排序防止 dts 乱序 ret av_interleaved_write_frame(fmt_ctx, pkt); av_packet_unref(pkt); if (ret 0) { char errbuf[AV_ERROR_MAX_STRING_SIZE] {0}; av_strerror(ret, errbuf, sizeof(errbuf)); qCritical() 推流写包失败: errbuf; break; } } // 推流结束写 trailer 并释放网络连接 av_write_trailer(fmt_ctx); if (fmt_ctx fmt_ctx-pb) { avio_closep(fmt_ctx-pb); } avformat_free_context(fmt_ctx);av_packet_rescale_ts 会同时对 pts、dts、duration 做等比换算。这里有个细节pts 和 dts 如果是 AV_NOPTS_VALUE这个函数会自动跳过所以不用额外判断。pkt.pos -1表示这是流媒体数据不是文件数据不设置也行但设置后不会往 FLV 里写入多余的偏移信息。av_interleaved_write_frame 和 av_write_frame 的区别值得单独说明。av_write_frame 是直接把包交给封装器不保证包的顺序av_interleaved_write_frame 会维护内部缓冲把 dts 乱序的包排好再写。从编码器出来的包可能因为 B 帧导致 dts 不单调递增所以优先用 av_interleaved_write_frame。代价是它会拷贝缓冲、增加一点延迟但对推流稳定性来说完全值得。3.4 收尾阶段最容易忽略的细节trailer 写失败也要清理av_write_trailer 之前必须先确保所有包都写完了。有时候推流到一半网络断了trailer 写不进去这是正常的。但无论如何avio_closep、avformat_free_context 必须执行否则内存和 socket 句柄都会泄漏。推流软件常常是长时间运行的程序泄漏一次两次看不出来挂机一天必然崩。收尾代码要放在 RAII 封装里或者用一个 finally 逻辑包起来确保任何路径都能走到。很多推流软件死在“退出时崩溃”而不是“推流中崩溃”就是因为退出逻辑里没有处理部分初始化的情况。比如 avio_open 失败后 fmt_ctx 已经分配了但 pb 是空的这时候直接 avio_closep(fmt_ctx-pb) 会触发空指针。所以收尾时先判断 fmt_ctx 和 fmt_ctx-pb 是否有效再逐个释放这个习惯建议从第一天就养成。4. 数据从哪来三种采集编码接法以及让延迟可控的关键参数推流管道的源头在数据。FFmpegPusher 这类软件的输入可以是本地文件、摄像头采集画面、或者另一个程序送进来的 H.264 裸流。不同来源决定了代码里要不要挂编码器也决定了 CPU 占用和延迟表现。这一章先讲清楚数据源的几种接法再给出我常用的编码参数组合。4.1 三种数据接入方式转封装、软编、硬编怎么选第一种是转封装输入已经是编码后的流比如本地 FLV 文件、RTSP 摄像头的 H.264 流代码里只做解封装和重新封装不做像素级处理。这种方式 CPU 占用极低延迟也最低适合“把一路已有的流搬到 RTMP 服务器”的场景缺陷是你没办法改分辨率、加 logo、调码率。第二种是软编拿到的是摄像头原始画面 YUV 或 RGB用 FFmpeg 内置的 x264 编码器压缩。这种方式最灵活可以任意控制分辨率、码率、帧率、GOP但 CPU 占用高——720p 25 帧编码大概要占满一个中端 CPU 核心。第三种是硬编调用 Intel QSV、NVIDIA NVENC 或树莓派上的硬件编码器。硬编延迟低、省电坑也最多参数兼容性参差不齐但工控机或嵌入式设备要跑长时间推流硬编几乎是必选项。4.2 编码器参数里真正影响延迟和画质的四个设置gop、preset、tune、threads很多人配置编码器只设置 bit_rate 和 width、height结果推出来的流延迟两三秒播放端看着卡。这里的问题不在网络在编码器参数。整理一张我自己常用的参数表参数推荐值作用gop_size25 到 50关键帧间隔决定播放端能多快切入画面presetveryfast编码速度与压缩率权衡推流优先速度tunezerolatency关闭编码器内部缓冲显著降低延迟threadsauto自动根据 CPU 核心数分配线程// 编码器上下文初始化片段 AVCodecContext *enc_ctx avcodec_alloc_context3(codec); enc_ctx-bit_rate 2500000; enc_ctx-width 1280; enc_ctx-height 720; enc_ctx-time_base (AVRational){1, 25}; enc_ctx-framerate (AVRational){25, 1}; enc_ctx-gop_size 50; enc_ctx-max_b_frames 0; // 推流场景建议设为 0 // x264 特有参数通过 priv_data 传入 av_opt_set(enc_ctx-priv_data, preset, veryfast, 0); av_opt_set(enc_ctx-priv_data, tune, zerolatency, 0); av_opt_set(enc_ctx-priv_data, threads, auto, 0);max_b_frames 设 0 很关键。B 帧能提升压缩率但会引入解码顺序重排增加端到端延迟而且很多低端播放器对 B 帧支持不好。OBS 这类直播软件默认不开 B 帧原因就在这里。gop_size 设 50 表示每 2 秒一个关键帧25 fps 下这个间隔对普通直播足够了。如果调试时需要播放端快速打开画面可以临时把 gop_size 调到 1确认流畅后再调回来。4.3 推流线程该怎么排写包循环不能占用 UI 线程缓冲队列要限长Qt 程序里最忌讳把 av_interleaved_write_frame 放在按钮的点击槽里直接执行。网络抖动一次这个函数就可能阻塞几百毫秒界面就会卡住用户会以为程序崩溃了。常见做法是开一个专门的工作线程负责推流UI 线程只负责把任务和参数传递进去。线程之间用 Qt 的信号槽通信或者用一个简单的线程安全队列。我一般会在推流线程内部再做一层缓冲一个 std::queue 存放待推送的 AVPacket队列长度上限设 30。达到上限后丢弃队尾最老的包优先保留关键帧。如果关键帧都满了那就丢掉旧关键帧之后的所有包保证新数据能进来。这套策略不完美但能在网络抖动时保证延迟不无限增大。丢包的逻辑要谨慎只丢非关键帧不能把刚拿到的关键帧丢掉否则播放端画面会一直卡在花屏状态。5. 推流避坑记录库不匹配、连不上服务器、花屏跳帧都在这做推流软件最花时间的不是写代码是排错。下面这几条是我自己在开发 FFmpegPusher 这类程序时反复踩过的坑每一条都按照现象、原因、解决来写。遇到同样问题可以直接对照排查。5.1 fatal: cannot mix incompatible Qt library (version ex50601) with this librar现象程序编译通过运行瞬间弹窗报错提示 Qt 库版本不兼容或者干脆在 stderr 里打出一段fatal: cannot mix incompatible Qt library (version ex50601) with this library。原因Qt 的库有两种编译套件MinGW 和 MSVC。程序链接时用的头文件是一套版本运行时加载的 DLL 是另一套版本。最常见的场景是之前装过另一个 Qt 版本它的 bin 目录残留在 PATH 里排在了前面程序启动时加载了错误的 Qt 库。版本号里的 ex50601 这种写法表示某个构建的特定版本标记问题本质是多个 Qt 环境互相污染。解决编译前先跑qmake -v确认当前用的 qmake 路径和开发工具包一致运行时用Dependency Walker类的工具看程序实际加载的 Qt DLL 出自哪个目录。最直接的办法是编译完程序后把 PATH 里所有 Qt 相关的旧路径删掉只保留当前套件的 bin 目录然后重新运行。我自己的习惯是项目 pro 文件里写死所有依赖库的绝对路径绝不靠 PATH 碰运气。5.2 avformat_write_header 返回 0服务器也连上了但播放端就是没画面现象日志里 write_header 成功推流端没有任何报错用 ffplay 拉流转圈画面一直出不来。服务器管理页能看到连接但查不到流信息。原因H.264 的 SPS/PPS 没有正确传给封装器。FLV 封装要求视频流第一个包必须是关键帧而且关键帧的 NALU 里要带 SPS/PPS或者通过 extradata 在 FLV 头里声明。如果是自己调编码器产生的流没有设置AV_CODEC_FLAG_GLOBAL_HEADER编码器会把 SPS/PPS 放在每个关键帧前面FLV 封装器从 extradata 里拿不到参数就不会往 FLV 头里写 avcC 配置。播放端拿到流之后等不到 SPS/PPS解码器就一直停在初始化状态。解决在编码器初始化时给 enc_ctx-flags 加上AV_CODEC_FLAG_GLOBAL_HEADER然后调用 avcodec_open2 后检查 enc_ctx-extradata 是否为非空。拿到 extradata 后通过 avcodec_parameters_from_context 把参数同步到输出流。这样 FLV 头里就会带 avcC 数据播放端连上就能立即解码。5.3 推流画面卡顿服务器日志提示 keyframe too far apart 或者 dts 异常现象程序能推流但播放端时不时卡一下服务器日志报关键帧间隔过长或者报 dts 乱序。原因时间基换算没写好。输入流和输出流的 time_base 不一致时pts/dts 换算错误服务器收到的关键帧位置错乱。另一种情况是 gop_size 设得太大比如设了 25025fps 下就是 10 秒才一个关键帧网络一丢包播放端就得等下一个关键帧才能恢复画面。解决用 av_rescale_q 或 av_packet_rescale_ts 统一把每个包的时间戳换算到输出流的时间基。关键帧间隔控制在 2 秒以内25fps 对应 gop_size 50。改完之后用一条命令检查实际推流结果ffprobe -v trace -show_frames -select_streams v rtmp://127.0.0.1/live/test看输出里是不是每隔约 50 帧出现一个key_frame1。5.4 嵌入式板上运行报 qt.qpa.plugin: could not find the qt platform plugin linuxfb现象程序部署到树莓派或者 ARM 工控板运行时报错qt.qpa.plugin: could not find the qt platform plugin linuxfb界面起不来。原因Qt 的程序运行依赖平台插件。开发机上插件在 Qt 安装目录的 plugins/platforms 下交叉编译部署到板子时这个目录没跟着拷过去或者环境变量没有指定插件路径。linuxfb 是 Linux 下无窗口系统时的平台插件嵌入式推流设备经常要依赖它。解决把platforms/libqlinuxfb.so拷贝到程序运行目录的 platforms 文件夹下启动程序前设置 QT_QPA_PLATFORM_PLUGIN_PATH 指向该目录。如果板子上有 X11 或 Wayland也不建议直接用 linuxfb先确认系统里真正的窗口系统是什么。还有一个经常犯的错插件架构是 ARM 的开发机上拷贝过去的还是 x86 编译版本运行时会报 CRC 或者加载失败。5.5 推流几小时后 CPU 占用逐渐升高最后程序被系统杀掉现象程序刚启动时 CPU 正常挂机两三个小时后 CPU 占用越来越高内存也持续增长最后系统 OOM 把进程杀掉。原因AVPacket 或 AVFrame 没有正确释放。每次 av_read_frame 返回的 packet 都要用 av_packet_unref 释放否则 refcount 一直占用内存。另一个常见泄漏点是编码器那里av_frame_alloc 申请的视频帧没有及时 av_frame_unref。程序崩溃时 FFmpeg 内部线程可能还在跑不显式清理也会导致资源回收不干净。解决这就是“FFmpeg 是 C 库内存全靠自己管”的典型体现。我习惯对每个 AVPacket 的释放封装成一句av_packet_unref(pkt)并且保证它在每次循环迭代末尾执行不管中途是否 return。推流线程退出前按顺序 avcodec_close 关闭编码器、avformat_free_context 释放上下文。想验证是否有泄漏可以在推流线程里加一个计数每 1000 个包打印一次当前内存占用观察是否线性增长。6. 用 ffprobe 量化推流质量再用 SRT 给软件留一条逃生通道程序能推流是起点能证明它推得“对”才是另一回事。我见过太多开发者只靠 ffplay 里画面能动就宣布完事结果分辨率、码率、关键帧间隔全是乱的。ffplay 能播不代表流是对的。正确的验证方式是用 ffprobe 拉流抓取流信息量化检查几个硬指标。建立一个自己的验证清单养成推流后固定跑一遍的习惯能省掉后续大量联调时间。# 查看推流的基本信息编码格式、分辨率、码率、音频参数 ffprobe -v error -show_streams -show_format rtmp://127.0.0.1/live/test # 下载一段推流数据落盘再对文件做帧级检查 ffmpeg -i rtmp://127.0.0.1/live/test -c copy -t 10 dump.flv ffprobe -v error -select_streams v -show_frames -show_entries framekey_frame,pict_type,pts_time dump.flv第二行命令的输出里重点看关键帧间隔。每个pict_typeI的帧之间的时间差应该在 2 秒左右。如果间隔忽长忽短说明推流端的 GOP 控制有问题。同时看 pts_time 是否单调递增如果出现回跳说明时间基换算逻辑还存在边界情况。验证通过后还有一个方向值得提前布局RTMP 在公网上被运营商限速很常见弱网环境下延迟和卡顿往往不可控。SRT 协议专为公网传输设计抗丢包能力比 RTMP 好很多。设计软件架构时不要把 RTMP 写死在核心链路里而是把“协议名 地址”当作字符串传入底层。这样后面改 SRT 只需要在创建输出上下文时把格式名换成 mpegts、URL 前缀换成 srt://代码骨架完全不用动。我当时就是因为把 RTMP 硬编码在了推流循环里后来接 SRT 时被迫重构了半个模块这种教训一次就够。从那以后任何推流软件我都把协议层和业务层分开哪怕前期只做 RTMP也预留好抽象层。如果这篇文章能帮你省掉这部分重构成本那这个方向就值得继续往下做。希望帮到你。本文还有配套的精品资源点击获取