在 RK3588 边缘计算开发中,Python 开发者常面临一个痛点:OpenCV 的 CPU 解码性能不足,而 FFmpeg 的硬解配置极其繁琐。 本文将分享一种"最佳实践"架构:利用 Rockchip 原生 MPP (Media Process Platform) 库编写高性能 C++ 解码器,并通过 Pybind11 将其封装为 Python 模块。通过这种方式,我们可以在 Python 中轻松实现 8 路 1080P 实时解码,并将数据零拷贝无缝传递给 YOLOv8 等 AI 模型。
1. 背景与痛点
RK3588 拥有强大的 VPU(视频处理单元),宣称支持 32 路 1080P 解码。但在 Python 环境下,简单的 cv2.VideoCapture 只能利用 CPU 软解,跑两三路流就已经 CPU 爆满。
虽然可以使用带有 GStreamer 支持的 OpenCV,但环境配置极其容易出错。最硬核且高效的方法是:
-
C++ 层:调用 librkmpp 进行硬件解码。
-
C++ 层:调用 RGA。
-
中间层 :使用 Pybind11 封装。
-
Python 层:直接获取解码后的图像数据进行推理。
2. 整体架构设计
我们将项目分为两部分:
-
rkmpp_decoder(C++ Shared Library): 负责 RTSP 拉流、MPP 解码、RGA 格式转换。 -
app.py(Python Script): 负责业务逻辑、多线程管理、AI 推理。
3. Python 端 8 路并行测试(一路解码cpu利用率约6%,八路解码cpu利用率约
25%)
python实例代码如下
python
import sys
import os
import time
import multiprocessing
import signal
import gc
# 1. 确保能找到 libs 目录下的 rk_decoder.so
sys.path.append(os.path.join(os.path.dirname(__file__), 'libs'))
try:
from libs import rk_decoder
except ImportError as e:
print(f"❌ 导入失败: {e}. 请确保 rk_decoder.so 在 libs 目录下。")
sys.exit(1)
# --- 2. 视频流处理子进程 ---
def worker_process(worker_id, rtsp_url):
"""
每个子进程独立运行的逻辑
"""
PREFIX = f"[Worker-{worker_id:02d}]"
print(f"{PREFIX} 🚀 正在初始化: {rtsp_url}")
decoder = rk_decoder.MppDecoder(rtsp_url)
decoder.start()
total_frames = 0
start_time = time.time()
# 调整 Python 垃圾回收阈值,防止高并发下内存抖动
gc.set_threshold(10000, 10, 10)
try:
while True:
# 读取一帧 (BGR)
frame = decoder.read()
if frame is None or frame.size == 0:
# 稍微休息,避免 CPU 盲等
time.sleep(0.01)
continue
total_frames += 1
# 每 60 帧打印一次状态 (约 2 秒)
if total_frames % 60 == 0:
elapsed = time.time() - start_time
fps = total_frames / elapsed
print(f"{PREFIX} FPS: {fps:.2f} | 帧数: {total_frames} | 尺寸: {frame.shape[1]}x{frame.shape[0]}")
# 每 600 帧做一次垃圾回收大扫除
if total_frames % 600 == 0:
gc.collect()
except KeyboardInterrupt:
pass
finally:
print(f"{PREFIX} 🛑 正在停止解码器...")
decoder.stop()
# --- 3. 主进程控制逻辑 ---
def main():
# ================= 自定义配置 =================
TOTAL_PROCESSES = 16 # 你想启动的总路数 (例如 6, 8, 16)
URL_1 = "rtsp://192.168.31.151:8554/mystream1"
URL_2 = "rtsp://192.168.31.151:8554/mystream2"
# =============================================
process_list = []
print(f"🔥 准备启动 {TOTAL_PROCESSES} 路解码任务...")
# 1. 分配任务并启动进程
for i in range(TOTAL_PROCESSES):
# 核心逻辑:对半分配 URL
if i < (TOTAL_PROCESSES // 2):
target_url = URL_1
else:
target_url = URL_2
p = multiprocessing.Process(
target=worker_process,
args=(i, target_url),
name=f"RK-Worker-{i}"
)
p.start()
process_list.append(p)
# 交错启动,给 MPP 驱动一点缓冲时间
time.sleep(0.5)
print(f"✅ 所有进程已启动。监控中... (按 Ctrl+C 退出)")
# 2. 守候进程
try:
while True:
# 检查子进程健康状况
for p in process_list:
if not p.is_alive():
print(f"⚠️ 警告: {p.name} 已意外退出!")
time.sleep(5)
except KeyboardInterrupt:
print("\n🛑 接收到终止信号,正在清理所有路数...")
for p in process_list:
if p.is_alive():
p.terminate() # 发送 SIGTERM
# 等待所有子进程释放硬件资源
for p in process_list:
p.join()
print("👋 运行结束,再见!")
if __name__ == "__main__":
# 在 RK3588 上,使用 'spawn' 启动模式最稳定,避免 hardware handle 冲突
multiprocessing.set_start_method('spawn', force=True)
main()
一路解码cpu使用情况截图

八路解码cpu使用情况截图

4. 总结
通过 Pybind11 将 RK3588 的 MPP 能力暴露给 Python,我们既保留了 C++ 的极致性能,又享受了 Python 生态的便利性。