C++音视频流媒体实战:从FFmpeg到RTMP/RTSP/WebRTC
最近在做直播中台和视频监控设备的接入方案时经常被问到同一类问题RTMP、RTSP、WebRTC 到底怎么选H264 和 H265 码率差多少FFmpeg 推流命令怎么才能不卡用 C 直接调 FFmpeg 库和直接用命令行有什么区别这些问题看起来零散其实都指向同一条主线C 音视频流媒体链路。本文围绕这条主线结合 FFmpeg、H264、RTMP、RTSP、WebRTC、SRS 等常用技术梳理一套从环境搭建、命令验证、C 集成到服务器部署的完整实战笔记适合刚开始接触音视频的 C 开发者也适合需要快速排错的工程人员。1. 背景与核心概念1.1 为什么 C 是音视频开发的主力语言音视频领域有两类开发者一类用现成 SDK 做业务另一类用 C/C 自己封装底层能力。大多数流媒体服务器、播放器内核、编码器封装层底层都是 C 或 C 写的例如 FFmpeg 本身是 C 库SRS、ZLMediaKit 是 C 项目。C 之所以长期占据主导是因为音视频处理对内存和性能的要求比普通业务系统高很多。在 C 工程中操作 FFmpeg核心是理解指针、引用计数和生命周期。AVPacket、AVFrame 这些结构体并不是普通的类对象它们内部维护引用计数拷贝时要用 av_packet_ref、av_frame_ref释放时要用 av_packet_unref、av_frame_unref。如果只记得 new 和 delete很容易出现内存泄漏、野指针、double free。另外C 的多维数组和指针概念虽然听起来基础但在处理图像数据时非常实用。视频帧本质上是三维数据宽度、高度、通道。RGB24 图像一行的大小是 width * 3YUV420P 图像一行的对齐规则更复杂。很多人在写图像处理算法时被“二维数组当一维数组用”卡住其实理解了指针偏移和步长后面看 FFmpeg 的 linesize 就会非常轻松。所以想走音视频方向C 语法基础尤其是指针和内存管理一定要先夯实。1.2 音视频播放链路全景音视频链路看起来复杂拆开以后其实是一条清晰的数据流水线采集端 → 编码 → 封装 → 传输协议 → 流媒体服务器 → 分发协议 → 播放端以最常见的直播场景为例摄像头采集 → H264 编码 → FLV/TS 封装 → RTMP 推流 → SRS/ZLMediaKit → RTMP/HTTP-FLV/HLS/WebRTC → 播放器采集端负责拿到原始图像和声音编码器把原始数据压缩成 H264、H265 或 AAC封装格式决定数据如何组织传输协议决定数据如何发送流媒体服务器负责接收、转协议、分发。C 开发者的工作通常集中在这几个环节封装 FFmpeg 的拉流、推流、解码、编码逻辑。基于 SRS、ZLMediaKit、Mediamtx 搭建流媒体服务。对传输链路做稳定性、延迟、安全方面的优化。这条链路中任意一环出问题表现都是“卡顿、黑屏、延迟高”但排查方向完全不同所以先把链路图记在脑子里再做实操。1.3 常见协议与编码格式速览RTMP、RTSP、WebRTC 是三种最常见的流媒体协议H264、H265 是最常见的视频编码格式。它们各自的定位差异很大选择时主要看播放端环境。协议/编码全称特点典型场景RTMPReal-Time Messaging Protocol基于 TCP生态成熟延迟一般 1~3 秒直播推流、传统 Flash 生态、国内直播平台RTSPReal Time Streaming Protocol控制信令与 RTP 媒体传输分离支持回放控制摄像头、安防监控、工业视觉WebRTCWeb Real-Time Communication基于 UDP弱网优先丢帧延迟可以做到 300~800ms互动直播、视频会议、远程操作H264H.264/AVC兼容性极好软硬解码支持最广直播、监控、浏览器播放H265H.265/HEVC同等清晰度下码率比 H264 省 30%~50%兼容性较弱4K/8K、存储受限的监控场景后续的实战环节会围绕这些协议和编码展开先记住一点协议解决“怎么传”编码解决“怎么压”两者是独立维度。2. 环境准备与版本说明2.1 FFmpeg 安装与常用验证方式FFmpeg 是本文的核心工具既可以用命令行做快速验证也可以在 C 工程中通过 libavformat、libavcodec、libavutil 等库调用。Windows 环境最简单的方式是到 FFmpeg 官网下载编译好的二进制包解压后把 bin 目录加入 PATH然后打开终端验证ffmpeg -versionLinux 环境以 CentOS 7 为例可以通过源码编译安装。示例命令如下实际版本请以官方发布为准yum install -y yasm nasm git git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg cd ffmpeg ./configure --prefix/usr/local/ffmpeg --enable-gpl --enable-libx264 --enable-libx265 --enable-network make -j$(nproc) make install ln -s /usr/local/ffmpeg/bin/ffmpeg /usr/local/bin/ffmpeg这里要注意--enable-libx264 和 --enable-libx265 需要系统已经安装 x264、x265 开发库否则 configure 阶段会报错。如果只是做转封装、拉流、推流不强依赖这两个编码库。FFmpeg 不同版本之间 API 有差异项目中使用 C 集成时要对版本做统一管理避免开发环境和服务器环境不一致。2.2 流媒体服务器选型C 音视频链路中除了 FFmpeg还需要一台流媒体服务器。常见选择有 SRS、ZLMediaKit、Mediamtx三者的定位不同。服务器定位主要协议特点SRS直播服务器RTMP、WebRTC、HLS、HTTP-FLV、SRT部署简单直播能力全面社区活跃ZLMediaKit高性能流媒体服务RTSP、RTMP、HLS、WebRTC、GB28181安防和标准流媒体协议支持好适合二次开发Mediamtx轻量级流媒体网关RTSP、RTMP、HLS、WebRTC配置文件简单适合本地调试和边缘设备SRS 和 ZLMediaKit 都是 C 写的非常适合 C 开发者阅读源码、二次开发。Mediamtx 是 Go 写的但它的配置方式和协议转发能力非常适合做通道验证。2.3 示例工程目录结构为了便于后续实战先规划一个最小工程目录media-demo/ ├── CMakeLists.txt ├── conf/ │ └── rtmp.conf ├── src/ │ ├── push_rtmp.cpp │ ├── pull_rtsp.cpp │ └── decode_h264.cpp └── README.mdpush_rtmp.cpp 负责把本地视频文件转推到 RTMP 服务器pull_rtsp.cpp 负责从 RTSP 地址拉流并解码decode_h264.cpp 可以单独演示 H264 解码逻辑。后面章节会逐步给出这些文件的代码。3. FFmpeg 基础与 C 调用方式3.1 FFmpeg 常用命令与参数解释FFmpeg 的命令行能力非常强大很多 C 工作可以先在命令行中验证通过再封装成库调用。下面列出几个高频命令并解释关键参数。查看媒体文件信息ffmpeg -i input.mp4覆盖输出文件-y 的含义是“当输出文件已存在时直接覆盖不再交互确认”ffmpeg -y -i input.mp4 -c copy output.mp4把 m4s 文件转封装为 mp4这也是很多缓存视频修复场景的常用操作ffmpeg -i 1.m4s -c copy 1.mp4合并多个 TS 文件推荐使用列表文件方式echo file segment1.ts filelist.txt echo file segment2.ts filelist.txt echo file segment3.ts filelist.txt ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4从 RTSP 地址拉流并保存为 mp4指定 -rtsp_transport tcp 可以避免 UDP 传输在跨网络时丢包ffmpeg -rtsp_transport tcp -i rtsp://user:passip:554/stream -c copy -f mp4 output.mp4向 RTMP 服务器推流这是直播场景最常用的命令ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 50 -c:a aac -f flv rtmp://127.0.0.1:1935/live/demo这里的 -re 表示按原始帧率读取输入避免推流速度过快-g 50 设置 GOP 大小为 50也就是每 50 帧一个关键帧-tune zerolatency 是 x264 的低延迟参数适合直播场景。3.2 在 C 工程中集成 FFmpegC 工程集成 FFmpeg推荐使用 CMake pkg-config 的方式。需要安装 FFmpeg 开发库包含头文件和 so/dll 文件。一个最小 CMakeLists.txt 如下# 文件路径media-demo/CMakeLists.txt cmake_minimum_required(VERSION 3.16) project(media_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(PkgConfig REQUIRED) pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavformat libavcodec libavutil ) add_executable(push_rtmp src/push_rtmp.cpp) target_link_libraries(push_rtmp PRIVATE PkgConfig::FFMPEG) add_executable(pull_rtsp src/pull_rtsp.cpp) target_link_libraries(pull_rtsp PRIVATE PkgConfig::FFMPEG)小技巧在源码中引入 FFmpeg 头文件时需要使用 extern C 包裹避免 C 名字修饰导致链接失败extern C { #include libavformat/avformat.h #include libavcodec/avcodec.h #include libavutil/avutil.h }3.3 H264 与 H265 编码选择H264 是当前兼容性最好的编码格式浏览器、手机、摄像头、RTMP 生态都支持H265 压缩率更高但在 WebRTC 和部分浏览器中的兼容性不如 H264而且存在专利授权方面的历史问题。工程中并非“H265 一定更好”而是要看播放端和转码成本。从经验值来看1080p 30fps 的视频编码格式典型码率范围H2643~5 MbpsH2652~3 MbpsH264 低码率1.5~2.5 MbpsH265 低码率0.8~1.5 Mbps注意这是工程经验值不是官方标准。实际码率取决于画面复杂度、帧率、编码预设。FFmpeg 中使用 libx264 和 libx265 编码的命令示例如下ffmpeg -i input.mp4 -c:v libx264 -b:v 4M -preset medium -c:a aac out_h264.mp4 ffmpeg -i input.mp4 -c:v libx265 -b:v 2.5M -preset medium -c:a aac out_h265.mp4相同清晰度下 H265 码率更低但编码耗时通常更长。在实时推流场景中如果播放端 H264 都能满足需求优先 H264如果存储空间紧张或播放端明确支持 H265再考虑 H265。4. RTMP 推流实战4.1 RTMP 协议特点与延迟分析RTMP 是 Adobe 推出的流媒体协议基于 TCP 长连接。它把视频、音频、控制消息都封装在一个个消息块中通过同一个连接传输。国内早期直播、秀场、游戏直播大量使用 RTMP到现在依然是许多推流端和服务器之间的首选协议。RTMP 的常见延迟在 1~3 秒左右。延迟来源不只是协议本身还包括推流端编码缓冲比如 x264 的 lookahead。服务端转封装、转发队列缓冲。播放器的 jitter buffer 抖动缓冲。TCP 拥塞控制导致的排队。RTMP 有一个明显的短板默认协议不支持浏览器直接播放。浏览器要看 RTMP 流要么通过 HTTP-FLV 转发要么通过 WebRTC 转换要么由服务器转为 HLS。所以实际生产架构中RTMP 通常只做推流协议分发端根据播放场景再转成 HTTP-FLV、HLS 或 WebRTC。本地调试时可以用环回地址作为测试地址不建议直接使用公网第三方测试地址rtmp://127.0.0.1:1935/live/demo4.2 使用 SRS 搭建 RTMP 推流服务器SRS 是国产开源流媒体服务器对 RTMP、WebRTC、HLS、HTTP-FLV 支持很完善。先用源码方式搭建一个最小 RTMP 服务假设在 CentOS 或 Ubuntu 环境git clone --depth 1 https://github.com/ossrs/srs.git cd srs ./configure make -j$(nproc) ./objs/srs -c conf/rtmp.conf不同版本 SRS 的目录结构可能不同有的源码在仓库根目录有的在 trunk 目录以官方 README 为准。启动成功后默认监听 1935 端口。用 ffmpeg 推流并用 ffplay 拉流验证ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 50 -c:a aac -f flv rtmp://127.0.0.1:1935/live/demoffplay rtmp://127.0.0.1:1935/live/demo如果推流端和拉流端都在本机先把本地回环跑通再去处理跨机器的防火墙和网络问题。SRS 还支持 HTTP-FLV 和 HLS 分发后面根据需求可以打开对应配置。4.3 C 实现 RTMP 转推在 C 中实现推流典型场景是把一个本地视频文件或 RTSP 流转推到 RTMP 服务器。下面代码演示的是“转封装不转码”的推流方式读取文件后直接按原编码格式打包成 FLV 推到 RTMP// 文件路径media-demo/src/push_rtmp.cpp #include iostream extern C { #include libavformat/avformat.h #include libavutil/timestamp.h } int main(int argc, char *argv[]) { const char *src_file input.mp4; const char *rtmp_url rtmp://127.0.0.1:1935/live/demo; if (argc 1) src_file argv[1]; if (argc 2) rtmp_url argv[2]; avformat_network_init(); AVFormatContext *ifmt_ctx nullptr; int ret avformat_open_input(ifmt_ctx, src_file, nullptr, nullptr); if (ret 0) { std::cerr open input failed std::endl; return 1; } ret avformat_find_stream_info(ifmt_ctx, nullptr); if (ret 0) { std::cerr find stream info failed std::endl; return 1; } AVFormatContext *ofmt_ctx nullptr; ret avformat_alloc_output_context2(ofmt_ctx, nullptr, flv, rtmp_url); if (ret 0 || !ofmt_ctx) { std::cerr alloc output context failed std::endl; return 1; } for (unsigned int i 0; i ifmt_ctx-nb_streams; i) { AVStream *in_stream ifmt_ctx-streams[i]; AVStream *out_stream avformat_new_stream(ofmt_ctx, nullptr); ret avcodec_parameters_copy(out_stream-codecpar, in_stream-codecpar); if (ret 0) { return 1; } out_stream-codecpar-codec_tag 0; } ret avio_open(ofmt_ctx-pb, rtmp_url, AVIO_FLAG_WRITE); if (ret 0) { std::cerr avio_open failed std::endl; return 1; } ret avformat_write_header(ofmt_ctx, nullptr); if (ret 0) { std::cerr write header failed std::endl; return 1; } AVPacket *pkt av_packet_alloc(); while (av_read_frame(ifmt_ctx, pkt) 0) { AVStream *in_stream ifmt_ctx-streams[pkt-stream_index]; AVStream *out_stream ofmt_ctx-streams[pkt-stream_index]; pkt-pts av_rescale_q_rnd(pkt-pts, in_stream-time_base, out_stream-time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt-dts av_rescale_q_rnd(pkt-dts, in_stream-time_base, out_stream-time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt-duration av_rescale_q(pkt-duration, in_stream-time_base, out_stream-time_base); pkt-pos -1; ret av_interleaved_write_frame(ofmt_ctx, pkt); av_packet_unref(pkt); if (ret 0) { break; } } av_write_trailer(ofmt_ctx); av_packet_free(pkt); avformat_close_input(ifmt_ctx); if (ofmt_ctx !(ofmt_ctx-oformat-flags AVFMT_NOFILE)) { avio_closep(ofmt_ctx-pb); } avformat_free_context(ofmt_ctx); avformat_network_deinit(); return 0; }这段代码的核心思路是先打开输入文件创建输出上下文然后按输入流的编码参数创建输出流最后逐帧读取并写入输出。生产环境需要补充的错误处理还有断线重连机制。按 DTS 控制发送速率避免推流过快。对音频采样格式变化、流参数动态变化的兼容。长时间无人播放时自动关闭推流。5. RTSP 拉流与多路接入实战5.1 RTSP 协议特点RTSP 是安防和摄像头领域最常用的协议。它本身负责会话控制真正的音视频数据通过 RTP/RTCP 传输。RTSP 支持播放、暂停、seek 等控制指令非常适合监控播放。实际调试 RTSP 时最常遇到的问题有两个第一个是传输模式。RTSP 默认可能走 UDPUDP 在局域网内表现稳定但跨网络、跨防火墙时容易丢包。许多情况下需要显式指定 TCPffmpeg -rtsp_transport tcp -i rtsp://127.0.0.1:554/stream output.mp4第二个是地址安全性。很多摄像头 RTSP 地址包含用户名和密码例如rtsp://user:password192.168.1.13:554/stream。这类地址不要硬编码在代码里也不要直接截图发到公开渠道生产环境应该从配置中心或数据库读取并做访问控制。RTSP 地址用于测试时优先使用本地流媒体服务器生成一条测试流

相关新闻