三、RTSP/RTMP 拉流与转发——FFMedia入门系列教程

本节以 FFMedia SDK 2.6.1 为例,使用预编译的 ffmedia CLI,搭建上游流源 → 网络拉流 → 编码码流直通(或转码)→ RTSP/RTMP 服务的完整管线。

本节重点介绍编码码流的直接转发:不解码、不重新编码,延迟和 CPU 占用都较低。只有当上游编码格式与目标输出协议不兼容时,才需要增加 mpp-dec + mpp-enc 转码链路。

准备环境

硬件环境

准备以下硬件:

  • Firefly RK 平台(例如 ROC-RK3588-RT Firefly Ubuntu 22.04)
  • 一台 PC,与开发板处于同一局域网,用于提供上游流源并验证转发结果
  • (可选)IPC 网络摄像头,用于提供真实码流的上游源

上下游之间必须能互相访问对应端口。跨网段或防火墙环境下,需放行 8554/tcp、8554/udp 与 1935/tcp。

软件环境

目标板上解压 SDK 发布包,并确认 CLI 可用:

tar -xzvf ffmedia_release-2.6.1.tar.gz
cd ffmedia_release-2.6.1

./bin/ffmedia modules

确认网络模块的参数定义,参数名与枚举名会随 SDK 版本变化:

./bin/ffmedia params rtsp-client
./bin/ffmedia params rtsp-server
./bin/ffmedia params rtmp-client
./bin/ffmedia params rtmp-server
./bin/ffmedia params mpp-enc

本节管线用到的模块:

模块 类别 作用
rtsp-client 输入(vi) 拉取 RTSP 流,输出压缩视频与可用的音频通道
rtmp-client 输入(vi) 通过 publish=true 拉取 RTMP 流
ffmpeg-demux 输入(vi) 通过 FFmpeg 的协议与 demuxer 读取媒体(HLS、WHEP、SRT 等)
mpp-dec 处理(vp) 使用 Rockchip MPP 将码流解码为原始帧(仅转码时需要)
mpp-enc 处理(vp) 使用 Rockchip MPP 将原始帧重新编码(仅转码时需要)
rtsp-server 输出(vo) 接收编码媒体包,作为 RTSP 服务端向客户端分发
rtmp-server 输出(vo) 接收编码媒体包,作为 RTMP 服务端向客户端分发

上游流源准备

演示环境里的上游源:

方案 说明
MediaMTX + ffmpeg 合成流 PC 上零配置起步,同时提供 RTSP 与 RTMP 两种上游,覆盖本节全部管线
IPC 网络摄像头 真实码流,但通常只提供 RTSP,RTMP 拉流管线仍需 MediaMTX

在 PC 上启动 MediaMTX(单文件免安装,默认监听 RTSP 8554 / RTMP 1935):

mediamtx.exe

默认配置就监听 8554 (RTSP) 和 1935 (RTMP),当然也提供监听 8888 (HLS) 、1935 (WebRTC) 等:

再推入两路测试流。第一路为 H.264 + AAC,用于直通管线:

ffmpeg -re -f lavfi -i testsrc2=size=1280x720:rate=30 \
  -f lavfi -i sine=frequency=1000 \
  -c:v libx264 -profile:v high -g 60 -pix_fmt yuv420p \
  -c:a aac -b:a 128k \
  -f flv rtmp://127.0.0.1:1935/live

拓扑如下:

                                 +-> rtsp-server :8554/live
ffmpeg ---> MediaMTX ------------+-> rtmp-server :1935/live
  rtmp://<PC_IP>:1935/live       +-> hls-server :8888/live/index.m3u8
                                 +-> ...

第二路为 H.265,用于后面的转码管线:

ffmpeg -re -f lavfi -i testsrc2=size=1280x720:rate=30 \
  -c:v libx265 -preset fast -g 60 -pix_fmt yuv420p -tag:v hvc1 \
  -f rtsp rtsp://127.0.0.1:8554/h265

推流成功后,PC 对外提供以下上游地址(<PC_IP> 为 PC 的局域网地址):

上游地址 编码 用于
rtsp://<PC_IP>:8554/live H.264 + AAC RTSP 拉流、分支管线
rtmp://<PC_IP>:1935/live H.264 + AAC RTMP 拉流
rtsp://<PC_IP>:8554/h265 H.265 转码管线

Windows 可以用 ipconfig 查看 <PC_IP>,例如这里为 192.168.31.183:

若改用 IPC 摄像头,先在 Web 后台把视频编码设为 H.264,再按厂商格式拼接 RTSP 地址。

地址与端口

名称 示例 说明
上游 RTSP 地址 rtsp://192.168.1.100:8554/live 被目标板拉取的源地址
上游 RTMP 地址 rtmp://192.168.1.100:1935/live 被目标板拉取的源地址
转发设备地址 172.16.18.63 目标板在局域网中的地址
RTSP 服务端口 8554 避免使用需要高权限的 554
RTMP 服务端口 1935 RTMP 默认端口
服务路径 /live Server 的默认路径

原理

网络模块收发的是编码后的码流,不涉及像素数据。因此"拉流 → 转发"的最短链路里没有解码与编码环节:

直通(默认)                        转码(编码不兼容时才用)
──────────────────────              ──────────────────────────────────
client → server                     client → mpp-dec → mpp-enc → server
不解码、不重编码                     解码到帧,再按目标编码重编
延迟低、CPU 占用低                    延迟增加,占用 MPP 硬件单元
输入编码被完整保留                    可把 H.265 转成 H.264 以适配 RTMP

选择依据只有一条——看上游视频编码:

上游编码 输出 RTSP 输出 RTMP
H.264 直通即可 直通即可
H.265 直通即可(RTSP 兼容性更宽) 必须转码,RTMP 客户端通常不支持 H.265

验证上游可用

先跑最简验证——不涉及开发板,确认上游源本身可用。这一步能把"源的问题"和"板子的问题"提前分开:

ffprobe rtsp://<PC_IP>:8554/live
ffplay  rtsp://<PC_IP>:8554/live

ffprobe 输出中重点看流的编码标识:

Stream #0:0: Video: h264 ...
Stream #0:1: Audio: aac ...

例如:

上游流在 PC 上播放:

如果看到 hevc / h265,后续转发为 RTMP 时要使用本文的转码管线。


拉流并转发为 RTSP 服务

先跑最简转发管线——拉流并原样提供 RTSP 服务。

管线结构

直通管线:

rtsp-client → rtsp-server

完整命令

./bin/ffmedia run \
  -m src=rtsp-client \
  -m relay=rtsp-server \
  -p 'src:source{url=rtsp://<PC_IP>:8554/live}' \
  -p 'relay:endpoint/port=8554' \
  -c src=relay

命令拆解

部分 含义
-m src=rtsp-client 声明拉流模块实例 src
-p 'src:source{url=...}' 指定上游 RTSP 地址,子参数用 ; 分隔
-m relay=rtsp-server 声明 RTSP 服务端模块实例 relay
-p 'relay:endpoint/port=8554' 服务端口设为 8554(endpoint/port 是子参数写法)
-c src=relay 连接 src → relay;省略通道号时自动匹配兼容的视频/音频通道

验证

其他机器通过以下地址拉取目标板转发的流:

ffplay rtsp://<BOARD_IP>:8554/live

或者使用 VLC 客户端拉取开发板提供的 RTSP 流:

FFMedia 打印的信息:

该链路不含解码模块,属于压缩码流直通。如果只需要视频,可以显式连接视频通道:-c src@0=relay。

上游走 TCP 传输

RTSP 客户端默认使用 UDP。跨网段或防火墙环境下如果 UDP 不通,先查看 params rtsp-client 中 stream-type 支持的枚举值,再配置为 TCP:

-p 'src:stream-type=tcp;source{url=rtsp://<PC_IP>:8554/live}'

转发为 RTMP 服务

在上一步的基础上,只把服务端模块换成 rtmp-server。

管线结构

直通管线:

rtsp-client → rtmp-server

完整命令

./bin/ffmedia run \
  -m src=rtsp-client \
  -m relay=rtmp-server \
  -p 'src:source{url=rtsp://<PC_IP>:8554/live}' \
  -p 'relay:endpoint/port=1935' \
  -c src=relay

新增项命令

部分 含义
-m relay=rtmp-server 声明 RTMP 服务端模块实例 relay
-p 'relay:endpoint/port=1935' 服务端口设为 1935(RTMP 默认端口)

拉流侧的 src 实例与连接关系完全不变。

验证

ffplay rtmp://<BOARD_IP>:1935/live

或者使用 VLC 客户端拉取开发板提供的 RTMP 流:

FFMedia 打印的信息:

注意:直通转发要求上游视频是 H.264。如果上游是 H.265,或 RTMP 客户端提示不支持视频 codec,请使用下一小节的转码管线。


上游为 H.265 时转码后转发

当上游编码与目标协议不兼容时,需要在管线中插入解码与编码模块。本例把 H.265 转成 H.264 后再提供 RTMP 服务。

管线结构

转码管线:

rtsp-client → mpp-dec → mpp-enc → rtmp-server

完整命令

./bin/ffmedia run \
  -m src=rtsp-client \
  -m dec=mpp-dec \
  -m enc=mpp-enc \
  -m relay=rtmp-server \
  -p 'src:source{url=rtsp://<PC_IP>:8554/h265}' \
  -p 'enc:encode{codec=5;fps=30;gop=60};output-timeout-ms=-1' \
  -p 'relay:endpoint/port=1935' \
  -c src=dec \
  -c dec=enc \
  -c enc=relay

新增项命令

部分 含义
-m dec=mpp-dec 声明硬件解码模块实例 dec
-m enc=mpp-enc 声明硬件编码模块实例 enc
-p 'enc:encode{codec=5;fps=30;gop=60}' 编码格式设为 5(H.264),帧率 30,GOP 60
-p '...;output-timeout-ms=-1' 关闭输出超时,避免转码链路在无输出时提前退出
-c src=dec 拉流输出接入解码模块
-c dec=enc 解码帧接入编码模块(连接的是帧,不再是码流)
-c enc=relay 编码后的 H.264 码流接入 RTMP 服务端

编码格式枚举对照表

encode/codec 参数的取值对应不同的视频编码格式,常见值如下:

枚举名称 整数值 说明
MEDIA_CODEC_UNKNOWN 0 未知编码格式
MEDIA_CODEC_VIDEO_VCM 1 VCM 视频编码
MEDIA_CODEC_VIDEO_MPEG4 2 MPEG-4 视频编码
MEDIA_CODEC_VIDEO_MPEG1 3 MPEG-1 视频编码
MEDIA_CODEC_VIDEO_MPEG2 4 MPEG-2 视频编码
MEDIA_CODEC_VIDEO_H264 5 H.264 (AVC) 视频编码 ← 本节示例使用
MEDIA_CODEC_VIDEO_H265 6 H.265 (HEVC) 视频编码
MEDIA_CODEC_VIDEO_VP8 7 VP8 视频编码
MEDIA_CODEC_VIDEO_VP9 8 VP9 视频编码
MEDIA_CODEC_VIDEO_AV1 9 AV1 视频编码
MEDIA_CODEC_VIDEO_MJPEG 10 Motion JPEG 视频编码
MEDIA_CODEC_VIDEO_H266 11 H.266 (VVC) 视频编码
MEDIA_CODEC_VIDEO_RAW 12 原始视频数据

具体可以参考 SDK 的 include/ffmedia/base/ff_type.hpp。

注意:该管线只处理视频。如果还需要保留上游音频,需单独规划音频通道的编码与连接,不能假设音频会自动穿过视频转码链路。


从 RTMP 上游拉流

rtmp-client 同时支持拉流和推流,方向由 publish 参数决定:

取值 方向 在管线中的位置
publish=true 拉流 作为根源模块(Producer)
publish=false 推流 作为下游消费者(Consumer)

完整命令

拉流并提供 RTSP 服务:

./bin/ffmedia run \
  -m src=rtmp-client \
  -m relay=rtsp-server \
  -p 'src:source{url=rtmp://<PC_IP>:1935/live;publish=true};timeout/seconds=1' \
  -p 'relay:endpoint/port=8554' \
  -c src=relay

新增项命令

部分 含义
-m src=rtmp-client 声明 RTMP 客户端模块实例 src
-p 'src:source{url=rtmp://...;publish=true};timeout/seconds=1' 指定上游 RTMP 地址,并显式声明拉流方向以及超时时间

只把 relay 换成 rtmp-server(端口 1935),即可得到"RTMP 拉流 → RTMP 服务"的组合,连接关系不变。

注意:拉流模式下必须显式设置 publish=true。漏掉该参数时模块会按推流模式工作,表现为拉不到流。


同一上游同时提供 RTSP 与 RTMP

管线结构

分支管线(同一个拉流源可以分支连接到两个服务端):

                    +-> rtsp-server :8554/live
rtsp-client --------+
                    +-> rtmp-server :1935/live

完整命令

./bin/ffmedia run \
  -m src=rtsp-client \
  -m rtsp=rtsp-server \
  -m rtmp=rtmp-server \
  -p 'src:source{url=rtsp://<PC_IP>:8554/live}' \
  -p 'rtsp:endpoint/port=8554' \
  -p 'rtmp:endpoint/port=1935' \
  -c src=rtsp \
  -c src=rtmp


新增项命令

部分 含义
-m rtsp=rtsp-server 声明 RTSP 服务端实例 rtsp
-m rtmp=rtmp-server 声明 RTMP 服务端实例 rtmp
-c src=rtsp 第一路分支连接(同一 Producer 的第一个 Consumer)
-c src=rtmp 第二路分支连接(同一 Producer 的第二个 Consumer)

src 的实例名始终不变,它通过两条 -c src=... 同时连接了两个服务端——不需要额外写分发逻辑,框架自动完成。两条分支互不影响。

验证

ffplay rtsp://<BOARD_IP>:8554/live
ffplay rtmp://<BOARD_IP>:1935/live

该方式仍然是码流直通。若上游编码可以被 RTSP 客户端播放但不适合 RTMP,只需把 RTMP 分支改为 mpp-dec → mpp-enc(H.264) → rtmp-server,RTSP 分支保持直通。


拉取 FFmpeg 支持的其他输入

rtsp-client 和 rtmp-client 是专用网络模块;需要 HLS、HTTP、SRT、WHEP 等 FFmpeg 支持的输入格式时,改用 ffmpeg-demux:

模块 适用协议
rtsp-client / rtmp-client RTSP、RTMP,专用实现
ffmpeg-demux HLS、HTTP、SRT、WHEP,以及 FFmpeg 支持的其他输入

查看 ffmpeg-demux 支持的参数:

./bin/ffmedia params ffmpeg-demux

基本参数格式为:

-p 'src:input-format=<FORMAT>;source{uri=<INPUT_URI>;loop=0}'
部分 含义
input-format FFmpeg 的输入格式短名称;不确定时可省略,让 FFmpeg 自动探测
source/uri 输入地址;使用 uri,不要写成专用客户端的 url
source/loop=0 网络流不循环,一直运行到断流或收到停止信号

以 HLS 输入为例(把地址换成一个真实可用的 .m3u8):

./bin/ffmedia run \
  -m src=ffmpeg-demux \
  -m relay=rtsp-server \
  -p 'src:input-format=hls;source{uri=http://<PC_IP>:8888/live/index.m3u8;loop=0}' \
  -p 'relay:endpoint/port=8554' \
  -c src@0=relay

这里使用 src@0 只转发视频通道,避免上游的 Opus 等音频编码与 RTSP Server 的输入能力不匹配。

RTSP 的 TCP 选项则通过 ffmpeg/options 传递:

-p 'src:input-format=rtsp;source{uri=rtsp://<PC_IP>:8554/live;loop=0};ffmpeg{options=rtsp_transport=tcp}'

IPC 网络摄像头

IPC 网络摄像头通常是一个“采集 + 编码 + RTSP 服务”一体的小流媒体设备,它直接替代了“PC + MediaMTX + ffmpeg 推流”这一整段。

主要原理如下:

  • 采集端:sensor 出图 → ISP 处理(3A、降噪)→ VENC 硬件编码成 H.264/H.265
  • 服务端:固件里跑着一个内置 RTSP Server,监听 554 等某个端口
  • 它对外是一个标准的 RTSP 源——FFMedia 的 rtsp-client 拉它,和拉 MediaMTX 的流没有任何区别

拓扑如下:

IPC 摄像头(对外就是一个 RTSP 源)
  图像传感器 + ISP --> 硬件编码 H.264 / H.265 --> 内置 RTSP Server :554
        |
        |  rtsp://admin:<Password>@<IPC_IP>:554/Streaming/Channels/101
        v
RK3588 板
  rtsp-client(拉流,不解码)--> rtsp-server :8554 / rtmp-server :1935
        |
        |  rtsp://<BOARD_IP>:8554/live 或 rtmp://<BOARD_IP>:1935/live
        v
PC 客户端
  ffplay / VLC / ...

例如对网络摄像头拉流后转发为 rtmp-server ,编码格式设为 5(H.264),帧率 30,GOP 60:

./bin/ffmedia run \
  -m src=rtsp-client \
  -m dec=mpp-dec \
  -m enc=mpp-enc \
  -m relay=rtmp-server \
  -p 'src:source{url=rtsp://admin:<Password>@<IPC_IP>:554/Streaming/Channels/101}' \
  -p 'enc:encode{codec=5;fps=30;gop=60};output-timeout-ms=-1' \
  -p 'relay:endpoint/port=1935' \
  -c src=dec \
  -c dec=enc \
  -c enc=relay

其中这里的 rtsp://admin:<Password>@<IPC_IP>:554/Streaming/Channels/101} 要替换成实际网络摄像头的 RTSP URL。

小结

本节把 CLI 三要素用到网络管线上,每一步只增加一个模块或一条连接:

  1. 第一步(上游验证):用 MediaMTX + ffmpeg 合成流充当上游源,先在 PC 上用 ffprobe / ffplay 确认可用,把问题边界划在开发板之外;
  2. 第二步(拉流 + RTSP 服务):用 rtsp-client 拉流,rtsp-server 分发,完成最简直通管线;
  3. 第三步(换 RTMP 服务端):只替换 relay 的模块与端口(1935),拉流侧完全不动;
  4. 第四步(插入转码):上游为 H.265 时,在 src 与 relay 之间加入 mpp-dec 与 mpp-enc(codec=5 转成 H.264);
  5. 第五步(换拉流端):用 rtmp-client + publish=true 替换 rtsp-client,显式声明拉流方向;
  6. 第六步(分支管线):一个 rtsp-client 通过两条 -c src=... 同时连接 rtsp-server 与 rtmp-server;
  7. 第七步(换通用拉流模块):需要 HLS、SRT 等 FFmpeg 支持的输入时,用 ffmpeg-demux 替换专用客户端;
  8. 第八步(换真实上游源):把上游地址换成 IPC 摄像头的 RTSP URL。