简介基于Qt与FFmpeg实现的推流软件源码包面向C/Qt开发者以及有音视频传输需求的流媒体初学者。资源围绕ffmpegPusher推流程序展开完整覆盖初始化FFmpeg、配置编码器、输出流设置、音视频捕获、编码封装、网络发送及异常监控等核心环节适合学习RTMP等推流协议与Qt多媒体集成时参考。压缩包共1150个文件约40.76MB以h头文件、cpp源文件为主包含dll/lib运行与依赖库、pro/vcxproj工程文件、资源文件及编译配置便于在Qt项目中直接打开构建目录结构清晰可快速定位推流模块。目前已有64人学习作为一套小而完整的推流实践样本能够帮助读者从源码层面理解本地音视频数据如何经过编码和网络传输发送至流媒体服务器同时提供音视频同步、延迟控制以及错误处理等真实问题的处理参考对进阶开发很有价值。1. 基于 Qt FFmpeg 的推流源码ffmpegPusher 到底解决了什么如果你手头刚好有一个 Qt 界面程序想把摄像头画面或本地视频实时推到 RTMP 服务器然后发现 qtffmpeg 推流这条路坑比想象的多API 版本对不上、编码器参数一错就黑屏、时间戳不对就花屏查一圈文档还没法直接跑通那这份 ffmpegPusher 源码压缩包大概率能帮你省掉两三天的试错时间。它本质上是一个把 FFmpeg 静态库.a 文件集成进 Qt 工程的完整推流程序里面包含了 libavcodec、libavformat、libswscale 等八个静态库以及配套的 moc 预编译文件。整套东西解决的核心问题很明确本地音视频数据怎么编码、怎么封装、怎么通过 RTMP 协议发到流媒体服务器以及在这个过程中出现的音视频同步、编码参数、链路异常怎么处理。适合正在写推流工具、直播辅助软件或者想在 Qt 里第一次接触 FFmpeg API 的 C 开发者。2. FFmpeg 静态库与 Qt 工程的融合选型理由与 .pro 搭建2.1 静态库与动态库之争为什么这份源码直接给了 .a很多人在 Qt 里集成 FFmpeg 的第一反应是去官网下载 Windows 动态库版本也就是那一堆 DLL。但动态库方案有一个很烦的问题编译能过运行的时候经常提示找不到 avcodec-59.dll或者版本号对不上直接崩。而且 FFmpeg 的 DLL 之间有依赖关系少一个、乱一个版本整个程序就起不来。发布的时候还得把一堆 DLL 和 exe 一起打包稍有遗漏就是运行时报错。ffmpegPusher 这套源码走的是静态库路线把 libavcodec.a、libavformat.a、libavutil.a 这些 .a 文件直接链接进可执行文件里。这样做的直接好处是发布物只剩一个 exe不用再操心目标机器上有没有装 FFmpeg 运行库。代价是链接时间变长、生成的体积变大但对于推流工具这种工具型软件来说稳定性和分发便利性远比那几 MB 体积重要。这里有一个容易被忽略的点这套库文件是预先用特定编译器编出来的也就是说它和你使用的 Qt 版本存在绑定关系。压缩包里出现 moc_predefs.h.cbt 这个文件说明库的作者在编译 FFmpeg 时使用了一套和 Qt 版本匹配的预定义宏。如果你本机的 Qt 版本和库编译时用的版本差异过大链接阶段就容易报出类似 cannot mix incompatible qt library 的错误这一点在后面的避坑章节会专门展开。2.2 八个静态库各管什么链接顺序不对就别想编过拿到压缩包后先别急着解压建工程先搞清楚这套库里都有哪些静态文件以及它们在推流链路里各自扮演什么角色。源码包列出的 .a 文件总共有八个对应 FFmpeg 的八个模块我按推流时的调用链整理了一下静态库文件职责在推流链路中的角色libavformat.a封装与解封装负责 FLV 封装、RTMP 协议处理libavcodec.a编解码视频 x264 编码、音频 AAC 编码libavfilter.a滤镜处理可做缩放、水印等过滤操作libavdevice.a设备采集从摄像头、麦克风等设备取数据libavutil.a公共工具内存管理、时间戳、日志等基础函数libswscale.a像素格式转换把抓到的帧转成编码器要求的 YUV420Plibswresample.a音频重采样把采集到的音频转成 AAC 编码器要求的格式libpostproc.a后处理一般推流场景用不到留着备选链接顺序是一个特别容易翻车的地方。静态库链接遵循被依赖者靠后的规则avformat 依赖 avcodec、avutilavcodec 依赖 avutilswscale 也依赖 avutil所以 avutil 必须放在最后面。我见过不少人把库顺序随便一摆结果编译报 undefined reference to av_read_frame其实就是顺序错了GCC 链接器是从左往右解析符号的顺序一乱前置库需要的符号在后面还没解析到就报错。2.3 最小可复现的 .pro 配置照抄能编过的版本一个能用的 Qt 工程文件并不复杂关键在于把库路径、链接顺序、C 兼容宏三个点都写对。下面这份 .pro 配置是我照这套源码整理出来的最小可用版本QT core gui network widgets greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET ffmpegPusher TEMPLATE app CONFIG console c11 FFMPEG_ROOT $$PWD/ffmpeg-static INCLUDEPATH $$FFMPEG_ROOT/include LIBS -L$$FFMPEG_ROOT/lib \ -lavformat \ -lavcodec \ -lavfilter \ -lavdevice \ -lswscale \ -lswresample \ -lavutil \ -lpostproc \ -lpthread -lz -lm SOURCES main.cpp pusher.cpp DEFINES __STDC_CONSTANT_MACROS这段配置里的关键点有几个。第一行到第三行是常规的 Qt 模块引入network 用于 RTMP 连接FFMPEG_ROOT 指向你解压静态库的目录目录下必须包含 include 和 lib 两个子目录。LIBS 部分-L 指定了库文件的搜索路径后面一串 -l 则是真正的库链接列表注意顺序avformat 在最前avutil 在最后avfilter 和 avdevice 放在中间因为它们同时依赖 avcodec 和 avutil。最后的 -lpthread、-lz、-lm 是 FFmpeg 本身的外围依赖线程库、压缩库、数学库缺一不可。DEFINES 里的__STDC_CONSTANT_MACROS是很多人在 FFmpeg 入坑时踩的老雷。FFmpeg 用了一些类似于INT64_C的宏如果不定义这个宏在 C 环境下编译会报错。新版 FFmpeg 可能已经不需要了但老版本必须加上。如果你把这份配置原样搬到一个较新 FFmpeg 项目里这个宏大概率是多余的但保留也无害。另外提醒一下如果你用的是 MSVC 编译器而不是 MinGW还需要额外处理一下strcasecmp、snprintf这些 C 函数在 Windows 下的兼容问题。常见做法是在工程里加一个 compat.c里面用_stricmp替代strcasecmp或者直接定义宏映射。这就是为什么压缩包里可能还有一些额外的 .c 文件它们不是在实现业务逻辑而是给 FFmpeg 做平台适配。3. 初始化与编码器配置推流前必须过掉的四个关口3.1 初始化注册老 API 与新 API 的岔路口FFmpeg 的初始化代码非常容易看出这套源码的编译年代因为这跟 API 的演进直接相关。老版本的 FFmpeg 要求在程序开头调用av_register_all()来注册所有的封装器、解封装器和协议而新版本4.x 之后的某个节点把注册动作改成了懒加载这个函数被标记为废弃调不调都不影响。但如果你拿到的是老版本编译的静态库那av_register_all()就仍然是必须的不调用的话后面avformat_find_stream_info会找不到对应的解析器。一套兼容两种时代的初始化写法是这样的#include QCoreApplication #include QDebug extern C { #include libavformat/avformat.h #include libavcodec/avcodec.h #include libavdevice/avdevice.h #include libavutil/time.h } int initPush(const QString rtmpUrl) { // 老版本 FFmpeg 必须手动注册全局组件新版里是个空操作 av_register_all(); avformat_network_init(); avdevice_register_all(); AVFormatContext *ofmtCtx nullptr; int ret avformat_alloc_output_context2(ofmtCtx, nullptr, flv, rtmpUrl.toUtf8().constData()); if (ret 0) { qCritical() alloc output context failed, error code: ret; return ret; } // 后续的编码器配置、AVStream 创建都在这个函数后面继续 return 0; }这里的逻辑链条是这样的av_register_all()做的是全局注册它在老版本中必须第一个调用avformat_network_init()初始化网络库在 Windows 上会调用 WSAStartup不调用的话 RTMP 网络请求会直接失败avdevice_register_all()则是注册输入设备如果后续要接摄像头采集这个就不能省。avformat_alloc_output_context2是创建输出上下文的核心函数四个参数注意一下第一个是输出上下文的指针地址第二个指定封装格式传 nullptr 时让它根据文件后缀或 URL 前缀自动推断第三个强制指定 flv第四个就是推流地址。3.2 输出上下文创建RTMP 地址与 invalid argument推流地址的格式直接影响avformat_alloc_output_context2的成败。常见的错误写法是rtmp://192.168.1.10/live少了应用名后面的流名称服务器无法确定推流到哪个流。一个完整的 RTMP 地址应该长这样rtmp://192.168.1.10:1935/live/test其中 1935 是 RTMP 默认端口live 是应用名test 是流名称可以是任意字符串播放时要用同一个名字拉流。如果传的地址格式不对最常见的返回值是AVERROR(EINVAL)也就是Invalid argument。这时候不要急着怀疑库有问题先检查地址里有没有rtmp://前缀、应用名和流名是不是都齐了。另一个隐藏问题是没有在调用前设置ofmtCtx-oformat如果第二个参数传了 nullptr 而且地址格式不够典型自动推断可能会落空。我在实际项目中更倾向于强制指定flv作为封装格式因为 RTMP 推流必须走 FLV 封装让 FFmpeg 去猜不如直接告诉它。3.3 视频编码器x264 的码控与 GOP 参数视频编码器配置是整个推流程序里参数最密集的一段也是最容易因为照搬网上模板而出问题的地方。这段源码里视频走的是 x264 编码器以下是配置一段 720p 视频编码参数的典型代码AVCodec *codec avcodec_find_encoder_by_name(libx264); if (!codec) { qCritical() libx264 encoder not found; return -1; } AVCodecContext *videoCtx avcodec_alloc_context3(codec); videoCtx-codec_type AVMEDIA_TYPE_VIDEO; videoCtx-width 1280; videoCtx-height 720; videoCtx-pix_fmt AV_PIX_FMT_YUV420P; videoCtx-time_base {1, 25}; videoCtx-framerate {25, 1}; videoCtx-bit_rate 2000000; videoCtx-gop_size 50; videoCtx-max_b_frames 0; videoCtx-qmin 18; videoCtx-qmax 51; if (ofmtCtx-oformat-flags AVFMT_GLOBALHEADER) { videoCtx-flags | AV_CODEC_FLAG_GLOBAL_HEADER; } int ret avcodec_open2(videoCtx, codec, nullptr); if (ret 0) { qCritical() open video encoder failed: ret; }这里面每一个参数都是有用的。width和height决定了编码分辨率pix_fmt必须设成 YUV420P这是 x264 编码器唯一稳妥接受的像素格式输入源如果不是这个格式后面用 swscale 转换。time_base设置为{1, 25}表示编码时基是每秒 25 等分framerate也要同步设成 25 帧每秒两者不一致会导致编码器计算出来的 pts 距离异常。bit_rate设成 2000000 就是 2 Mbps 码率这是一个对 720p 来说清晰度和带宽比较平衡的数值局域网内可以调高到 4Mbps公网建议降到 1.5Mbps 左右。gop_size 50表示每 50 帧插入一个关键帧也就是每两秒一个 I 帧。这个值直接影响播放端起播的延迟和网络波动的抗性GOP 越大起播越慢但同等码率下画质更好GOP 越小起播越快但码率开销更大。直播场景一般用 1 到 2 秒一个 GOP 比较合适我建议 25 到 50 之间别为了画质把 GOP 拉到 250那会让观众端等好几秒才能出画面。max_b_frames 0是低延迟的关键B 帧虽然压缩率高但会引入额外的重组延迟和播放缓冲推流场景一般直接关掉。最后一个看似不起眼但必须做的操作如果封装格式要求全局头就把AV_CODEC_FLAG_GLOBAL_HEADER打上去。FLV 封装就有这个要求不打这个 flag 的话编码器 SPS/PPS 参数会随帧发送部分播放器解析会失败表现就是画面出不来或者花屏。打完标记得在avcodec_open2之后读取videoCtx-extradata并 copy 给 stream 的 extradata这样播放器才能从流头拿到解码参数。3.4 音频编码器AAC 的 sample_fmt 与重采样音频编码的坑和视频不同主要在格式匹配上。FFmpeg 的 AAC 编码器只接受AV_SAMPLE_FMT_FLTP浮点平面格式而我们在 Qt 里从麦克风采集到的数据通常是AV_SAMPLE_FMT_S16带符号整型交错格式两者不匹配的情况下avcodec_open2可能会直接报错或者虽然能打开但编码出来的全是噪音。处理方式就是引入 swresample 库做一次重采样。AVCodec *audioCodec avcodec_find_encoder(AV_CODEC_ID_AAC); AVCodecContext *audioCtx avcodec_alloc_context3(audioCodec); audioCtx-sample_fmt AV_SAMPLE_FMT_FLTP; audioCtx-sample_rate 44100; audioCtx-channel_layout AV_CH_LAYOUT_STEREO; audioCtx-channels 2; audioCtx-bit_rate 128000; SwrContext *swr swr_alloc_set_opts(nullptr, audioCtx-channel_layout, audioCtx-sample_fmt, audioCtx-sample_rate, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100, 0, nullptr); swr_init(swr);这里swr_alloc_set_opts的后三个参数描述的是输入音频的格式双声道、S16 整型交错、44100 采样率。前三个参数是输出格式也就是编码器要求的 FLTP 浮点平面、44100 采样率。每次从设备拿到一帧采集数据后用swr_convert把数据从 S16 转成 FLTP再送入编码器。这里要注意每次转换后输出的样本数可能和输入的样本数不同特别是采样率不匹配的场景需要循环调用swr_convert直到输入缓冲区消费完。项目里如果只做视频推流、不做音频这段可以跳过但完整的 RTMP 流一般建议带上音频否则很多播放器会出现只有画面没有声音的尴尬局面。4. 推流主循环与数据写入让一帧帧数据真正跑上网络4.1 推流循环av_interleaved_write_frame 与时间基换算初始化完成、编码器打开之后推流的工作就进入了一个持续循环抓取原始帧、编码成 AVPacket、写入封装器、发往网络。这段循环是推流程序的发动机代码结构其实不复杂但时序处理非常讲究。下面是一个简化版的视频推流主循环AVPacket pkt; av_init_packet(pkt); AVRational tb stream-time_base; int fps 25; while (!m_stopFlag) { AVFrame *frame grabFrame(); // 从摄像头或文件读取一帧原始画面 if (!frame) { qWarning() grab frame failed, retry...; continue; } int ret avcodec_send_frame(videoCtx, frame); if (ret 0) { qCritical() send frame to encoder failed: ret; av_frame_free(frame); continue; } while (avcodec_receive_packet(videoCtx, pkt) 0) { av_packet_rescale_ts(pkt, videoCtx-time_base, tb); pkt.stream_index stream-index; int wret av_interleaved_write_frame(ofmtCtx, pkt); if (wret 0) { qWarning() write frame failed, network maybe broken: wret; } av_packet_unref(pkt); } av_frame_free(frame); av_usleep(1000000 / fps); }这个循环里最关键的是av_packet_rescale_ts这一行。编码器输出的 AVPacket 里pts 和 dts 是基于编码器 time_base 的而封装器期望的时间基是 stream 的 time_base两者如果不做换算写进 FLV 的时间戳就是错的播放端会花屏或者直接卡住不动。av_packet_rescale_ts做的事情就是把时间戳从一个时间基等比缩放到另一个时间基注意它的赋值方式是双向的函数内部会同时处理 pts 和 dts 以及 duration。另一个值得注意的地方是av_interleaved_write_frame和av_write_frame的选择。前者会在内部维护一个交织缓冲区确保音频和视频包按时间戳交错排列这对于 RTMP 这种实时性要求高的场景至关重要后者则不做任何缓冲按你给的顺序直接写。如果音频和视频分别用自己的线程推流就必须用av_interleaved_write_frame否则最终输出的流里可能出现一长段视频跟着一长段音频的情况播放器要缓冲很久才能开始播放。4.2 音视频同步从盯住 pts/dts 开始推流开发的玄学时刻通常发生在这里明明画面和声音都正常但播放端就是音画不同步且延迟越来越大。原因往往是音频和视频的时间戳基准没有统一。音频的采样率是 44100Hz视频是 25fps两者天然用不同的时钟推进如果不做对齐推流十分钟后音频比画面快了半秒甚至更多。一个可操作的做法是在编码每一帧视频时手动为它计算基于实际时间的 pts而不是依赖编码器内部的帧计数。用av_gettime_relative()获取当前微秒时间换算成视频 time_base 下的数值。这样音频和视频各自以真实时间为基准时间戳自然对齐。注意换算公式pts (double)elapsed_us / av_q2d(stream-time_base) / 1000000.0。这套逻辑放在循环里并不复杂但它是音视频同步的定海神针。4.3 像素格式转换sws_scale 在推流链路中的位置摄像头采集到的原始帧通常是 RGB24 或 NV12而 x264 编码器只接收 YUV420P这中间就必须有一次像素格式转换。FFmpeg 提供的 swscale 库承担这个任务整个转换过程是三行代码的事SwsContext *swsCtx sws_getContext(srcW, srcH, srcPixFmt, dstW, dstH, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); uint8_t *dstData[4]; int dstLineSize[4]; av_image_alloc(dstData, dstLineSize, dstW, dstH, AV_PIX_FMT_YUV420P, 1); sws_scale(swsCtx, srcData, srcLineSize, 0, srcH, dstData, dstLineSize);sws_getContext的第六个参数是缩放算法SWS_FAST_BILINEAR是性能和画质的折中选择追求清晰度可以用SWS_BICUBIC。av_image_alloc用来分配目标帧的内存注意转换完成后用memcpy把它搬到 AVFrame 的数据区里或者更优雅的做法是在创建 AVFrame 时用av_frame_get_buffer分配缓冲区然后直接转进去。sws_scale本身接受源数据指针数组和行大小数组这两个数组可以从采集设备返回的结构体里拿到也可以从 AVFrame 的 data 和 linesize 字段里拿。整个转换过程是纯 CPU 计算720p 分辨率下每帧大概要消耗几毫秒在性能不高的嵌入式板子上会成为瓶颈需要留意帧率是否因此下降。5. 常见问题排查Qt 版本不匹配、invalid argument 与黑屏花屏5.1 编译报错cannot mix incompatible qt library现象在 Qt Creator 里点构建链接阶段报出 fatal: cannot mix incompatible qt library (version ex50601) with this library或者类似无法混用 Qt 库版本的信息整个项目直接编不过。原因这套源码里的静态 FFmpeg 库是用特定版本的 Qt 工具链预编译的moc 预处理文件里带有对应 Qt 版本的宏定义。你本机安装的 Qt 版本如果和它不一致头文件里的QT_VERSION_CHECK宏比较就会失败编译器判定版本不兼容。解决优先在 Windows 上使用与压缩包同期的 Qt 版本打开工程一般是 5.x 系列。如果不想切换 Qt 版本就只能放弃 .a 静态库下载对应你当前 Qt 工具链的 FFmpeg 源码重新编译。这也是我用这套源码时第一个放在心上的约束先看 moc_predefs.h.cbt 里的版本信息再决定用哪个 Qt 打开。5.2 avformat_alloc_output_context2 返回 invalid argument现象程序运行时输出 alloc output context failed, error code: -22也就是 AVERROR(EINVAL)推流根本没开始就中止了。原因绝大多数情况是 RTMP 地址写得不完整或者格式不对。比如只写了rtmp://192.168.1.10:1935/live少了流名FFmpeg 解析 URL 时无法确定最终推流目标。解决把地址补成完整的rtmp://ip:port/app/streamName格式。另一个可能原因是avformat_alloc_output_context2的第二个参数传了 nullptr而 URL 又是动态拼接的格式不够标准导致封装格式推断失败。此时显式把第三个参数传成flv强制指定封装格式问题立即消失。5.3 音视频不同步且差异随时间越拉越大现象播放端看画面还算流畅但音画对不上播放时间越久偏差越明显重开播放器又能对齐几秒然后继续错位。原因音频和视频时间戳分别用自己的帧率计数器在推视频按 25fps 每帧加 1音频按 44100 采样率递增两边的时间基准不对齐累积误差越来越大。解决在推流循环里改用真实时钟校准 pts。用 av_gettime_relative 获取微秒级的时间戳再换算到各自 stream 的 time_base确保实时流的时间戳基于同一个墙钟。修改后重启程序让表现正常后再加入 gop 和码率调整。5.4 播放端黑屏但有声音现象RTMP 流在 VLC 里能连接上音轨有声音画面却一直黑屏或者要等很久突然闪一下画面。原因视频编码器的 SPS/PPS 没有随关键帧发送。FLV 封装要求 extradata 在流头部就写好如果没设置 AV_CODEC_FLAG_GLOBAL_HEADERSPS/PPS 会以附带数据包的形式跟帧一起发送部分播放器等到编码器输出关键帧之前一直拿不到解码参数就会黑屏。解决在 avcodec_open2 之前给 videoCtx 打上 AV_CODEC_FLAG_GLOBAL_HEADER 标记并把编码器输出的 extradata 在创建 AVStream 后 copy 到 stream-codecpar 的 extradata 字段里。做完这一步播放器能在握手阶段就拿到解码器配置画面起播速度会明显改善。5.5 推流一段时间后 av_interleaved_write_frame 写包失败现象程序跑了几分钟甚至几小时后日志里开始刷 write frame failed推流中断但进程没有崩溃。原因网络抖动导致 TCP 连接被对端关闭或者 RTMP 服务端主动断开了长时间没有关键帧的流。前者是网络环境问题后者通常是你把 GOP 调得太大服务端长时间收不到 I 帧判断链路异常。解决在写包失败时主动调用 avformat_free_context 释放资源然后等待网络恢复后重建整套推流链路这是重连的标准姿势。另外检查一下 gop_size 是不是调得过大我建议直播场景不超过 100 帧越大的 GOP 越容易触发服务端保护性断连。6. 验证与调优用 ffprobe 确认流状态再用三组参数压低延迟6.1 先跑通本地 RTMP 服务再测外网拿到这套源码后第一件事不是改代码而是先搭建一个本地 RTMP 服务把链路跑通。常见做法是用 docker 拉一个 nginx-rtmp 镜像docker run -d -p 1935:1935 -p 8080:80 --name rtmp-server tiangolo/nginx-rtmp然后把 ffmpegPusher 的推流地址填成rtmp://127.0.0.1:1935/live/test启动程序。接着用 ffprobe 验证流是否正常ffprobe -v error -show_streams -show_format rtmp://127.0.0.1:1935/live/test正常输出里应该能看到两条流的信息一条 codec_name 是 h264另一条是 aac并且 duration 在持续增长。如果只看到一条流说明编码器或者采集环节有一个没工作。ffprobe 的-show_streams会把每路的宽高、采样率、像素格式、编码参数全部列出来这是快速确认编码配置是否生效的最直接手段。6.2 低延迟参数组合跑通之后要做低延迟调优的话核心是把三处省时间的参数都压到极限。第一处是视频头编码器里把max_b_frames设 0gop_size设 25 到 50这样解码端拿到第一个 I 帧就能立即出画面。第二处是封装头在创建 AVStream 时给codecpar-codec_tag手动设置mktag(f,l,v,1)省掉 FFmpeg 探测编码格式的时间。第三处是网络层avformat_alloc_output_context2之后给ofmtCtx-max_delay设为 0避免封装器为了交织缓冲数据而攒包。参数位置低延迟建议值作用max_b_framesAVCodecContext0去掉 B 帧消除解码端重组等待gop_sizeAVCodecContext251 秒一个关键帧起播即出画面max_delayAVFormatContext0关闭封装器攒包数据到即发bit_rateAVCodecContext1500000-2500000码率过高会加剧网络抖动下的延迟积压这里注意一下 GOP 设 25 在码率上是有代价的I 帧数据量大每秒一个 I 帧会比两秒一个多消耗约百分之十的码率。如果网络带宽紧张可以退回 50起播延迟多不到一秒但画面稳定性更好。这三组参数改完重新编译再用 ffprobe 看到流的 duration 平稳增长、没有大跳变就说明推流链路已经稳定了。这套源码对新手最友好的地方就是整个链路是闭环的你不用去拼凑各个模块直接改参数看效果比我当年从零搭采集、编码、封装三套代码要省太多事。从那以后我每次改完编码器参数都会强制自己先用 ffprobe 跑一遍看时间戳排序和流的完整性再交给播放器做人工确认这个习惯帮我避开了至少三次线上推流事故希望这篇笔记也能帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
手绘教程避坑指南:掌握最佳实践,面试原理不再卡壳 手绘教程避坑指南:掌握最佳实践,面试原理不再卡壳 面试时被问到底层原理,大脑一片空白,这是很多转行开发者的噩梦。你背了一堆八股文,但面试官稍一追问,你就露馅了。其实,问题不出在记忆力,而出在学习方法。… · 2026/9/23 11:05:35
单片机PWM从原理到实战:硬件与软件实现及常见问题解析 1. 从一个“灯不够亮”的抱怨说起:PWM到底在解决什么问题很多刚接触单片机的朋友,第一次听到PWM这个词,大概率是在调LED亮度或者调电机转速的时候。我当时也一样,拿着51单片机点了个LED,发现它要么全亮要么全灭&#x… · 2026/9/23 11:05:29
基于STM32与FreeRTOS的室内空气质量监测开源项目KLL 1. 项目概述:这个KLL到底是什么1.1 项目起源与命名先说清楚KLL是什么。它是我花了两个多月整理出来的一套室内空气质量检测开源项目,软硬件资料全部放出来了,包含STM32固件、传感器驱动、PCB工程、3D外壳模型和配套的上位机脚本。KLL这个名字… · 2026/9/23 11:05:29
君正T40 EVB原理图深度解析:电源树、DDR参考网络与启动配置 简介:北京君正T40EVB原理图是面向AIoT与机器视觉应用的T40通用型SoC评估底板原理图文件,适合嵌入式硬件工程师、方案设计人员、AIoT产品开发者与研究者参考。T40集成双核XBurst2处理器、RISC-V协处理器与8TOPS AI引擎,支持4K ISP及多摄像头输… · 2026/9/23 11:49:18
与的繁体图解原理:3个坑让你面试挂科 与的繁体图解原理:3个坑让你面试挂科 上周有个学员找我吐槽,说面试时被问“与的繁体在数据库里怎么存才不炸”,他愣了半天,只憋出一句“用UTF-8呗”。面试官没说话,直接让他回去等通知。 这就是典型的 面试被问原理答不上来 。… · 2026/9/23 11:49:11
10句经典英文励志名言:低谷时多撑一口气的认知行为疗法 1. 为什么这10句话能让人在低谷里多撑一口气1.1 从“打鸡血”到“真管用”的认知转变很多人第一次接触英文励志名言,是在学生时代的教室墙上,或者朋友圈的配图里。那时候觉得这些话就是“打鸡血”,读起来热血沸腾,合上手机该躺平还… · 2026/9/23 11:49:05
3个技巧搞定i排版微信编辑器性能优化 3个技巧搞定i排版微信编辑器性能优化 配置环境就卡半天,是不是让你抓狂?刚拿到i排版微信编辑器源码,本地跑不起来,或者一排版长文章就卡顿,这种痛我太懂了。很多应届生做技术博客或公众号运营时,第一反应就是装个编辑器工具,结果发现默认的样式在移… · 2026/9/23 11:49:05
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29