在 C++ 后端开发慢慢走向“全栈音视频化”的今天,流媒体相关的技术栈已经成了很多团队的高频需求:安防监控平台要拉取摄像头 RTSP 流,直播业务要往 CDN 推 RTMP 流,在线教育要做低延迟互动,短视频平台要批量转码压缩。而这些场景背后,C++ 依然是最核心、最底层的技术语言,配合 FFmpeg 这一套音视频处理库,几乎可以覆盖从采集、编码、封装、传输到解码、渲染的全部环节。

本文将围绕 C++ 音视频流媒体开发这条主线,梳理 FFmpeg、H264/H265、RTMP、RTSP、WebRTC、SRS 这些关键词之间的关系,并给出从环境准备到 C++ API 实战的完整流程。无论你是刚接触音视频方向的后端工程师,还是想转行做流媒体开发的 C++ 学习者,这篇文章都可以作为一条比较完整的学习索引和入门参考。

1. 音视频流媒体开发到底在学什么

1.1 音视频处理的基本链路

很多人第一次接触音视频开发,会被一堆名词砸晕:文件格式、封装格式、编码格式、传输协议、流媒体服务器、推流、拉流、解码渲染……但实际上,音视频开发始终围绕一条核心链路展开:

采集 → 编码 → 封装 → 传输 → 解封装 → 解码 → 渲染

以一个最简单的直播场景为例:

  1. 摄像头采集到原始视频帧。
  2. 视频编码器把原始数据压缩为 H264 或 H265 码流。
  3. 封装层把编码后的码流和音频码流写入 FLV、TS 或 MP4 等容器格式。
  4. 通过 RTMP、RTSP 或 WebRTC 等协议进行网络传输。
  5. 播放端收到数据后,先解封装,再解码,最后渲染到屏幕。

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++ 代码层面排查,效率很低。建议流程是:

  1. 先用 ffmpeg 命令行验证源地址能不能拉通;
  2. 用 ffprobe 确认流的分辨率、编码格式、帧率;
  3. 用命令行确认转封装或转码参数是否可行;
  4. 再把验证过的流程迁移到 C++ API 中;
  5. 最后通过 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++ 音视频流媒体的学习路线,可以参考下面的顺序:

  1. 熟悉 FFmpeg 命令行常用操作,能用 ffmpeg 完成拉流、推流、转码、切片、截图;
  2. 理解 H264/H265 编码基础和 FLV/TS/MP4 封装格式的差异;
  3. 能读懂 RTSP、RTMP、HLS、WebRTC 的基本协议流程;
  4. 使用 FFmpeg C API 写出第一个 RTSP 拉流、RTMP 推流的程序;
  5. 学习 SRS 或 ZLMediaKit 源码,理解服务端流媒体处理流程;
  6. 转向 WebRTC,理解信令、ICE、DTLS-SRTP 等底层机制;
  7. 在嵌入式或服务器上完成一个完整的端到端项目。

在实际项目中,C++ 音视频流媒体开发的核心并不是“背 API”,而是理解数据从摄像头到播放器的完整流向,并能在每个环节出现问题时快速定位。这篇文章从 FFmpeg、H264/H265、RTMP、RTSP、WebRTC、SRS 这些高频技术词入手,梳理了一条比较完整的入门路径,并给出了一个可运行的 C++ 拉流推流转封装示例。如果你正在做监控平台、直播系统或低延迟播放服务,可以先按照文中的步骤把环境跑通,再逐步替换成自己的业务逻辑。调试过程中遇到具体协议或编码问题,优先用 ffprobe 和命令行 ffmpeg 复现问题,往往比直接在代码里排查更快。

Logo

邀请您加入社区

更多推荐