RK 音视频开发:从硬件到软件的完整链路

本文以 Rockchip(RK)平台为背景,讲清音视频数据从真实世界进入芯片、被处理和编码、最终通过网络播放的完整过程。

贯穿示例:CSI 摄像头与麦克风接入 RK 开发板,向 VLC 等客户端提供带声音的 RTSP 实时流。

不同 RK SoC(如 RK356x、RK3588、RV1106)与 SDK 的具体设备名、结构体字段、API 名称可能略有差异;但媒体链路和模块职责一致。


1. 核心认知:一条实时数据流水线

真实世界
  │
  ├─ 摄像头光信号 ─→ Sensor ─→ MIPI-CSI ─→ VICAP ─→ ISP ─→ NV12/YUV
  │                                                        │
  │                                                        ├─ RGA:缩放/旋转/叠字
  │                                                        └─ VENC:H.264/H.265 编码
  │                                                               │
  │                                                               └─ RTSP / MP4 / WebRTC / 屏幕显示
  │
  └─ 麦克风声信号 ─→ Audio Codec ─→ I2S ─→ ALSA ─→ AI ─→ AENC
                                                               │
                                                               └─ AAC / G.711 / Opus → 网络或文件

音视频开发不是简单地“读取摄像头、发送网络包”,而是搭建一条尽量由硬件完成、尽量少经过 CPU 拷贝的高吞吐流水线。


2. RK 芯片内的主要硬件模块

硬件块作用常见软件接口
CSI / DPHY接收 MIPI 摄像头高速数据Device Tree、V4L2
VICAP将摄像头帧采集进内存缓冲区/dev/video*、VI
ISP去噪、曝光、白平衡、HDR、颜色处理rkaiq、ISP 参数
RGA缩放、裁剪、旋转、颜色转换、OSDRGA API
VENCH.264/H.265/MJPEG 硬件编码MPP、RKMedia VENC
VDECH.264/H.265 等硬件解码MPP、RKMedia VDEC
I2S音频数字总线Device Tree、ALSA
Audio CodecADC/DAC,模拟与数字音频转换ALSA mixer
VO / DRM把图像送到 HDMI、MIPI 屏等显示设备DRM/KMS、VO

几个容易混淆的事实:

  • 摄像头通常先输出 RAW Bayer 原始像素,并非 H.264 视频。
  • ISP 将 RAW 转换为可供处理、显示或编码的 YUV/NV12 图像。
  • VENC 将逐帧的 NV12/YUV 图像压缩为 H.264/H.265 码流。
  • RTSP 仅负责传输已经编码的码流,不负责编码。

3. 一帧视频图像经历了什么

以 1920×1080、30fps 的摄像头为例:

Sensor
  ↓ 输出 RAW10 Bayer 数据
MIPI CSI-2
  ↓
VICAP
  ↓ 将帧送入内存缓冲区
ISP
  ↓ 自动曝光、白平衡、去马赛克、降噪、颜色校正
NV12,1920×1080,30fps
  ↓
VENC
  ↓ H.264/H.265 压缩
H.264 NALU:SPS/PPS/IDR/P 帧
  ↓
RTSP Server
  ↓ RTP 打包并经 TCP/UDP 发送
VLC / NVR / Web 前端

一帧 1080P NV12 图像约占:

1920 × 1080 × 1.5 ≈ 3 MB

30fps 时,未压缩图像数据量约为:

3 MB × 30 = 90 MB/s

若 H.264 编码码率为 4 Mbps,网络侧实际数据量约为:

4 Mbps ÷ 8 = 0.5 MB/s

这解释了硬件编码器的价值:把巨大的原始图像流压缩成适合存储和网络传输的码流,同时避免 CPU 被软件编码占满。


4. 软件分层

应用程序
  ├─ RTSP / MP4 / WebRTC 业务逻辑
  ├─ RKMedia 或 MPP
  └─ ALSA / V4L2 / DRM
          ↓
RK 内核媒体驱动
  ├─ rkcif / vicap
  ├─ rkisp
  ├─ rkvenc / rkvdec
  ├─ rga
  ├─ i2s / codec
  └─ drm
          ↓
Device Tree
  ├─ 摄像头型号、I2C 地址
  ├─ MIPI lane、时钟、reset/pwdn GPIO
  ├─ ISP/VICAP 连接关系
  └─ I2S、Codec、功放等
          ↓
RK 芯片与板级硬件

各层职责:

  • Device Tree:描述开发板上有哪些硬件、硬件之间如何连接。
  • 内核驱动:使硬件成为 Linux 可使用的设备节点。
  • V4L2 / ALSA:Linux 通用视频和音频接口。
  • MPP:Rockchip 较底层的媒体处理接口。
  • RKMedia:对 VI、VENC、AI、AENC、RGA 等模块的较高层封装。
  • rkaiq:调节 ISP 的自动曝光、白平衡、HDR、降噪等画质能力。

5. Demo 架构:Camera + Mic → RTSP

目标链路:

CSI 摄像头 ─→ VI ─→ VENC(H.264) ─→ RTSP video track
I2S 麦克风 ─→ AI ─→ AENC(G.711/AAC) ─→ RTSP audio track

RKMedia 中的关键思想是模块绑定:

VI channel 0  ── bind ──>  VENC channel 0
AI device     ── bind ──>  AENC channel 0

绑定后:

  1. VI 产生 NV12 帧;
  2. 帧通过 DMA Buffer 直接传给 VENC;
  3. VENC 输出 H.264 码流;
  4. 应用只需要从 VENC 获取编码后码流,发送给 RTSP 客户端。

这种链路通常称为 zero-copy(零拷贝) 或 少拷贝。实际含义是:图像数据尽量不从硬件缓冲区复制到 CPU 私有内存后再复制回来。


6. Demo 的初始化与退出顺序

启动顺序:

1. 初始化系统
2. 启动 ISP(摄像头需要 ISP 时)
3. 配置并启动 VI
4. 配置并创建 VENC
5. Bind:VI → VENC
6. 配置 AI 与 AENC
7. Bind:AI → AENC
8. 创建 RTSP Server
9. 循环获取 VENC/AENC 码流并发送

退出时按相反方向执行:先停止 RTSP 和取流线程,解除 Bind,销毁编码器,关闭输入,再反初始化系统。


7. 视频核心代码骨架

以下示例以 RKMedia 风格 API 展示核心流程。请按实际 SDK 调整头文件、设备节点和字段名。

#include "rkmedia_api.h"
#include "rtsp_demo.h"

int main(void) {
    RK_MPI_SYS_Init();

    /* 1. 摄像头输入:VI */
    VI_CHN_ATTR_S vi_attr;
    memset(&vi_attr, 0, sizeof(vi_attr));

    vi_attr.pcVideoNode = "rkispp_scale0";
    vi_attr.u32BufCnt = 3;
    vi_attr.u32Width = 1920;
    vi_attr.u32Height = 1080;
    vi_attr.enPixFmt = IMAGE_TYPE_NV12;
    vi_attr.enWorkMode = VI_WORK_MODE_NORMAL;

    RK_MPI_VI_SetChnAttr(0, 0, &vi_attr);
    RK_MPI_VI_EnableChn(0, 0);

    /* 2. 硬件编码器:VENC */
    VENC_CHN_ATTR_S venc_attr;
    memset(&venc_attr, 0, sizeof(venc_attr));

    venc_attr.stVencAttr.enType = VIDEO_ID_AVC;
    venc_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;
    venc_attr.stVencAttr.u32PicWidth = 1920;
    venc_attr.stVencAttr.u32PicHeight = 1080;
    venc_attr.stVencAttr.u32VirWidth = 1920;
    venc_attr.stVencAttr.u32VirHeight = 1080;

    venc_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
    venc_attr.stRcAttr.stH264Cbr.u32BitRate = 4 * 1024;
    venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 30;
    venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;
    venc_attr.stRcAttr.stH264Cbr.u32Gop = 60;

    RK_MPI_VENC_CreateChn(0, &venc_attr);

    /* 3. 建立零拷贝绑定:VI → VENC */
    MPP_CHN_S vi_chn = {
        .enModId = RK_ID_VI,
        .s32DevId = 0,
        .s32ChnId = 0
    };

    MPP_CHN_S venc_chn = {
        .enModId = RK_ID_VENC,
        .s32DevId = 0,
        .s32ChnId = 0
    };

    RK_MPI_SYS_Bind(&vi_chn, &venc_chn);

    /* 4. 创建 RTSP 服务 */
    rtsp_demo_handle rtsp = create_rtsp_demo(554);
    rtsp_session_handle session = rtsp_new_session(rtsp, "/live");
    rtsp_set_video(session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);

    /* 5. 取得 H.264 码流并发送 */
    while (1) {
        MEDIA_BUFFER mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, 0, -1);

        if (mb) {
            void *data = RK_MPI_MB_GetPtr(mb);
            size_t size = RK_MPI_MB_GetSize(mb);
            int64_t pts = RK_MPI_MB_GetTimestamp(mb);

            rtsp_tx_video(session, data, size, pts);
            rtsp_do_event(rtsp);

            RK_MPI_MB_ReleaseBuffer(mb);
        }
    }
}

启动服务后,通常可通过 VLC 打开:

rtsp://<开发板IP>/live

8. Bind 背后的意义

下面这句是高性能链路的关键:

RK_MPI_SYS_Bind(&vi_chn, &venc_chn);

它表达的是:

VI 产生的 NV12 Buffer
     ↓
尽量不经过应用层 memcpy
     ↓
直接交给 VENC 硬件编码

如果业务需要在中间自行处理帧,例如送 NPU 推理、做算法或调试,可以不用 Bind,而由应用手动转发:

MEDIA_BUFFER frame = RK_MPI_SYS_GetMediaBuffer(RK_ID_VI, 0, -1);
RK_MPI_SYS_SendMediaBuffer(RK_ID_VENC, 0, frame);
RK_MPI_MB_ReleaseBuffer(frame);

手动转发灵活,但需要自行处理帧率节奏、缓存积压、超时与 Buffer 生命周期。


9. 音频链路

音频模块和视频模块是一一对应的:

AI = Audio Input,采集 PCM
AENC = Audio Encoder,将 PCM 编码为 G.711/AAC 等

监控场景常见参数:

采样率:16000 Hz
位宽:16 bit
声道:1
编码:G.711A

原始 PCM 数据量:

16000 samples/s × 16 bit × 1 channel = 256 kbps

G.711A 后约为:

128 kbps

音视频同步依赖 PTS:

  • 视频 PTS 通常来自编码器;
  • 音频 PTS 应按采样率和采样数递增;
  • 不要将“收到数据的系统时间”随意混作媒体时间戳。

如果声音越来越不同步,检查音视频 PTS、丢帧、缓存积压,以及 RTSP 客户端是否正确识别音频编码。


10. ISP:决定画质的关键模块

ISP 不是编码器,但它决定图像质量。典型能力包括:

  • AE:自动曝光;
  • AWB:自动白平衡;
  • AF:自动对焦,需要镜头马达支持;
  • NR:暗光降噪;
  • HDR:同时保留亮部和暗部细节;
  • 锐化、去雾、畸变校正等。

推荐调试顺序:

确认 Sensor 被识别
→ 确认 MIPI/CSI 稳定收帧
→ 确认 ISP 输出图像正确
→ 再调编码码率、GOP 与低延迟

如果原始画面已经花屏、偏色、闪烁或曝光异常,优先检查 Sensor、MIPI、时钟、设备树与 ISP 参数,而不是先怀疑 H.264 编码器。


11. 常用排障路径

没有视频设备节点

dmesg | grep -Ei "isp|cif|csi|sensor|mipi"
media-ctl -p
v4l2-ctl --list-devices

重点检查:

  • Sensor 驱动是否 probe 成功;
  • I2C 地址是否正确;
  • reset/pwdn GPIO 是否正确;
  • MIPI lane 数、lane 顺序及时钟是否匹配;
  • Device Tree endpoint 是否正确连接。

有画面但花屏或偏色

常见原因:

  • RAW10/RAW12 等输入格式配置不一致;
  • MIPI lane 或时钟质量问题;
  • Sensor 寄存器初始化错误;
  • ISP IQ 文件或参数不匹配;
  • NV12 与 NV21 等像素格式混淆。

RTSP 可打开但黑屏

逐段验证:

v4l2-ctl 验证摄像头
→ 保存一帧 NV12/JPEG 验证 ISP
→ 将 H.264 码流写为 .h264 文件
→ 用 VLC 本地播放 .h264
→ 最后检查 RTSP
  • 裸 .h264 无法播放:问题多在 VI、ISP 或 VENC;
  • 裸 .h264 可播放但 RTSP 失败:重点检查 SPS/PPS、时间戳、RTP/RTSP 封装和网络。

延迟过高

检查以下项目:

  • GOP 是否过大;
  • 是否使用 B 帧;
  • 编码或网络缓冲是否积压;
  • RTSP 是否走 TCP;
  • 客户端网络缓存是否过大;
  • 应用层是否发生多次 memcpy;
  • 是否把高分辨率图像拉到 CPU 做软件缩放。

低延迟监控的常见设置:

H.264 CBR
30 fps
GOP = 30 或 60
关闭 B 帧
使用硬件 RGA
VI → VENC Bind
客户端缩小网络缓存

12. 用“媒体图”理解整个系统

不要把 RK 音视频理解为一段孤立的摄像头编码代码。它是一张可以按业务拼接的媒体图:

Camera → VI → ISP → RGA → VENC → RTSP
                         ├→ JPEG → 抓拍
                         ├→ NPU  → AI 检测
                         └→ VO   → 本地屏幕

Mic → AI → AENC → RTSP / MP4

加入 AI 检测时,一个常见架构为:

VI → RGA(缩小到 640×640) → NPU
VI → VENC → RTSP
NPU 检测结果 → RGA/OSD → VENC

主码流持续以高分辨率编码,NPU 只处理缩小后的图像,检测结果以框或文字叠加回视频。这是智能摄像机常见的实现方式。


13. 推荐学习顺序

  1. Linux 基础:设备树、驱动日志、dmesg、media-ctl、v4l2-ctl、ALSA。
  2. 摄像头链路:Sensor → MIPI → CSI → ISP → NV12。
  3. 硬件编解码:NV12 → H.264/H.265;理解码率、帧率、GOP、I/P/B 帧。
  4. RKMedia / MPP:VI、VENC、AI、AENC、RGA、Bind、MEDIA_BUFFER。
  5. 协议与封装:H.264 裸流、MP4、RTSP、RTP、WebRTC。
  6. 系统优化:DMA Buffer、零拷贝、缓冲深度、延迟与内存带宽。
  7. 进阶:ISP 调优、OSD、录像、双码流、NPU 推理、WebRTC。

总结

硬件负责高吞吐处理;内核驱动负责把硬件暴露为 Linux 设备;RKMedia/MPP 负责连接媒体模块;应用负责业务和协议输出。

真正的工程能力,是能够沿着 Sensor → ISP → Buffer → 编码器 → 网络 → 播放端 逐段验证、定位和优化问题。