C++音视频流媒体开发入门:FFmpeg与RTMP/RTSP/WebRTC实战指南
在 C++ 后端开发慢慢走向“全栈音视频化”的今天,流媒体相关的技术栈已经成了很多团队的高频需求:安防监控平台要拉取摄像头 RTSP 流,直播业务要往 CDN 推 RTMP 流,在线教育要做低延迟互动,短视频平台要批量转码压缩。而这些场景背后,C++ 依然是最核心、最底层的技术语言,配合 FFmpeg 这一套音视频处理库,几乎可以覆盖从采集、编码、封装、传输到解码、渲染的全部环节。
本文将围绕 C++ 音视频流媒体开发这条主线,梳理 FFmpeg、H264/H265、RTMP、RTSP、WebRTC、SRS 这些关键词之间的关系,并给出从环境准备到 C++ API 实战的完整流程。无论你是刚接触音视频方向的后端工程师,还是想转行做流媒体开发的 C++ 学习者,这篇文章都可以作为一条比较完整的学习索引和入门参考。
1. 音视频流媒体开发到底在学什么
1.1 音视频处理的基本链路
很多人第一次接触音视频开发,会被一堆名词砸晕:文件格式、封装格式、编码格式、传输协议、流媒体服务器、推流、拉流、解码渲染……但实际上,音视频开发始终围绕一条核心链路展开:
采集 → 编码 → 封装 → 传输 → 解封装 → 解码 → 渲染
以一个最简单的直播场景为例:
- 摄像头采集到原始视频帧。
- 视频编码器把原始数据压缩为 H264 或 H265 码流。
- 封装层把编码后的码流和音频码流写入 FLV、TS 或 MP4 等容器格式。
- 通过 RTMP、RTSP 或 WebRTC 等协议进行网络传输。
- 播放端收到数据后,先解封装,再解码,最后渲染到屏幕。
C++ 开发者在这个链路里,既可以调用 FFmpeg 的库函数处理音视频数据,也可以基于已有的流媒体服务器(如 SRS、ZLMediaKit)做二次开发,还可以自研协议解析和播放器内核。
1.2 为什么首选 C++ 和 FFmpeg
Python 虽然音视频库也不少,但在高并发、低延迟、内存可控的流媒体服务场景下,C++ 仍然是绝对主力。FFmpeg 则是目前跨平台音视频处理的事实标准库,它集成了:
- 超过数百种音视频编解码器;
- 丰富的封装/解封装格式支持;
- 强大的滤镜系统;
-
完整的命令行工具
ffmpeg; - 可供 C/C++ 程序直接调用的 libavcodec、libavformat、libavfilter 等 SDK。
简单来说,FFmpeg 几乎做到了“一库通吃”。学习 C++ 音视频流媒体开发,很大一部分工作就是在学习如何正确使用 FFmpeg 这套库,以及如何在它之上搭建传输与服务能力。
1.3 常见应用场景
音视频流媒体技术覆盖的典型场景包括:
- 安防监控:对接海康、大华等摄像头 RTSP 流,转码、截图、上云;
- 直播推流与拉流:主播端通过 RTMP 推流到服务器,观众端通过 HLS、WebRTC 或 FLV 拉流观看;
- 视频会议:使用 WebRTC 完成低延迟音视频通信;
- 视频点播:对上传的原始视频做 H264/H265 转码,并切片为 HLS 输出;
- 智能硬件:RK3588、海思等嵌入式平台上的硬编硬解和 RTSP 处理。
2. 视频编码基础:H264 与 H265 的区别和选择
2.1 H264 为什么是主流
H264,也叫 AVC(Advanced Video Coding),是目前兼容性最好、应用最广泛的视频编码标准。无论是摄像头输出、短视频上传、还是浏览器播放,H264 都能获得比较完善的软硬件支持。
H264 的核心优势包括:
- 编解码器实现成熟,CPU 编解码和硬编硬解都有大量现成方案;
- 同等画质下,码率远低于 MPEG-2 等老标准;
- 所有主流播放器和浏览器都支持。
2.2 H265 解决了什么问题
H265,也叫 HEVC(High Efficiency Video Coding),可以理解为 H264 的继任者。它的主要目标是:在同样画质下进一步降低码率。根据大量实际测试,H265 通常比 H264 节省大约 30% 到 50% 的码率,也就是说,原来需要 2Mbps 码率才能看清楚的画面,H265 用 1Mbps 左右就可能达到接近的效果。
这对带宽敏感场景很有吸引力,比如:
- 4K/8K 视频存储与分发;
- 网络监控摄像头长期存储;
- 移动网络下的视频传输。
2.3 H264 与 H265 码率的实际对比
下面是一个经验性的参考(具体数值受分辨率、帧率、画面复杂度影响很大):
| 视频规格 | H264 建议码率 | H265 建议码率 | 大致节省 |
|---|---|---|---|
| 1080p 30fps 直播 | 3-5 Mbps | 1.5-3 Mbps | 约 40% |
| 4K 30fps 点播 | 15-20 Mbps | 8-12 Mbps | 约 35% |
| 720p 安防监控 | 2-4 Mbps | 1-2 Mbps | 约 40% |
注意,上表只是经验值。实际选型时要结合视频内容动态程度和设备解码能力综合考虑。安防场景经常遇到的情况是,现场有一批老摄像头只支持 H264,而后端存储平台希望统一转成 H265 来节省磁盘空间,这个时候就需要 FFmpeg 做实时转码。
2.4 开发时怎么选编码标准
在实际项目中,我们通常遵循这样的选择逻辑:
- 面向浏览器播放,优先 H264,因为浏览器对 H265 支持还不稳定;
- 面向高压缩比存储,如果播放端支持,优先 H265;
- 嵌入式平台上优先使用硬件编码器,例如 RK3588 的 MPP 硬编,避免 CPU 软编压力过大;
- 兼容性要求极高时,H264 Baseline 或 Main Profile 是更稳妥的选择。
3. 传输协议体系:RTMP、RTSP、WebRTC 与 SRS
很多初学者会把“视频编码格式”和“传输协议”混在一起,其实它们是两台事。编码格式决定数据如何压缩,传输协议决定压缩后的数据如何分包、如何传输、如何保证实时性。
3.1 RTMP 协议
RTMP(Real-Time Messaging Protocol)最初由 Adobe 推出,基于 TCP,是一种典型的直播推流协议。
特点:
- 推流成熟:几乎所有直播软件和编码器都支持 RTMP 推流;
- 延迟一般:属于“服务端缓冲型”协议,端到端延迟通常在 1 到 3 秒左右,如果服务器配置不合理或网络波动,可能到 5 秒以上;
- 浏览器原生不支持:老一代网页播放需要 Flash,Flash 停用后,浏览器播放 RTMP 流的方案已经基本退出历史舞台;
- 实际传输的数据通常是 FLV 封装格式。
当前场景中,RTMP 更多用于“推流”这一侧,也就是主播端把流推到服务器,而播放端则通过服务器转出的 HLS、HTTP-FLV 或 WebRTC 流来观看。
3.2 RTSP 协议
RTSP(Real Time Streaming Protocol)是一种应用层实时流协议,通常和 RTP/RTCP 配合使用。它最典型的应用场景就是安防摄像头。
特点:
- 支持拉流:客户端主动向摄像头或流媒体服务器请求视频流;
- 支持控制:可以发送播放、暂停、停止、快进等控制指令;
- 传输较复杂:RTSP 通常同时使用 RTSP(控制)和 RTP(数据)两套通道;
- 延迟较低:局域网内 RTSP 延迟可以做到几百毫秒;
- 浏览器不能直接播放:网页端通常需要经过服务器转码或转封装后再播放。
常见的摄像头 RTSP 地址格式如下:
rtsp://用户名:密码@IP地址:端口/streaming/channels/101
注意,在实际项目中,不要把摄像头地址硬编码到代码里,更不要用真实摄像头地址做公开演示。在测试环境里,可以使用本地推流生成的 RTSP 源,或者使用厂商提供的模拟地址。
3.3 WebRTC 协议
WebRTC(Web Real-Time Communication)是一个开源实时通信技术集,主打低延迟点对点通信。
特点:
- 基于 UDP:配合 SRTP、NACK、FEC 等机制,实现低延迟和抗丢包;
- 浏览器原生支持:Chrome、Firefox 等浏览器无需安装插件即可通信;
- 延迟极低:端到端延迟可以做到几百毫秒以内;
- 架构复杂:涉及 STUN/TURN、ICE、SDP、DTLS 等一系列复杂机制。
WebRTC 已经成了视频会议、在线课堂、主播连麦等场景的首选方案。在服务器侧,SRS、ZLMediaKit 等流媒体服务器都开始支持将 RTMP/RTSP 流转为 WebRTC 流,从而让浏览器直接低延迟观看。
3.4 SRS 流媒体服务器
SRS(Simple Realtime Server)是一个开源的流媒体服务器,使用 C++ 开发,支持 RTMP、HLS、HTTP-FLV、WebRTC、SRT 等多种协议。
SRS 的作用类似一个协议中转站:接收各种推流协议,再根据播放端需求输出不同协议。
一个典型的架构是:
摄像头 RTSP 流 → FFmpeg 拉流转推为 RTMP → SRS 服务器 → WebRTC/HLS/HTTP-FLV 分发给浏览器
也有另一种常见架构:
摄像头 RTSP 流 → ZLMediaKit 直接拉流 → 浏览器 WebRTC 播放
这两条路径在工程上都可行,区别在于对协议转换能力的取舍。如果团队已经熟悉 FFmpeg 命令行操作,前一种架构更灵活;如果希望服务端集成度更高,减少额外转推进程,后一种方案更省心。
3.5 技术选型对比
| 协议 | 传输层 | 延迟 | 浏览器支持 | 主要用途 |
|---|---|---|---|---|
| RTMP | TCP | 1-3秒 | 不支持 | 推流到服务器 |
| RTSP | TCP/UDP | 数百毫秒 | 不支持 | 安防监控拉流 |
| HLS | HTTP/TCP | 3-10秒 | 原生支持 | 点播、延迟不敏感的直播 |
| WebRTC | UDP | 几百毫秒 | 原生支持 | 实时会议、低延迟直播 |
| HTTP-FLV | HTTP/TCP | 1-3秒 | 需引入 flv.js | 低延迟直播播放 |
4. 环境准备与 FFmpeg 编译安装
4.1 操作系统与工具链
本文的示例以 Linux(CentOS 7 或 Ubuntu 20.04/22.04)为主,因为流媒体服务更多部署在 Linux 上。Windows 环境也可以参考,思路完全一致,只是安装命令不同。
需要准备的基础工具链:
- gcc / g++
- make
- cmake
- pkg-config
- git
以 CentOS 7 为例,安装基础工具:
yum install -y gcc gcc-c++ make cmake pkgconfig git
以 Ubuntu 20.04 为例:
apt update
apt install -y build-essential cmake pkg-config git
4.2 编译安装 FFmpeg
虽然很多系统自带 FFmpeg,但自带版本往往偏旧,且缺少某些扩展库。建议在开发时自行编译一套完整版 FFmpeg。
以当前常用的稳定发布版本为例,编译步骤大致如下:
# 下载并解压 FFmpeg 源码,请从官方地址获取 release 版本
wget https://ffmpeg.org/releases/ffmpeg-6.1.tar.xz
tar xf ffmpeg-6.1.tar.xz
cd ffmpeg-6.1
# 配置编译选项
./configure --enable-shared --disable-static --enable-gpl --enable-libx264 --enable-libx265
# 编译安装
make -j$(nproc)
make install
注意:
--enable-libx264
和
--enable-libx265
需要提前安装 x264 和 x265 开发库,否则 configure 会报错。更完整的方案是使用 x265、x264 和 FFmpeg 一起编译,这一套在流媒体开发中比较常见。
如果没有安装 x264,可以这样处理:
# Ubuntu
apt install -y libx264-dev libx265-dev nasm
# CentOS
yum install -y epel-release
yum install -y x264-devel x265-devel nasm
编译完成后验证:
ffmpeg -version
ffprobe -version
如果能看到版本号和配置信息,说明安装成功。
如果你想省略编译过程,也可以使用官方提供的 Windows 静态编译版,解压后配置环境变量即可使用。但有一个限制:官方 static 版大多不带 libx264/x265 静态库,适合命令行使用,不太适合做 C++ 二次开发编译。因此,做 C++ 开发时通常还是需要自己编译一套 SDK。
4.3 Windows 开发注意事项
如果你是 Windows 上做 C++ 音视频开发,通常会使用
Visual Studio + MSYS2 + FFmpeg
的组合。MSYS2 中可以一键安装 FFmpeg 开发库:
pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-toolchain
安装后需要把 FFmpeg 的 include 目录和 lib 目录配置到 Visual Studio 工程中,并注意运行时动态库的 DLL 路径。
4.4 示例项目结构
为了方便后面实战,建议创建如下目录结构:
stream-demo/
├── CMakeLists.txt
├── main.cpp
├── third_party/
│ ├── include/
│ └── lib/
└── output/
其中
third_party
用来存放 FFmpeg 头文件和库文件,
output
用来存放生成的可执行文件和测试输出。
5. FFmpeg 命令行核心用法
在写 C++ 代码之前,建议先把 FFmpeg 命令行用熟练,因为很多问题都可以先用命令行验证思路,再回到 C++ 中实现。
5.1 查看媒体信息:ffprobe
ffprobe -show_streams -show_format input.mp4
这条命令会输出文件的音频流信息、视频流信息和封装格式信息。排查“为什么这个视频没有声音”或者“这个 RTSP 流分辨率是多少”时非常有用。
5.2 转码与转封装
# 将 m4s 文件转成 mp4,不重新编码,只拷贝数据流
ffmpeg -i 1.m4s -c copy 1.mp4
-c copy
表示 stream copy,即不重新编码,直接拷贝。这种方式的优点是速度快,缺点是如果源文件封装格式和目标格式存在不兼容(例如某些 ts 流、m4s 流的问题),会导致处理失败。
# 重新编码并限制分辨率
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -c:a aac output.mp4
5.3 RTSP 拉流与 RTMP 推流
这是监控场景最常用的命令之一:
ffmpeg -rtsp_transport tcp -i "rtsp://your_rtsp_address" -c copy -f flv "rtmp://your_server/live/stream"
这里的
-rtsp_transport tcp
表示使用 TCP 传输 RTSP 数据,通常比默认的 UDP 模式更稳定,适合跨网段拉流。
如果想做转码:
ffmpeg -rtsp_transport tcp -i "rtsp://your_rtsp_address" \
-c:v libx264 -preset veryfast -b:v 2M \
-c:a aac -b:a 128k \
-f flv "rtmp://your_server/live/stream"
5.4 截图与切片
从视频流中截取一张图片:
ffmpeg -i input.mp4 -ss 00:00:10 -frames:v 1 output.jpg
生成 HLS 点播切片:
ffmpeg -i input.mp4 -c:v libx264 -hls_time 4 -hls_list_size 0 output.m3u8
5.5 FFmpeg 的 -y 参数是什么意思
-y
表示覆盖输出文件而不询问。默认情况下,如果输出文件已经存在,FFmpeg 会交互式询问是否覆盖。在脚本和自动化任务中,如果不加
-y
,进程可能会卡在等待输入的状态。所以服务端程序中通常都会加上
-y
:
ffmpeg -y -i input.mp4 output.mp4
5.6 合并多个 TS 文件
点播场景中经常遇到从 HLS 下载下来的多个
.ts
分片,合并成一个文件:
# 先创建 list.txt,内容按顺序写入文件名
# file 'file1.ts'
# file 'file2.ts'
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
6. C++ 实战:基于 FFmpeg API 实现 RTSP 拉流与 RTMP 推流
在熟悉命令行之后,我们来写一个简化版 C++ 程序,它完成以下功能:
- 从 RTSP 地址拉取视频流;
- 将视频流转换成 FLV 格式;
- 推流到 RTMP 服务器。
这个案例是一个很典型的 C++ 流媒体开发入门骨架。
6.1 编写 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(stream_demo)
set(CMAKE_CXX_STANDARD 11)
# FFmpeg 头文件路径,根据你的实际安装位置修改
include_directories(/usr/local/include)
# FFmpeg 库文件路径
link_directories(/usr/local/lib)
# 可执行文件名
add_executable(stream_demo main.cpp)
# 链接 FFmpeg 库
target_link_libraries(stream_demo
avformat
avcodec
avutil
swscale
)
6.2 编写 main.cpp
下面这个程序是核心函数节的完整示例,思路可以参考,但实际运行时需要根据你的 FFmpeg 版本、头文件路径和 RTMP 服务器地址做调整:
// 文件路径:main.cpp
#include <iostream>
#include <cstdlib>
extern "C" {
#include <libavformat/avformat.h>
}
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: stream_demo <input_rtsp> <output_rtmp>" << std::endl;
return -1;
}
const char* inputUrl = argv[1];
const char* outputUrl = argv[2];
// 1. 初始化 FFmpeg 网络模块
avformat_network_init();
AVFormatContext* ifmtCtx = nullptr;
int ret = avformat_open_input(&ifmtCtx, inputUrl, nullptr, nullptr);
if (ret < 0) {
char errBuf[256] = {0};
av_strerror(ret, errBuf, sizeof(errBuf));
std::cerr << "open input failed: " << errBuf << std::endl;
return -1;
}
ret = avformat_find_stream_info(ifmtCtx, nullptr);
if (ret < 0) {
std::cerr << "find stream info failed" << std::endl;
avformat_close_input(&ifmtCtx);
return -1;
}
// 2. 打开输出上下文
AVFormatContext* ofmtCtx = nullptr;
ret = avformat_alloc_output_context2(&ofmtCtx, nullptr, "flv", outputUrl);
if (ret < 0) {
std::cerr << "alloc output context failed" << std::endl;
avformat_close_input(&ifmtCtx);
return -1;
}
// 3. 为输出上下文创建流
for (unsigned int i = 0; i < ifmtCtx->nb_streams; ++i) {
AVStream* inStream = ifmtCtx->streams[i];
AVStream* outStream = avformat_new_stream(ofmtCtx, inStream->codecpar->codec_id);
if (!outStream) {
std::cerr << "create new stream failed" << std::endl;
return -1;
}
ret = avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);
if (ret < 0) {
std::cerr << "copy codec parameters failed" << std::endl;
return -1;
}
outStream->codecpar->codec_tag = 0;
}
// 4. 打开输出 URL
if (!(ofmtCtx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmtCtx->pb, outputUrl, AVIO_FLAG_WRITE);
if (ret < 0) {
std::cerr << "open output url failed" << std::endl;
return -1;
}
}
ret = avformat_write_header(ofmtCtx, nullptr);
if (ret < 0) {
std::cerr << "write header failed" << std::endl;
return -1;
}
// 5. 循环读取、转封装、推流
AVPacket packet;
av_init_packet(&packet);
packet.data = nullptr;
packet.size = 0;
int64_t frameCount = 0;
while (true) {
ret = av_read_frame(ifmtCtx, &packet);
if (ret < 0) {
break;
}
// 根据输入流的索引找到输出流
AVStream* inStream = ifmtCtx->streams[packet.stream_index];
AVStream* outStream = ofmtCtx->streams[packet.stream_index];
// 修正时间戳
packet.pts = av_rescale_q_rnd(packet.pts, inStream->time_base, outStream->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.dts = av_rescale_q_rnd(packet.dts, inStream->time_base, outStream->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.duration = av_rescale_q(packet.duration, inStream->time_base, outStream->time_base);
packet.pts = packet.pts;
packet.dts = packet.dts;
ret = av_interleaved_write_frame(ofmtCtx, &packet);
av_packet_unref(&packet);
if (ret < 0) {
std::cerr << "write frame failed" << std::endl;
break;
}
frameCount++;
if (frameCount % 100 == 0) {
std::cout << "pushed frames: " << frameCount << std::endl;
}
}
// 6. 写文件尾并清理
av_write_trailer(ofmtCtx);
if (ofmtCtx && !(ofmtCtx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&ofmtCtx->pb);
}
avformat_close_input(&ifmtCtx);
avformat_free_context(ofmtCtx);
avformat_network_deinit();
std::cout << "push finished, total frames: " << frameCount << std::endl;
return 0;
}
6.3 代码关键点解释
-
avformat_open_input负责打开 RTSP 流,并创建输入上下文;这里的ifmtCtx保存了拉流的所有状态; -
avformat_alloc_output_context2创建输出上下文,这里我们指定封装格式为flv,这样可以推给 RTMP 服务器; -
avcodec_parameters_copy把输入流的编码参数“拷贝”给输出流,这样可以实现不转码的流拷贝,CPU 负担小很多; -
时间戳修正非常重要,RTSP 源和 RTMP 目的流的时间基很可能不同,直接用
av_rescale_q_rnd做对齐; -
av_interleaved_write_frame按时间戳排序写入帧,避免因包顺序错乱导致播放端花屏或卡顿。
6.4 编译与运行
在项目根目录执行:
mkdir build && cd build
cmake ..
make
运行示例(需要准备一个 RTMP 推流服务器,建议先在本地用 SRS 或 nginx-rtmp 搭建测试环境):
./stream_demo "rtsp://your_test_source" "rtmp://127.0.0.1:1935/live/test"
这里的 RTSP 测试源可以使用本地 FFmpeg 命令创建一个:
ffmpeg -re -i test.mp4 -c copy -f rtsp rtsp://127.0.0.1:8554/live/test
使用 RTSP 服务器时,需要先启动一个支持 RTSP 的服务端,例如
mediamtx
或
ZLMediaKit
。
6.5 如果是转码而不是转封装怎么做
上面的代码只做了“转封装”,也就是直接把 H264 数据从 RTSP 容器搬到 FLV 容器,不做重新编码。如果希望把 RTSP 拉到的 H264 流转成 H265 或降低分辨率,就需要在 C++ 中引入解码器、滤镜、编码器这一套完整管线,代码复杂度会增加不少。
工程上为了提高开发效率,通常不是每次都用 C++ 直接写完整转码管线,而是先借助 FFmpeg 命令行验证可行性,再通过 FFmpeg C API 封装到自己的服务中。
6.6 验证推流是否成功
推流过程中,可以用以下命令检查 RTMP 地址是否能正常拉流:
ffplay -fflags nobuffer -f flv "rtmp://127.0.0.1:1935/live/test"
如果能正常看到画面,说明推流链路是通的。
7. 常见问题与排查思路
7.1 ffmpeg: error while loading shared libraries
现象:
运行
ffmpeg
或自己编译的 C++ 程序时,提示找不到动态库。
原因: 动态库路径没有配置到系统搜索路径。
解决:
# 查看本地动态库路径
ldconfig -p | grep libavformat
# 临时设置环境变量
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# 或者写入系统配置
echo "/usr/local/lib" > /etc/ld.so.conf.d/ffmpeg.conf
ldconfig
7.2 RTSP 拉流经常超时或花屏
现象: 从摄像头拉流时,画面频繁卡顿、马赛克,或者程序报错退出。
原因: 默认使用 UDP 传输,跨网络环境丢包严重;部分摄像头设备在 UDP 模式下性能不佳。
解决: 在代码中显式设置 RTSP 传输方式为 TCP:
AVDictionary* options = nullptr;
av_dict_set(&options, "rtsp_transport", "tcp", 0);
ret = avformat_open_input(&ifmtCtx, inputUrl, nullptr, &options);
使用命令行时,对应参数就是
-rtsp_transport tcp
。
7.3 FFmpeg 的 -y 参数没有生效
现象:
明明加了
-y
,程序还是卡在等待输入状态。
原因:
有些情况是命令行参数顺序有误。
-y
应该放在输入文件之前或紧跟全局选项位置。
正确示例:
ffmpeg -y -i input.mp4 output.mp4
7.4 HLS 直播延迟越来越大
现象: 播放 HLS 流,延迟从 5 秒慢慢增大到 15 秒。
原因: 播放端和服务器端对切片列表的缓存策略不一致,切片数量累积导致延迟增长。
解决:
在服务端控制分片数量和列表长度,并配置
hls_list_size
、
hls_delete_threshold
等参数。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| RTSP 拉流卡顿 | 网络丢包或 UDP 不稳定 | 使用 TCP 传输 |
| RTMP 推流鉴权失败 | 服务器要求合法推流密钥 | 连接合法的本地服务器或按服务端规范添加密钥 |
| 视频没有声音 | 转码时丢弃了音频流 | 检查输入流信息,为音频单独编码并封装 |
| H264 转 H265 播放花屏 | 转码参数或解码器兼容问题 | 优先使用 FFmpeg 默认算法,确保播放端支持 H265 |
| 浏览器播放不了 RTSP | 浏览器不支持 RTSP 协议 | 服务端做 RTSP→WebRTC/HTTP-FLV/HLS 转换 |
| FFmpeg 编译缺头文件 | 代码包含路径不对 | 使用 pkg-config 定位 include 与 lib 路径 |
7.5 浏览器如何播放 RTSP 流
浏览器不能直接播放
rtsp://
地址。常用方案是:
- 方案一:用 SRS、ZLMediaKit、MediaMTX 把 RTSP 流转成 WebRTC 流,再通过浏览器原生 WebRTC API 播放;
- 方案二:拉取 RTSP 流后转成 HLS,播放 HLS;
- 方案三:拉取 RTSP 流后转成 HTTP-FLV,在前端使用 flv.js 播放。
以 MediaMTX 为例,它的配置思路是接收 RTSP 推流,同时输出 WebRTC、HLS 等协议,让浏览器直接拉流播放。这种方式在安防场景中越来越受欢迎,因为它省去了自己写转推脚本的成本。
8. 最佳实践与开发建议
8.1 优先用命令行验证,再写 C++ 代码
音视频链路涉及很多环节,一个报错可能来自协议、编码格式、封装格式、网络环境等多个方面。如果直接跳到 C++ 代码层面排查,效率很低。建议流程是:
-
先用
ffmpeg命令行验证源地址能不能拉通; -
用
ffprobe确认流的分辨率、编码格式、帧率; - 用命令行确认转封装或转码参数是否可行;
- 再把验证过的流程迁移到 C++ API 中;
-
最后通过
ffplay或播放器验证输出。
8.2 注意内存管理和线程安全
FFmpeg 的 C API 并不是完全线程安全的。常见规范有:
-
同一个
AVFormatContext不要在多线程中同时读写; -
解码器上下文
AVCodecContext每个线程应该独立; -
AVPacket和AVFrame必须正确使用av_packet_unref和av_frame_unref释放引用; - 长时间运行的推流程序,要关注内存增长,建议定期打印进程 RSS 信息。
8.3 时间基与时间戳是流媒体的灵魂
帧率、时间戳、时间基这三者决定了音视频同步是否正常。不同协议源的时间基可能完全不同,涉及转封装、转码时必须做
av_rescale_q
转换,否则会出现画面快进、慢放、音画不同步等问题。
8.4 尽量使用硬编硬解
在嵌入式平台和低功耗服务器上,软编软解非常消耗 CPU。以 RK3588 为例,官方提供 MPP 硬件编解码库,FFmpeg 也集成了对应的硬件加速接口。开发时优先考虑:
-
拉流后不解码,能用
-c copy就不转码; - 必须转码时,优先使用硬件编码器;
- 在性能测试时,同时观测 CPU、内存、带宽三个指标。
8.5 日志与监控
流媒体服务通常是 7x24 小时运行的程序,必须有完备的日志系统。建议至少记录:
- 拉流地址和推流地址;
- 连接建立与断开时间;
- 拉流帧率和推流帧率;
- 重连次数和错误码;
- 内存和 CPU 使用率。
8.6 安全合规与服务器变更
在开发和生产环境中,涉及摄像头地址、推流服务器、鉴权信息时,务必遵守以下原则:
- 不要使用真实摄像头公网地址写入代码和公开文档;
- 测试环境使用本地推流或构造的 RTSP 源;
- 涉及生产服务器配置变更前,先备份原配置并在测试环境验证;
- 推流鉴权尽量使用最小权限原则,避免 token 泄露。
8.7 从命令到产品化的演进路线
如果你正在规划一条 C++ 音视频流媒体的学习路线,可以参考下面的顺序:
-
熟悉 FFmpeg 命令行常用操作,能用
ffmpeg完成拉流、推流、转码、切片、截图; - 理解 H264/H265 编码基础和 FLV/TS/MP4 封装格式的差异;
- 能读懂 RTSP、RTMP、HLS、WebRTC 的基本协议流程;
- 使用 FFmpeg C API 写出第一个 RTSP 拉流、RTMP 推流的程序;
- 学习 SRS 或 ZLMediaKit 源码,理解服务端流媒体处理流程;
- 转向 WebRTC,理解信令、ICE、DTLS-SRTP 等底层机制;
- 在嵌入式或服务器上完成一个完整的端到端项目。
在实际项目中,C++ 音视频流媒体开发的核心并不是“背 API”,而是理解数据从摄像头到播放器的完整流向,并能在每个环节出现问题时快速定位。这篇文章从 FFmpeg、H264/H265、RTMP、RTSP、WebRTC、SRS 这些高频技术词入手,梳理了一条比较完整的入门路径,并给出了一个可运行的 C++ 拉流推流转封装示例。如果你正在做监控平台、直播系统或低延迟播放服务,可以先按照文中的步骤把环境跑通,再逐步替换成自己的业务逻辑。调试过程中遇到具体协议或编码问题,优先用
ffprobe
和命令行
ffmpeg
复现问题,往往比直接在代码里排查更快。
更多推荐

所有评论(0)