RK3588具备强大的Video Input功能。最大可支持
2 MIPI DCPHY + 4 MIPI CSI DPHY(2 lanes), totally support 6 cameras input
下面以操作MIPI DCPHY 0下挂一个GC8034 canmera 为例介绍如何进行camera编程。GC8034最大支持3264x2448像素,接线图如下
Linux下采用V4L2框架操作摄像头,这个框架的内容非常多。本文只介绍其应用编程部分。
"/dev/video*"的节点需要root权限才能执行读写操作,使用命令行或者C语言编译出来的程序操作camera都需要root权限。如果是ssh或者使用LX终端的,先执行如下命令获取root权限。
sudo su
板上的isp相关服务开机没有默认运行,而目前内核里面配置此摄像头的数据输入到isp,因此,要先运行如下命令,开启isp相关服务
/rockchip-test/camera/camera_rkaiq_test.sh
不管采用命令行还是C语言操作摄像头设备,首先都要获取其节点。linux下,一个camera通路(camera/isp/cif组合而成)在系统里面体现为media设备。板上有三个media设备,分别为/dev/media0 /dev/media1 /dev/media2,需要找到对应的media设备。执行如下命令
media-ctl -p -d /dev/media0
media-ctl -p -d /dev/media1
media-ctl -p -d /dev/media2
可以分别查看到三个media设备对应的通路。
找到rkisp_mainpath对应的entity,这个就是isp的的节点,如下图
可以看到, /dev/video22 即为要操作的节点
Linux下提供了v4l2-ctl命令来操作video节点。如果要抓取图像,可以执行如下命令
v4l2-ctl -d /dev/video22 --set-fmt-video=width=1632,height=1224,pixelformat=NV12 --stream-mmap --stream-skip=3 --stream-to=/tmp/isp.out --stream-count=10 --stream-poll
来抓取图像。
其中-d是指定节点
--set-fmt-video指定图像的长宽和格式。isp是具备缩小功能的。格式可以用NV12或者NV16,这个跟摄像头的数据格式无关,摄像头原始格式一般是RGB的,isp转换之后才是NV12或者NV16
--stream-mmap 指定buffer的类型为mmap
--stream-skip 丢弃前面的帧数,然后才开始抓图
--stream-to 指定输出文件
--stream-count 抓取的帧数
--stream-poll 采用poll方式抓取图像
执行上述命令之后,NV12格式的文件将被保存在/tmp/isp.out,使用adb或者ssh等方式放到PC端,在windows下,可以使用YUView这款工具来查看。
下面的demo介绍了如何从camera抓取一帧图像,并存储到一个文件中
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#define FMT_NUM_PLANES 1
struct frame_buffer {
void *start;
size_t length;
};
int main(int argc, char **argv)
{
int ret = 0;
enum v4l2_buf_type type;
int dev_fd = -1;
struct frame_buffer *buffers = NULL;
int file_fd = -1;
if (argc < 3) {
printf("v4l2_test dev_name save_file\n");
}
char *dev_name = argv[1]; // 摄像头设备名
char *save_file_name = argv[2]; // 存储文件名
file_fd = open(save_file_name, O_RDWR | O_CREAT , 0666); // 打开存储的文件
if(file_fd < 0) {
printf("cannot open %s \n",save_file_name);
return -1;
}
dev_fd = open(dev_name, O_RDWR | O_NONBLOCK, 0); // 打开设备
if(file_fd < 0) {
printf("cannot open %s \n",dev_name);
return -1;
}
// 获取摄像头参数
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));
ret = ioctl(dev_fd, VIDIOC_QUERYCAP, &cap);
if (ret < 0)
printf("failture VIDIOC_QUERYCAP\n");
else {
printf(" driver: %s\n", cap.driver);
printf(" card: %s\n", cap.card);
printf(" bus_info: %s\n", cap.bus_info);
printf(" version: %08X\n", cap.version);
printf(" capabilities: %08X\n", cap.capabilities);
}
// 设置视频设备的视频数据格式
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix.width = 1632;
fmt.fmt.pix.height = 1224;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; // V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV;
// fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(dev_fd, VIDIOC_S_FMT, &fmt); // 设置图像格式
if (ret < 0)
printf("VIDIOC_S_FMT failed\n");
// 申请一个内核缓冲区用于缓存一帧画面
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 1;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
req.memory = V4L2_MEMORY_MMAP;
ret = ioctl(dev_fd, VIDIOC_REQBUFS, &req); // 申请缓冲,count是申请的数量
if (ret < 0)
printf("VIDIOC_REQBUFS failed\n");
if (req.count < 1)
printf("Insufficient buffer memory\n");
buffers = calloc(req.count, sizeof(*buffers)); // 内存中建立对应空间
struct v4l2_buffer buf; // 驱动中的一帧
struct v4l2_plane planes[1];
for (unsigned int i = 0; i < req.count; ++i) {
memset(&buf, 0, sizeof(buf));
memset(&planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
// 如果是 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 类型
buf.m.planes = planes;
buf.length = FMT_NUM_PLANES;
if (-1 == ioctl(dev_fd, VIDIOC_QUERYBUF, &buf)) // 映射用户空间
printf("VIDIOC_QUERYBUF failed\n");
buffers[i].length = buf.m.planes[0].length;
buffers[i].start =
mmap(NULL /* start anywhere */, // 通过mmap建立映射关系
buf.m.planes[0].length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
dev_fd, buf.m.planes[0].m.mem_offset);
if (MAP_FAILED == buffers[i].start)
printf("mmap failed\n");
}
for (unsigned int i = 0; i < req.count; ++i) {
memset(&buf, 0, sizeof(buf));
memset(&planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
// 如果是 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 类型
buf.m.planes = planes;
buf.length = FMT_NUM_PLANES;
if (-1 == ioctl(dev_fd, VIDIOC_QBUF, &buf)) // 申请到的缓冲进入列队
printf("VIDIOC_QBUF failed\n");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (-1 == ioctl(dev_fd, VIDIOC_STREAMON, &type)) // 开始捕捉图像数据 此函数对应内核中的stream on
printf("VIDIOC_STREAMON failed\n");
// 此处的意思是弃掉前面几帧,buffers里面保留的是最后一帧数据
for (unsigned int i = 0; i < 3; ++i)
{
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds); // 将指定的文件描述符集清空
FD_SET(dev_fd, &fds); // 在文件描述符集合中增加一个新的文件描述符
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
// 判断是否可读(即摄像头是否准备好),tv是定时
r = select(dev_fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r) {
if (EINTR == errno)
continue;
printf("select failed\n");
}
if (0 == r) {
printf("select timeout\n");
exit(EXIT_FAILURE);
}
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
// 如果是 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 类型
buf.m.planes = planes;
buf.length = FMT_NUM_PLANES;
ret = ioctl(dev_fd, VIDIOC_DQBUF, &buf);
if (ret < 0)
printf("VIDIOC_DQBUF failed\n"); // 出列采集的帧缓冲
assert(buf.index < req.count);
printf("buf.index dq is %d,\n", buf.index);
ret = ioctl(dev_fd, VIDIOC_QBUF, &buf); // 再将其入列
if (ret < 0)
printf("failture VIDIOC_QBUF\n");
}
write(file_fd, buffers[buf.index].start, fmt.fmt.pix.sizeimage); // 将其写入文件中
fsync(file_fd);
unmap:
for (unsigned int i = 0; i < req.count; ++i)
if (-1 == munmap(buffers[i].start, buffers[i].length))
printf("munmap failed");
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (-1 == ioctl(dev_fd, VIDIOC_STREAMOFF, &type))
printf("VIDIOC_STREAMOFF failed");
close(dev_fd);
close(file_fd);
exit(EXIT_SUCCESS);
return 0;
}
将此源码保存为 v4l2_test.c 使用SDK中的交叉编译器执行编译
aarch64-none-linux-gnu-gcc v4l2_test.c -o v4l2_test
然后将这个 v4l2_test 放到板上执行
./v4l2_test /dev/video22 /tmp/isp.out
即可抓取一帧画面并保存到/tmp/isp.out中,将此文件放到pc端,使用YUView可以查看其内容