USB摄像头拍照Demo
本章节将讲解如何D1-H上使用一个USB摄像头拍摄一张照片。
D1-H哪吒开发板上有一个USB Host接口(即电脑上那种插鼠标键盘的USB口),同时D1-H Tina Linux支持UVC(USB Video Class,USB视频类),这样D1-H就具备了开发和使用USB摄像头的软硬件条件。
前期准备
硬件准备
- USB免驱摄像头一个,标准USB摄像头均可,淘宝直接搜“USB摄像头”搜出来排名靠前的随便买一个就行,本文中调试用到的是一个海康威视的摄像头,零售价格大概数十元。
- 哪吒开发板一块
软件准备
- 在运行本例之前,请确保你的Hello Word 的过程没有出现问题。即交叉编译工具和ADB工具都可正常使用或已经添加进环境变量。
- 下文将详细介绍demo的源码写的内容。
硬件连接
主要连接串口调试,USB连接电脑可以用来传输数据和供电,USB摄像头连接到开发板的USB接口。
连接好的系统就是下图的样子:
如果此时你的开发板是开机的话,终端会打印USB摄像头连接的Log。如下图:
从图中可以看到,摄像头为 HIK 720P 的摄像头,同时摄像头挂在 USB1总线、为 input3 设备。
此时我们查看/dev
外设目录,可以发现有/dev/video0
和 /dev/video1
设备,video1 为 video0的映射。
到此,我们的哪吒开发板已经成功连接上了USB摄像头,下一步是写程序来使用它。
程序获取
在编写程序之前,我们需要了解一下Linux中摄像头的接口标准。在LInux系统中,摄像头之所以能被识别离不开我们的系统对摄像头的驱动支持。
Video4Linux2(Video for Linux Two, 简称V4L2)是Linux中关于视频设备的驱动框架,为上层访问底层的视频设备提供统一接口。V4L2主要支持三类设备:视频输入输出设备、VBI设备和Radio设备,分别会在/dev目录下产生videoX、vbiX和radioX设备节点,其中X是0,1,2等的数字。如USB摄像头是我们常见的视频输入设备。
Linux 中强大的第三方库如:FFmpeg和OpenCV对V4L2均支持。
本例就使用V4L2库完成摄像头对图片的捕捉,并将其保存为一张图片。
依照Tina SKD开发架构,我们的代码创建在prckage目录下,我们新建文件夹camerademo
在文件夹中新建test.c
,或将提供资源中的代码包中的test.c
拷贝至此处。
cd d1-tina-open/package/test
mkdir camerademo
cd camerdemo
touch test.c
程序编译
程序编译采用最简单的命令行编译程序:
riscv64-unknown-linux-gnu-gcc test.c -o test
编译后,我们当前目录就会生成可执行文件test
文件烧录及传输
我们在windows中使用ADB工具将其送入开发板中:
adb push test ./.
程序运行
文件传输成功后,我们从开发板中运行。
运行之前首先要赋予文件可执行权限,然后再运行。
chmod +x test
./test
运行截图:
此时程序会打印保存成功的Log。我们使用Ctrl+C
终止程序运行后,可在当前文件夹看到有1.jpeg
图片生成,我们将他拿出来查看。
依然使用ADB
adb pull /root/1.jpeg .
运行截图:
打开1.jpeg可以看到刚才拍摄到的图片:
如果可以成果看到拍摄的图片,那么恭喜你,你已经给D1-H哪吒添上了一双眼睛,以后你就可以用这双眼睛,去探索这个有趣的世界了!
进阶:程序代码注释及讲解
开头说过我们Linux使用的是V4L2框架获取的摄像头数据。该框架的使用流程如下:
- 打开设备
- 初始化设备
- 注册内存映射I/O
- 开始捕捉
- 停止捕捉
- 关闭设备
根据如上大纲,我们分布讲解。
1. 打开设备
打开设备使用C标准接口open
函数,返回文件(设备)描述符。打开文件的方式可以选择**可读可写方式(O_RDWR)和无阻塞方式(O_NONBLOCK)**打开
#define CAM_DEV "/dev/video0"
int cam_fd;
if((cam_fd = open(CAM_DEV,O_RDWR)) == -1)
{
perror("ERROR opening V4L interface.");
return -1;
}
2. 初始化设备
初始化设备调用init_device
函数。
(1) 调用ioctl函数,对设备的I/O通道进行管理,查询设备能力,并将结果保存在结构体v4l2_capability中,结构体v4l2_capability内容如下:
struct v4l2_capability {
__u8 driver[16];
__u8 card[32];
__u8 bus_info[32];
__u32 version;
__u32 capabilities;
__u32 device_caps;
__u32 reserved[3];
};
if(ioctl(cam_fd,VIDIOC_QUERYCAP,&cam_cap) == -1)
{
perror("Error opening device %s: unable to query device.");
return -1;
}
(2) 判断是否视频捕获设备V4L2_CAP_VIDEO_CAPTURE
if((cam_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0)
{
perror("ERROR video capture not supported.");
return -1;
}
(3) 调用ioctl函数,设置流数据格式,包括宽、高、像素格式,并将结果保存在结构体v4l2_format中,结构体v4l2_format
struct v4l2_pix_format {
__u32 width;
__u32 height;
__u32 pixelformat;
__u32 field;
__u32 bytesperline;
__u32 sizeimage;
__u32 colorspace;
__u32 priv;
};
struct v4l2_format {
__u32 type;
union {
struct v4l2_pix_format pix;
struct v4l2_pix_format_mplane pix_mp;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
__u8 raw_data[200];
} fmt;
};
主要设置长宽及出输出格式。
struct v4l2_format v4l2_fmt;
v4l2_fmt.type = V4L2_CAP_VIDEO_CAPTURE;
v4l2_fmt.fmt.pix.width = WIDTH;
v4l2_fmt.fmt.pix.height = HEIGHT;
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
if (ioctl (cam_fd, VIDIOC_S_FMT, &v4l2_fmt) == -1)
{
perror("ERROR camera VIDIOC_S_FMT Failed.");
return -1;
}
3. 注册内存映射I/O
因为io采用的是MMAP即内存映射方式,因此调用init_mmap函数:
(1) 调用ioctl函数,设置内存映射I/O,并将结果保存在结构体v4l2_requestbuffers中,结构体v4l2_requestbuffers内容如下:
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1,
V4L2_MEMORY_USERPTR = 2,
V4L2_MEMORY_OVERLAY = 3,
V4L2_MEMORY_DMABUF = 4,
};
struct v4l2_requestbuffers {
__u32 count;
__u32 type;
__u32 memory;
__u32 reserved[2];
};
struct v4l2_requestbuffers v4l2_req;
v4l2_req.count = NB_BUFFER;
v4l2_req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_req.memory = V4L2_MEMORY_MMAP;
if (ioctl (cam_fd, VIDIOC_REQBUFS, &v4l2_req) == -1)
{
perror("ERROR camera VIDIOC_REQBUFS Failed.");
return -1;
}
(2) 调用ioctl函数,查询缓冲区状态,并将结果保存在结构体v4l2_buffer中.
(3) 调用mmap函数,应用程序通过内存映射将帧缓冲区地址映射到用户空间;通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能。
struct v4l2_buffer v4l2_buf;
v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
for(i = 0; i < NB_BUFFER; i++)
{
v4l2_buf.index = i;
if(ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0)
{
perror("Unable to query buffer.");
return -1;
}
pic.tmpbuffer[i] = (unsigned char*)mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, cam_fd, v4l2_buf.m.offset);
if(pic.tmpbuffer[i] == MAP_FAILED)
{
perror("Unable to map buffer.");
return -1;
}
if(ioctl(cam_fd, VIDIOC_QBUF, &v4l2_buf) < 0)
{
perror("Unable to queue buffer.");
return -1;
}
}
4. 开始捕捉
调用ioctl函数,VIDIOC_QBUF,并将结果保存在结构体v4l2_buffer中;
struct v4l2_buffer buff;
buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buff.memory = V4L2_MEMORY_MMAP;
if(ioctl(cam_fd, VIDIOC_DQBUF, &buff) < 0)
{
printf("camera VIDIOC_DQBUF Failed.\n");
usleep(1000*1000);
return -1;
}
5. 停止捕捉
调用munmap函数,取消映射设备内存;
for(i=0; i<NB_BUFFER; i++)
munmap(pic[0].tmpbuffer[i],pic[0].tmpbytesused[i]);
6. 关闭设备
调用close函数关闭设备。
close(cam_fd);