目录
pcVideoNode:视屏流的访问接口或者句柄,一对一的关系。
RK_MPI_VI_EnableChn 是 Rockchip MPI 中用于启动视频输入通道的函数
2.VI模块初始化---配置 结构体 const VI_CHN_ATTR_S *
2.结果是并没有打印,但vi创建成功,主要原因---程序中并没有终端打印,
一.理论前置知识
1.前置知识:V4L2框架
V4L2(video for linux2):是Linux操作系统上处理视频设备的核心框架。它提供了一套统一的API,允许应用程序与各种视频设备进行交互,无论是USB摄像头、内置摄像头还是复杂的视频采集卡。
-
它通过一系列
ioctl调用来控制设备。 -
其核心流程是:打开 -> 设置格式 -> 申请缓冲 -> 启动流 -> 循环取数据 -> 停止。
具体的可以看下这个博主:
2.VI模块:视频输入模块
作用:读取sensor(摄像头)的数据,本质是对V4L2驱动架构的接口做了封装,
-
打开
/dev/video0等设备节点。 -
使用
ioctl向V4L2驱动设置格式(如NV12)、申请缓冲区。 -
启动视频流,并循环从V4L2驱动获取数据帧。

3.RV1126的VI模块结构体参数重要参数值
typedef struct rk_vi_chn_attr {
// 【必填】视频节点路径,决定使用哪路视频流
const char *pcVideoNode; // 例如:"rkispp_scale0"
// 【必填】采集图像的分辨率
RK_U32 u32Width; // 宽度,例如:1920
RK_U32 u32Height; // 高度,例如:1080
// 【必填】采集图像的像素格式
IMAGE_TYPE_E enPixFmt; // 例如:RK_FMT_YUV420SP (NV12)
// 【重要】缓冲区数量
RK_U32 u32BufCnt; // 例如:3 (默认值)
// 【重要】缓冲区类型
VI_CHN_BUF_TYPE enBufType; // 例如:VI_CHN_BUF_TYPE_DMA (强烈推荐)
// 【可选】通道工作模式
VI_WORK_MODE enWorkMode; // 例如:VI_WORK_MODE_NORMAL
} VI_CHN_ATTR_S;
| 成员名称 | 描述 | 说明与典型值 |
| pcVideoNode | V4L2视频设备节点路径 | 这是一个字符串参数(如 "/dev/video0")。它告诉VI模块去操作哪个具体的摄像头设备。这是连接VI模块与底层V4L2驱动的桥梁。 |
| u32Width | 视频图像的宽度,单位像素 | 例如 1920。这必须与Sensor支持的分辨率及V4L2驱动配置相符。 |
| u32Height | 视频图像的高度,单位像素 | 例如 1080。与宽度共同决定采集图像的分辨率。 |
| enPixFmt | 视频数据的像素格式 | 这是枚举类型参数。它直接对应到我们之前讨论的YUV格式,例如: - RK_FMT_YUV420SP (即NV12)- RK_FMT_YUV420P (即I420/YU12)- RK_FMT_YUV422SP- RK_FMT_YUV422P设置此项,VI模块底层就会发起 VIDIOC_S_FMT 的ioctl调用。 |
| u32BufCnt | VI捕获视频的缓冲区数量 | 这是一个极其重要的性能参数。它决定了V4L2驱动内部维护的缓冲区队列长度(通过 VIDIOC_REQBUFS 设置)。典型值:3或4。太少可能导致丢帧(生产者-消费者模型失衡);太多则会增加内存开销和延迟。 |
VI_CHN_BUF_TYPE | VI通道输出数据的存储和传递方式 | 是一个枚举类型
|
| enWorkMode | VI通道的工作模式 | 这也是枚举类型参数,决定了VI模块如何处理数据流。常见模式: - VI_WORK_MODE_NORMAL:正常模式,一帧一帧处理。- VI_WORK_MODE_LINE:行模式(通常用于特定的高性能或算法处理场景,不常用)。 |
pcVideoNode:视屏流的访问接口或者句柄,一对一的关系。
RV1126的ISPP(硬件图形后处理单元):接收到Sensor传来原始图形,然后在硬件上并行生成多个不同规格的输出流。使用则使用下面四个节点,可以获取不同分辨率的视屏流
| 节点名称 | 最大宽度 | 缩放倍数 | 支持的输出格式 |
| rkispp_m_bypass | Sensor最大宽度 | 不支持 | NV12 / NV16 / YUYY / FBC0 / FBC2 |
| rkispp_scale0 | 3264 | 1-8倍 | NV12 / NV16 / YUYY |
| rkispp_scale1 | 1280 | 2-8倍 | NV12 / NV16 / YUYY |
| rkispp_scale2 | 1280 | 2-8倍 | NV12 / NV16 / YUYY |
你不需要自己去计算缩放倍数。你只需要在代码中指定你想要的目标分辨率(u32Width和u32Height),ISPP硬件会自动完成缩放
举例说明:假设你的Sensor支持输出 3264 x 2448 的图像,你使用 rkispp_scale0 节点。
| 设置的目标分辨率 | ISPP硬件执行的缩放操作 | 实际缩放倍数(约) |
3264 x 2448 | 不缩放,输出原图 | 1倍 |
1920 x 1080 | 将原图缩小以适应目标尺寸 | ~1.7倍 (3264/1920 ≈ 1.7) |
1280 x 720 | 将原图缩小到1280x720 | ~2.55倍 (3264/1280 ≈ 2.55) |
640 x 480 | 将原图缩小到640x480 | ~5.1倍 (3264/640 ≈ 5.1) |
416 x 416 | 将原图缩小到416x416 | ~7.85倍 (3264/416 ≈ 7.85) |
u32BufCnt:VI捕获视频缓冲区计数,默认是3
VI读取速度更不上摄像头的采集速度,建立多个缓冲器,解决速度不匹配的问题
在硬件(生产者)和应用程序(消费者)之间建立一个“数据缓冲区队列”,以平滑两者之间处理速度的波动,防止丢帧。
VI_CHN_BUF_TYPE:VI捕捉视频的类型
-
DMA 模式:核心是 “硬件直接内存访问”,专为系统内硬件单元间高效数据传输设计。
-
MMAP 模式:核心是 “用户空间直接映射内核缓冲区”,旨在减少用户态与内核态之间的数据拷贝。
-
特性 DMA 模式 ( VI_CHN_BUF_TYPE_DMA)MMAP 模式 ( VI_CHN_BUF_TYPE_MMAP)设计目的 异构计算/零拷贝流水线。让视频数据能被其他硬件加速器(VPU/NPU/RGA)直接处理。 CPU高效处理。让应用程序(CPU)能以零拷贝的方式访问视频数据。 性能 极高。数据从ISP出来后,存放在物理连续内存中,VPU、NPU等硬件可以直接存取,无需任何格式转换或拷贝,实现了真正的零拷贝流水线。 高。数据从ISP到应用程序,避免了从内核到用户空间的拷贝开销,但对硬件加速器不友好。 硬件资源消耗 对硬件更友好,整体系统效率更高。它消耗的是特定的DMA内存资源,但正因为使用了这种内存,才解放了CPU,并让硬件加速器得以高效工作,从整个SoC的角度看,资源利用率更高 对硬件加速器不友好。如果后续需要VPU编码或NPU推理,驱动需要将数据重新拷贝或映射到一块物理连续的内存中,这会消耗额外的CPU和总线带宽,反而造成了资源浪费。 稳定性/数据丢失 更稳定。驱动为DMA缓冲区管理提供了更成熟和可靠的机制,是嵌入式媒体处理的工业标准。 因为其缓冲区管理更依赖于V4L2驱动和用户空间程序的协调,在系统负载高或处理不及时时,确实有更高的丢帧风险。 4.RV1126的VI模块初始化API
-
RK_MPI_VI_SetChnAttr -
RK_S32 RK_MPI_VI_SetChnAttr(VI_PIPE ViPipe, VI_CHN ViChn, const VI_CHN_ATTR_S *pstChnAttr);功能:配置视频输入通道属性的函数。它在启动视频流之前调用,决定了VI通道如何工作。
-
参数:
-
参数 类型 描述 ViPipeVI_PIPEVI管道号。代表一个物理的摄像头输入源。例如,如果系统连接了2个摄像头,可能用 0和1来区分。ViChnVI_CHNVI通道号。一个物理摄像头(Pipe)可以创建多个通道(Chn),每个通道可以输出不同分辨率/格式的视频流。这正是RV1126“一摄多流”架构的体现。通常从 0开始。pstChnAttrconst VI_CHN_ATTR_S *指向通道属性结构体的指针。这是最关键的参数,它包含了我们之前讨论的所有具体设置,如分辨率、格式、缓冲区数量等。
-
返回值
-
RK_SUCCESS(0):表示函数执行成功,通道属性已按需设置。 -
非0值:表示函数执行失败。失败原因可以是:
-
参数错误(如通道号超出范围)。
-
底层V4L2驱动不支持所设置的分辨率或格式。
-
系统资源不足(如无法申请到指定数量的DMA缓冲区)。
-
注意事项:ViPipe VI管道好和ViChn VI通道号的区别
| 概念 | 比喻 | 在RV1126中的对应 |
|---|---|---|
ViPipe (管道) | 总进水管 | 一个物理摄像头传感器(Sensor),有多少个Sensor就有多少个管道 |
ViChn (通道) | 水龙头 | 一个视频流输出(对应一个 pcVideoNode,如 rkispp_scale0),一个管道里面头多个通道,一根总进水管(ViPipe)上,安装多个不同用途的水龙头(ViChn)。 |
关键结论:一个 ViPipe(摄像头)下面,可以创建多个 ViChn(视频流)。
前提是ViChn没有被占用,如果并占用此通道无法使用
RK_MPI_VI_EnableChn 是 Rockchip MPI 中用于启动视频输入通道的函数
RK_S32 RK_MPI_VI_EnableChn(VI_PIPE ViPipe, VI_CHN ViChn);
| 参数 | 类型 | 描述 |
|---|---|---|
ViPipe | VI_PIPE | VI管道号。指定要启动的通道属于哪个物理摄像头。 |
ViChn | VI_CHN | VI通道号。指定要启动的具体通道。 |
返回值
-
RK_SUCCESS(0):通道启动成功。 -
非0值:通道启动失败。失败原因可能包括:
-
指定的管道或通道不存在。
-
通道属性未设置或设置无效。
-
底层资源分配失败(如DMA内存不足、V4L2设备打开失败等)。
-
该通道已经被启用。
-
5.从指定通道中获取数据。
MEDIA_BUFFER RK_MPI_SYS_GetMediaBuffer(MOD_ID_E enModID, RK_S32 s32ChnID, RK_S32 s32MilliSec);
| 参数名称 | 描述 | 输入/输出 |
|---|---|---|
| enModID | 模块号。 | 输入 |
| s32ChnID | 通道号。 | 输入 |
| s32MilliSec | -1:阻塞,>=0:阻塞等待时间。 | 输入 |
返回值:
-
成功时返回有效的
MEDIA_BUFFER指针; -
失败或超时时返回
NULL。
6.获取缓冲区指针
void *RK_MPI_MB_GetPtr(MEDIA_BUFFER mb);
7.获取缓冲区数据长度
int RK_MPI_MB_GetSize(MEDIA_BUFFER mb);
void *ptr = RK_MPI_MB_GetPtr(mb); // 拿到地址
int size = RK_MPI_MB_GetSize(mb); // 拿到长度
fwrite(ptr, 1, size, file); // 把一帧完整写盘
二.实战----读取摄像头数据
1.思路:
VI模块初始化------启动VI模块工作------开启多线程采集VI数据并保存
2.VI模块初始化---配置 结构体 const VI_CHN_ATTR_S *
VI_CHN_ATTR_S vi_chn_attr;
vi_chn_attr.pcVideoNode = CAMERA_PATH;//配置视频节点路径
vi_chn_attr.u32Width = 1920;
vi_chn_attr.u32Height = 1080;
vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12;//配置像素格式 NV12
vi_chn_attr.u32BufCnt = 3;//数据接受缓存buff
vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;//VI输出数据的存储和传递方式---MMAP 内存映射型
vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;//工作模式 ---正常模式,一帧一帧处理
ret = RK_MPI_VI_SetChnAttr(PIPE_ID, CHN_ID, &vi_chn_attr);
配置像素格式 NV12:YUV420格式 平面打包格式---先存放所有Y,然后UV交替
3.启动VI模块
ret = RK_MPI_VI_EnableChn(PIPE_ID, CHN_ID);
4.开始读取数据:
ret = RK_MPI_VI_StartStream(PIPE_ID, CHN_ID);
5.建立独立的线程去读取VI数据
pthread_t pid;
pthread_create(&pid, NULL, get_camera_vi_thread, NULL);
pthread_create(&pid, NULL, get_camera_vi_thread, NULL); | 创建并启动线程: ① 线程 ID 存到 pid;② 线程属性默认( NULL);③ 线程入口函数是 get_camera_vi_thread;④ 无额外参数( NULL)。 |
思路:
- 建立一个信号处理函数 SIGINT,检测到SIGINT信号,跳到相对应的函数执行终止程序
- 配置vi_chn_attr结构体
- 写入vi_chn_attr结构体:ret = RK_MPI_VI_SetChnAttr(PIPE_ID, CHN_ID, &vi_chn_attr);
- 使能VI模块: ret = RK_MPI_VI_EnableChn(PIPE_ID, CHN_ID);//使用VI通道
- 开始读取VI数据
- 开启多线程读取数据 pthread_create(&pid, NULL, get_camera_vi_thread, NULL);
- while循环--检测到终止程序,跳出循环,直接步骤8,如果没有终止循环,会在线程中一直读取数据
- 关闭通道号
6.线程读取VI数据
void * get_camera_vi_thread(void * args)
{
MEDIA_BUFFER mb = NULL;
FILE * nv12_file = fopen("test_camera.nv12", "w+");
while (!quit)
{
mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VI, CHN_ID, -1);
if(!mb)
{
printf("get Vi mb break....\n");
break;
}
fwrite(RK_MPI_MB_GetPtr(mb), 1, RK_MPI_MB_GetSize(mb), nv12_file);
RK_MPI_MB_ReleaseBuffer(mb);
}
}
MEDIA_BUFFER:
在 RKMedia / Rockchip 的 IPC 框架里,
typedef void* MEDIA_BUFFER;
它只是一个指向内核/驱动层缓冲块的句柄,并非裸指针,更不能直接memcpy。该缓冲块里通常包含:
图像或音频裸数据地址(
MB_GET_PTR(mb))长度(
MB_GET_SIZE(mb))时间戳(
MB_GET_TIMESTAMP(mb))内存类型、缓存属性、偏移、文件描述符等元数据(驱动私有)
7.使用make对编译脚本文件 Makefile进行编译
Makefile 是一个 “编译脚本” 文本文件,里面写好了:
哪些源文件要先编译
编译成什么目标文件 / 可执行文件
用什么命令、什么参数去编译
文件改了哪些才需要重新编译(增量编译)
make 这个工具会 自动读取 Makefile,根据规则和时间戳决定 要不要重新编译、链接,最终帮你 一键生成可执行文件或库。
Makefile = 把“gcc 源文件 -o 可执行文件”这类手敲命令,写成规则,让 make 帮你批量、增量、自动化地完成编
hide := @ # 把“@”符号存起来,以后放在命令前面就能**不打印这条命令本身**,make 输出更干净
ECHO := echo # 把 echo 命令也存成变量,后面想打印提示时统一用 $(ECHO)
# 告诉 make:以后用 $(G++) 就等价于调用这个交叉编译器(生成 ARM 可执行文件)
G++ := /opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++
# 下面一堆 -I 就是给编译器“小旗子”:后面跟的是头文件目录,让 #include<> 能找到 .h 文件
CFLAGS := -I./include/rkmedia \
-I./include/rkaiq/common \
-I./include/rkaiq/xcore \
-I./include/rkaiq/uAPI \
-I./include/rkaiq/algos \
-I./include/rkaiq/iq_parser \
-I./rknn_rockx_include \
-I./im2d_api \
-I./arm_libx264/include \
-I./arm32_ffmpeg_srt/include
# 告诉链接器:找库文件(.so/.a)时,先到 ./rv1126_lib 目录去翻
LIB_FILES := -L./rv1126_lib
# 真正要链接的第三方库名单:pthread、Rockchip MPP、FFmpeg 全家桶、ALSA、V4L2、RGA、RKAIQ 算法库等
LD_FLAGS := -lpthread -leasymedia -ldrm -lrockchip_mpp \
-lavformat -lavcodec -lswresample -lavutil \
-lasound -lv4l2 -lv4lconvert -lrga \
-lRKAP_ANR -lRKAP_Common -lRKAP_3A \
-lmd_share -lrkaiq -lod_share
# 在 CFLAGS 里再追加一个宏定义:源码里 #ifdef RKAIQ 会因此生效
CFLAGS += -DRKAIQ
# 默认目标:敲 make 或 make all 时执行
all:
# 真正干活的一行:用交叉编译器把 rv1126_vi_stream.cpp 编译+链接成可执行文件 rv1126_vi_stream
$(G++) rv1126_vi_stream.cpp $(CFLAGS) $(LIB_FILES) $(LD_FLAGS) -o rv1126_vi_stream
# 下面一行被注释掉:如果想编译备份文件,把行首 # 去掉即可
#$(G++) rv1126_vi_stream_bak.cpp $(CFLAGS) $(LIB_FILES) $(LD_FLAGS) -o rv1126_vi_stream_bak
# 编译结束后偷偷打印一行提示(前面加 @ 所以命令本身不会显示)
$(hide)$(ECHO) "Build Done ..."

在当前工作文件下:输入 make指令会对Makefile自动编译生成文件
三.使用SSH对RV1126进行编译

1.先将文件cp到tmp中,使用 chmod对程序权限修改
2.结果是并没有打印,但vi创建成功,主要原因---程序中并没有终端打印,
void * get_camera_vi_thread(void * args)
{
/*
在 RKMedia / Rockchip 的 IPC 框架里,
typedef void* MEDIA_BUFFER;
它只是一个指向内核/驱动层缓冲块的句柄,并非裸指针,更不能直接 memcpy。
该缓冲块里通常包含:
图像或音频裸数据地址(MB_GET_PTR(mb))
长度(MB_GET_SIZE(mb))
时间戳(MB_GET_TIMESTAMP(mb))
内存类型、缓存属性、偏移、文件描述符等元数据(驱动私有)
*/
MEDIA_BUFFER mb = NULL;
//打开接收文件
FILE * nv12_file = fopen("test_camera.nv12", "w+");
while (!quit)
{
mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VI, CHN_ID, -1);
if(!mb)
{
printf("get Vi mb break....\n");
break;
}
//将接受到数据写入刚才打开的文件中
fwrite(RK_MPI_MB_GetPtr(mb), 1, RK_MPI_MB_GetSize(mb), nv12_file);
//KMedia 框架里释放 MEDIA_BUFFER 的唯一官方接口,一句话:
RK_MPI_MB_ReleaseBuffer(mb);
}
}
直接看test_camera.nv12的文件内容

3.运行后 要尽快的退出程序,因为vi数据是特别大的
使用ls -lna *.nv12
ls- 列出目录内容
-l- 长格式(详细信息)
-n- 显示数字化的 UID/GID(而不是用户名)
-a- 显示所有文件(包括隐藏文件)
*.nv12- 过滤显示以.nv12结尾的文件- 你会看到每个
.nv12文件的详细信息:文件权限
链接数
所有者 UID(数字)
所属组 GID(数字)
文件大小
修改时间戳
文件名

可以发现这个文件夹是空的,说明打开或者生成一个名为test_camera.nv12文件,但是并没有完里面写入数据
4.nv12文件创建成功,但是并没有写入数据问题排查
使用 lsmod去检查RV1126已加载的模块,发现没有摄像头模块
[root@RV1126_RV1109:/tmp]# lsmod
Module Size Used by Tainted: G
qmi_wwan_q 24576 0
8188eu 1441792 0
rtc 16384 0
galcore 319488 0
由于固件整体编译时间比较久,前面并没有编译,使用的是压缩包里现成的文件进行烧录,暂时不知道是不是因为这个原因,决定重新编译固件一下。

5.ffmpeg软件对nv12文件进行播放
下面是对ffmpeg的介绍
资源:
在Windows界面下使用cmd进行启动
切换到bin目录后:输入 ffplay -f rawvideo -pix_fmt nv12 -video_size 1920x1080 test_camera.nv12
是用 ffplay(FFmpeg 的极简播放器)直接播放裸 YUV 视频数据文件
test_camera.nv12,而 不需要任何封装格式(如 .mp4/.avi)
参数 含义 ffplayFFmpeg 自带的命令行播放器,跨平台,零配置。 -f rawvideo告诉解码器:文件是 裸像素数据,没有头部、没有索引、没有封装。 -pix_fmt nv12指定像素格式为 NV12(YUV 4:2:0,bi-planar,常见于摄像头、编解码)。 -video_size 1920x1080告诉解码器 一帧的宽高,否则它不知道多少字节算一帧。 test_camera.nv12待播放的裸数据文件名,必须 > 0 字节,且总字节数 = 帧数 × 1920×1080×1.5(NV12 每像素 1.5 B)

1259

被折叠的 条评论
为什么被折叠?



