# 基于RK3588的低延迟音视频项目 **Repository Path**: dilake2020/rk3588-av2 ## Basic Information - **Project Name**: 基于RK3588的低延迟音视频项目 - **Description**: 基于RK3588与IMX415的实时视频录像项目,采用V4L2采集、CPU缓冲池、MPP硬件H.264编码及三线程Sink写盘,实现1080p60稳定Annex B录像与长尾性能追踪。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-10-16 - **Last Updated**: 2026-10-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RK3588 实时视频采集与硬件编码项目 项目名:`rk_media_pipeline` ## 代码分层 仓库按五个物理层组织,目录名就是架构边界,不再使用统一 `include/`、`src/` 和 `apps/` 按文件类型堆放: ```text core/ 通用媒体模型、缓冲区、队列和会话时钟 hardware/ V4L2视频与ALSA音频设备适配 middleware/ 图像处理、编解码、同步、容器、输出和RTP/RTSP协议 service/ 实时线程、队列、停止及异常传播的业务编排 application/ 命令行入口、参数转换和结果打印 tests/ 按上述层次镜像组织的测试 ``` 依赖方向为: ```text application -> service -> hardware + middleware -> core ``` `middleware/` 内部继续按职责拆分: ```text processing/ 像素格式转换 codec/ MPP H.264与FFmpeg AAC sync/ 启动门控和运行同步观察 container/ MuxEvent与MPEG-TS复用 output/ 输出抽象及文件Sink streaming/rtp/ RTP头、Annex-B解析和H.264 FU-A ``` 分层只是代码和依赖边界,不等于一层一个线程;组合程序仍保持五个工作线程和三条有界队列。 本项目按“每一步都可独立运行和验证”的方式实现以下第一阶段数据流: ```text 摄像头 -> V4L2(YUYV422 或 NV12/NV12M)-> CPU/NV12 -> RK3588 MPP/H.264 -> output.h264 ``` ## 当前进度 - [x] 第一天:工程骨架和构建检查 - [x] 第二天:V4L2 YUYV 采集 - [x] 第三天:YUYV 转 NV12 - [x] V4L2 多格式与 IMX415 兼容层(`/dev/video11`、1080p60 NV12/MPLANE 已实机验证) - [x] 第四天:MPP 离线 H.264 编码 - [x] 第五天:数据模型与有界队列 - [x] 第六天:实时流水线(现为采集、编码、Sink三线程) - [x] 第七天:稳定性测试和文档收尾 - [x] IMX415 1080p60 CPU固定缓冲池与时间事件追踪 ## 构建 需要支持 C++17 的编译器和 CMake 3.16 或更高版本: ```bash cmake -S . -B build cmake --build build -j ./build/capture_dump --help ``` ### 在 x86_64 虚拟机交叉编译 MPP 目标 虚拟机不能运行 RK3588 硬件编解码,但可按野火教程用 AArch64 工具链编译 MPP,并以此完成 `mpp_encode`、`video_pipeline` 的编译检查。本仓库把生成物隔离在不跟踪的 `.deps/mpp-aarch64`,当前安装基于 Rockchip MPP `develop` 分支提交 `0986d01`: ```bash git clone --depth 1 --branch develop \ https://github.com/rockchip-linux/mpp.git /tmp/rockchip-mpp cmake -S /tmp/rockchip-mpp -B /tmp/rockchip-mpp-build-aarch64 \ -DCMAKE_BUILD_TYPE=Release \ -DTOOLCHAIN=aarch64-linux-gnu- \ -DCMAKE_TOOLCHAIN_FILE=/tmp/rockchip-mpp/build/linux/aarch64/arm.linux.cross.cmake \ -DCMAKE_INSTALL_PREFIX="$PWD/.deps/mpp-aarch64" \ -DBUILD_SHARED_LIBS=ON cmake --build /tmp/rockchip-mpp-build-aarch64 -j4 cmake --install /tmp/rockchip-mpp-build-aarch64 ``` 使用本地MPP安装交叉编译本项目: ```bash PKG_CONFIG_PATH="$PWD/.deps/mpp-aarch64/lib/pkgconfig" \ cmake -S . -B build-aarch64-verify \ -DCMAKE_TOOLCHAIN_FILE="$PWD/cmake/aarch64-linux-gnu.cmake" \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_SKIP_RPATH=ON \ -DRKMEDIA_BUILD_MPP_ENCODER=ON cmake --build build-aarch64-verify -j4 ``` 生成的是 AArch64 程序,只能复制到具有兼容 MPP 运行库和硬件服务节点的开发板运行。虚拟机安装 只证明头文件、链接和目标代码编译通过,不属于MPP硬件编码验收。 ## V4L2 原始帧采集 默认采集 `/dev/video0` 的 640x480 YUYV422,向驱动请求 30 fps,并在十秒后 写完 `capture.yuyv`: ```bash ./build/capture_dump ``` 完整调用示例: ```bash ./build/capture_dump \ --device /dev/video0 \ --output capture.yuyv \ --seconds 10 \ --width 640 \ --height 480 \ --fps 30 \ --pixel-format yuyv \ --buffer-layout single ``` `--seconds 0` 表示持续采集直到收到 Ctrl+C 或 SIGTERM。程序每秒打印实际帧率、 帧数、最新序号、`bytesused`、V4L2 时间戳、poll 超时数和序号缺口数。声明帧率 来自驱动,实际帧率由单调时钟统计。 采集层使用一个 `V4L2VideoSource` 同时适配 single-planar 和 multiplanar V4L2 设备。 `--pixel-format` 可选 `yuyv`、`uyvy`、`nv12`、`nv12m`;`--buffer-layout` 可选 `auto`、`single`、`multiplanar`。默认值保持 USB UVC 摄像头兼容。 当前板卡已确认 IMX415 的 RKISP 主路径为 `/dev/video11`,原生输出约为 1080p60;自研程序第一轮 按原生速率验收,之后再按产品需要实现确定性 30 fps 降帧: ```bash ./build/capture_dump \ --device /dev/video11 \ --output imx415_1080p60.nv12 \ --seconds 10 \ --width 1920 \ --height 1080 \ --fps 60 \ --pixel-format nv12 \ --buffer-layout multiplanar ``` 节点号以 `v4l2-ctl --list-devices` 的实际输出为准;若驱动协商为 `NV12M`,将 `--pixel-format` 改为 `nv12m`。4K@30 留待 1080p 采集、编码和稳定性验收之后。 ## ALSA 音频采集(开发中) 安装 ALSA 开发包后,CMake 会构建独立的 `audio_capture`: ```bash sudo apt-get install -y libasound2-dev cmake -S . -B build cmake --build build -j ``` 鲁班猫 4 当前实机枚举的模拟录音设备为 `rockchip-es8388`。第一轮基线使用 48 kHz、双声道、 S16_LE、1024-frame period 和 4096-frame buffer: ```bash ./build/audio_capture \ --device hw:CARD=rockchipes8388,DEV=0 \ --output audio.wav \ --sample-rate 48000 \ --channels 2 \ --period-frames 1024 \ --buffer-frames 4096 \ --seconds 10 ``` `--seconds 0` 表示持续录音到 SIGINT/SIGTERM。程序结束时回填 WAV 长度,并打印实际 ALSA period/buffer、采集和写出采样数、短读、overrun、挂起恢复次数及写出时长。当前 PCM 链路已通过 虚拟机构建、板端原生构建、60 秒、SIGINT/立即重开和 10 分钟 ES8388 实录;用户主观听感仍需 在板旁确认。 音频 PTS 使用 ALSA `SND_PCM_TSTAMP_TYPE_MONOTONIC`:首个 period 由硬件时间戳和 capture delay 推导首采样的绝对单调时钟时间,后续按累计采样数推进。该 PTS 不再从零开始,可与采用 `CLOCK_MONOTONIC` 的 V4L2 timestamp 计算初始偏移和长期漂移;组合流水线仍须检查 V4L2 buffer 报告的 timestamp flag,不能仅凭时间戳数值假设时钟域一致。 ### AAC-LC 独立编码 安装 FFmpeg 的 `libavcodec`、`libavutil`、`libswresample` 开发包后,CMake 会额外构建 `audio_aac_capture`。它将 ALSA 的 S16_LE 交错 PCM 放入样本 FIFO,以 1024 samples 为一帧 转换并编码成 AAC-LC,最后添加 ADTS 头写入独立 `.aac` 文件: ```bash ./build/audio_aac_capture \ --device hw:CARD=rockchipes8388,DEV=0 \ --output audio.aac \ --sample-rate 48000 --channels 2 --bitrate 128000 \ --period-frames 1024 --buffer-frames 4096 \ --seconds 10 ``` 正常到时或收到 SIGINT/SIGTERM 后都会 flush 编码器;尾部不足 1024 samples 时补零并打印 `padded_samples`。编码包继续携带绝对 monotonic PTS,程序同时统计非单调包数。ADTS 只用于 当前独立验收;后续接入 MPEG-TS/MP4 复用时应直接传递 AAC access unit,不重复添加 ADTS 头。 当前 `audio_aac_capture` 已采用采集、AAC 编码和 ADTS Sink 三线程结构。容量 8 的 PCM 队列与 容量 32 的 AAC 包队列都使用阻塞反压,音频过载时不会静默丢弃旧帧;程序结束时打印两级队列 峰值。该结构已通过 ES8388 的 10 秒、60 秒、SIGINT 和独立 AAC 5 分钟实机验证。 播放裸 YUYV 文件: ```bash ffplay -f rawvideo \ -pixel_format yuyv422 \ -video_size 640x480 \ -framerate 25 \ capture.yuyv ``` ## YUYV 转 NV12 离线转换程序按相邻两行色度样本的整数平均生成 NV12: ```bash ./build/yuv_convert \ --input capture.yuyv \ --output converted.nv12 \ --width 640 \ --height 480 \ --frames 0 ``` 默认输入 stride 为 `width * 2`,输出 stride 为 `width`;可用 `--input-stride` 和 `--output-stride` 指定带 padding 的行布局。 ## MPP 离线 H.264 编码 `mpp_encode` 在安装了 `rockchip_mpp` 开发包的 RK3588 环境构建,也可使用上述本地AArch64 MPP 在x86虚拟机交叉编译。它读取连续紧凑的NV12文件,使用CBR、指定GOP和Rockchip MPP输出 Annex B H.264: ```bash ./build/mpp_encode \ --input converted.nv12 \ --output output.h264 \ --width 640 --height 480 \ --fps 25 --bitrate 1500000 --gop 25 ``` 输入大小必须精确等于 `width * height * 3 / 2 * 帧数`,宽高必须为偶数。程序对最后一帧设置 EOS, 并要求 MPP 返回 EOS 包才报告成功。 ## 有界帧队列 `core/media/bounded_queue.hpp` 提供平台无关的 `BoundedQueue`。实时流水线的原始帧队列使用 `BoundedQueue>`:生产者不会因队列满而等待,满时丢弃 最旧帧并保留最新帧;编码包队列使用 `push_wait()`,满时反压编码线程而不丢包。消费者在空队列 等待;`close()` 会唤醒所有消费者,已排队元素仍会继续消费,队列排空后 `pop()` 返回空值,后续 `push()` 被拒绝。 该组件已独立完成 FIFO、满队列丢帧、等待/唤醒、关闭与多消费者唤醒测试,并已接入 V4L2 与 MPP 的实时线程。 ## 实测基线与限制 在鲁班猫 4、USB `/dev/video0`、640x480 YUYV、请求 30 fps、H.264 25 fps/1.5 Mbps/GOP 25 的条件下: - 60 秒:1,515 帧采集/编码、0 队列丢帧、25.22 fps、输出 11,341,517 bytes; - 10 分钟:15,133 帧采集/编码、0 队列丢帧、25.22 fps、输出 113,667,537 bytes; - 进程 RSS 在运行中由约 7,164 kB 一次性升至约 7,692 kB 后保持稳定,未观察到持续增长; - 两次输出均通过 `ffprobe` 解析和 FFmpeg 解码。 当前状态:IMX415 1080p60自研采集、MPP编码、信号退出、异常路径和CPU固定缓冲池已经实机验证; 旧逐帧分配路径的10分钟测试在队列容量4和10时分别丢弃28帧和15帧,池化路径使用队列10、池13 时36001帧全部编码写出。已验证V4L2 `VIDIOC_EXPBUF`、MPP DMA-BUF导入及3帧同步直连编码; 实时 pipeline 已接入 `FrameLease` DMA-BUF 模式,并在 IMX415 1080p60 上完成单消费者稳定性 验收。DMA-BUF 模式当前仅支持 RKISP 单 memory-plane NV12 和单个 MPP 消费者;慢消费者测试使用 `LatestFrameQueue` 只保留最新 token,不阻塞编码主链路。当前不使用 RGA,也未实现多消费者零拷贝;不包含 音频、网络、显示、MP4或H.265。 ## 实时三线程流水线 `video_pipeline` 使用采集、编码和Sink三个线程:采集线程从 V4L2 取得帧、在 QBUF 前转换为 拥有独立内存的 `VideoFrame` 并写入原始帧队列;编码线程调用MPP并将`EncodedPacket`写入阻塞式 压缩包队列;Sink线程独占Annex B文件写入和关闭。原始帧队列满时丢最旧帧,压缩包队列不丢包、 满时反压编码线程。最后一帧仅在原始帧队列关闭且排空后发送EOS,随后Sink排空压缩包队列。 ```bash ./video_pipeline \ --device /dev/video0 --output output.h264 \ --width 640 --height 480 \ --capture-fps 30 --encode-fps 25 \ --bitrate 1500000 --gop 25 --seconds 10 ``` `--seconds` 从首帧开始计时,`0` 表示持续运行到 Ctrl+C。程序退出时打印采集/编码帧数、队列 丢帧数、编码字节数、poll 超时、序号缺口和实际采集 fps。SIGINT/SIGTERM 会停止采集、关闭队列、 排空编码、确认 EOS 后退出。 开发板验证输出: ```bash ffprobe -v error -show_entries stream=codec_name,profile,width,height,avg_frame_rate \ -of default=noprint_wrappers=1 output.h264 ffmpeg -v error -i output.h264 -f null - ``` 当前IMX415实时基线命令: ```bash ./video_pipeline \ --device /dev/video11 --output imx415-1080p60.h264 \ --width 1920 --height 1080 \ --pixel-format nv12 --buffer-layout multiplanar \ --capture-fps 60 --encode-fps 60 \ --bitrate 8000000 --gop 60 --queue-capacity 10 \ --packet-queue-capacity 120 \ --buffer-mode pool --buffer-pool-capacity 13 \ --trace-output pipeline-trace.csv --seconds 10 ``` RKISP mainpath不支持 `VIDIOC_S_PARM/G_PARM` 时,程序不把它当作采集失败;编码时间基仍使用 显式的 `--encode-fps`,实际采集速率由运行统计测量。USB摄像头不再作为后续验收依赖。 `--trace-output`可省略;启用时,关键节点事件先写入预分配内存,线程结束后再批量生成CSV, 不会在实时线程中逐帧执行文件写入。因编码线程的一帧前瞻,CPU池容量必须至少为原始帧队列容量加3。 ### DMA-BUF 模式 将 `--transport dmabuf` 传给 `video_pipeline` 可启用 V4L2 到 MPP 的 DMA-BUF 路径: ```bash ./video_pipeline \ --device /dev/video11 --output imx415-dmabuf.h264 \ --width 1920 --height 1080 \ --pixel-format nv12 --buffer-layout multiplanar \ --capture-fps 60 --encode-fps 60 \ --bitrate 8000000 --gop 60 --queue-capacity 4 \ --v4l2-buffer-count 8 --transport dmabuf --seconds 10 ``` 该模式通过 `VIDIOC_EXPBUF` 导出 V4L2 buffer,并将导出的 fd 导入 MPP。DMA-BUF fd 由导出表 统一持有和关闭;token 只借用 fd,并通过 `CapturedFrameLease` 保持对应 V4L2 buffer 处于 DQBUF 状态。只有最后一个 token/lease 释放后,采集线程才会执行 `QBUF`。所有消费者退出并完成最终 回收后,程序才执行 `STREAMOFF`。普通模式要求 V4L2 buffer 数量至少为 `queue_capacity + 3`;启用人为慢消费者测试时要求至少为 `queue_capacity + 5`。 慢消费者验证参数仅用于测试: ```bash --transport dmabuf --slow-consumer-delay-ms 100 ``` 它模拟一个落后消费者。`LatestFrameQueue` 覆盖旧 token 时会立即释放其 lease,因此慢消费者 不会阻塞 MPP 编码;该功能不是正式 AI 推理接口。 ## 固定 MVP 参数 | 参数 | 值 | |---|---| | 设备 | `/dev/video0` | | 输入 | YUYV422,640x480,30 fps | | 编码输入 | NV12 | | 编码格式 | H.264 CBR,1.5 Mbps,GOP 25 | | 输出 | `output.h264`(Annex B) | ## 开发环境检查 首次检查所在环境为 x86_64 Ubuntu 20.04 虚拟机,未检测到: - `/dev/video*` 摄像头设备; - `v4l2-ctl`、FFmpeg/FFplay/FFprobe; - Rockchip MPP 库、头文件和 `mpi_enc_test`。 因此虚拟机适合工程开发、编译及 CPU 单元测试;V4L2 实机采集、MPP 硬编和 后续单次稳定性验收最长运行 5 分钟,并在 RK3588 鲁班猫板卡执行。 板卡环境建议安装 `v4l-utils`、FFmpeg 和 Rockchip MPP 开发包,并依次检查: ```bash v4l2-ctl --list-devices v4l2-ctl -d /dev/video0 --list-formats-ext ffmpeg -encoders | grep rkmpp ffmpeg -decoders | grep rkmpp ffmpeg -filters | grep rkrga mpi_enc_test --help ``` ## 当前限制 第一阶段不包含音频、网络传输、RTSP、Qt、RGA、DRM、MP4、H.265 和多路编码。 DMA-BUF 已实现为当前 RKISP 单平面 NV12 到单 MPP 消费者的受限路径;尚未实现 RGA、AI 分支或 多消费者零拷贝。MIPI 摄像头已通过 IMX415/RKISP 的当前配置验证,但尚未扩展为通用多路摄像头框架。