目录
1. 打开设备
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 1. 打开设备
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("failed open");
return -1;
}
// 2. 关闭设备
close(fd);
return 0;
}
返回一个文件描述符。
2. 获取/配置设备支持格式
2.1 主要函数
要实现功能,最重要的是以下这个函数:
int ioctl(int fd, unsinged long request, ...);
// 即ioctl(文件描述符,操作命令,与命令的对应的结构体)
// 查看方式man ioctl
2.2 unsinged long request
ioctl函数参数的查看方式:
- https://v4l.videotechnology.com/dwg/v4l2.html
/usr/include/linux/videodev2.h
关键宏 | 功能 | 对应的结构体 |
---|---|---|
VIDIOC_QUERYCAP | 视频设备支持的功能查询 | struct v4l2_capability(传出参数) |
VIDIOC_ENUM_FMT | 查看usb摄像头支持的图片格式 | struct v4l2_fmtdesc (传入传出参数) |
VIDIOC_G_FMT | 获取图片格式(图像高宽、像素格式等) | struct v4l2_format(传入传出参数) |
VIDIOC_S_FMT | 设置图像格式 | struct v4l2_format(传入传出参数) |
对应结构体具体内容
struct v4l2_capability {
__u8 driver[16]; /* i.e. "bttv" */
__u8 card[32]; /* i.e. "Hauppauge WinTV" */
__u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
__u32 version; /* should use KERNEL_VERSION() */
__u32 capabilities; /* Device capabilities */
__u32 reserved[4];
};
struct v4l2_fmtdesc {
__u32 index; /* Format number 设置好index*/
__u32 type; /* enum v4l2_buf_type, 设置好类型 */
__u32 flags;
__u8 description[32]; /* Description string */
__u32 pixelformat; /* Format fourcc */
__u32 reserved[4];
};
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
V4L2_BUF_TYPE_VBI_CAPTURE = 4,
V4L2_BUF_TYPE_VBI_OUTPUT = 5,
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,
V4L2_BUF_TYPE_SDR_CAPTURE = 11,
V4L2_BUF_TYPE_SDR_OUTPUT = 12,
V4L2_BUF_TYPE_META_CAPTURE = 13,
V4L2_BUF_TYPE_META_OUTPUT = 14,
/* Deprecated, do not use */
V4L2_BUF_TYPE_PRIVATE = 0x80,
};
struct v4l2_format {
__u32 type; // 设置好类型
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
2.3 代码具体实现
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/videodev2.h>
int v4l2_check_cap(int fd) {
struct v4l2_capability cap = {};
if (-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap)) {
printf("this device unable to query capability.");
return -1;
}
// 查询是否是视频设备
if (0 == cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
printf("this device does not support capture.\n");
return -1;
}
// 查询是否支持IO数据流
if (0 == cap.capabilities & V4L2_CAP_STREAMING) {
printf("this device does not support I/O stream.\n");
return -1;
}
return 0;
}
void v4l2_enum_fmt(int fd) {
struct v4l2_fmtdesc fmtd = {};
fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtd.index = 0; // 取第几个格式
while (0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtd)) {
printf("pixel format: %s \n", fmtd.description);
fmtd.index ++;
}
}
int v4l2_get_format(int fd) {
struct v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_G_FMT, &fmt)) {
printf("cannot get pixel format");
return -1;
}
printf("format: %u x %u, pixeformat:%c%c%c%c\n",
fmt.fmt.pix.width, fmt.fmt.pix.height,
fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
}
int v4l2_set_format(int fd) {
struct v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 320;
fmt.fmt.pix.height = 240;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
if (-1 == ioctl(fd, VIDIOC_S_FMT, &fmt)) {
printf("cannot get pixel format");
return -1;
}
return 0;
}
int main () {
int fd, rval;
fd = open("/dev/video0", O_RDWR);
if (fd == -1) {
perror("cannot open device.");
exit(1);
}
// 1. 视频设备支持的功能查询
rval = v4l2_check_cap(fd);
if (rval == -1) {
close(fd);
return rval;
}
// 2. 查看usb摄像头支持的图片格式
v4l2_enum_fmt(fd);
// 3. 获取、设置摄像头的图像格式
// 获取图片格式
rval = v4l2_get_format(fd);
if (rval < 0) {
close(fd);
return rval;
}
// 设置图像格式
rval = v4l2_set_format(fd);
if (rval < 0) {
close(fd);
return rval;
}
v4l2_get_format(fd);
close(fd);
return 0;
}
3. 申请内核缓冲区队列
内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存(多次读写速度慢),一种是将内核空间的缓存映射到用户空间。
关键宏 | 功能 | 对应的结构体 |
---|---|---|
VIDIOC_REQBUFS | Initiate Memory Mapping or User Pointer I/O | struct v4l2_requestbuffers(传入参数) |
struct v4l2_requestbuffers {
__u32 count; // 设置缓冲区的数量
__u32 type; /* enum v4l2_buf_type */ // 设置类型
__u32 memory; /* enum v4l2_memory */ //设置映射方式
__u32 capabilities;
__u32 reserved[1];
};
int v4l2_request_kernel_buffer(int fd) {
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; //申请4个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP ;//映射方式
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
perror("申请队列空间失败");
return -1;
}
return 0;
}
4. 映射队列空间到用户空间
先前配置了四个缓冲区,依次查询出一个缓冲区进行映射。
内存映射
用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。
mmap函数是unix/linux下的系统调用,mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后,内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你所写的内容就能立即保存到文件里了.这点应该和驱动相关。 不过通过mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.如果想取消内存映射,可以调用munmap()来取消内存映射。
扫描二维码关注公众号,回复: 14996398 查看本文章
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
-
start:要映射到的内存区域的起始地址,通常都是用NULL(NULL即为0)。NULL表示由内核来指定该内存地址
-
length:要映射的内存区域的大小
-
prot:内存映射区的权限。期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
-
flags:指定映射对象的类型,内存上的修改是否反映到磁盘上。映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体。
MAP_SHARED :对映射区域的写入数据会复制回文件内, 而且允许其他映射该文件的进程共享。
MAP_PRIVATE:映射区做的修改不会反映到物理设备。
-
fd:文件描述符(由open函数返回)
-
offset:映射文件的偏移(4k的整数倍, 4096字节,linux一页的大小),表示被映射对象(即文件)从那里开始对映,通常都是用0。 该值应该为大小为PAGE_SIZE的整数倍
返回说明:成功执行时,mmap()返回被映射区的首地址。失败时,mmap()返回MAP_FAILED[其值为(void *)-1]
买一送一:munmap(void* addr, size_t length)
关闭释放映射区
- addr:mmap返回值对应的指针
- length:与mmap的length对应
mmap函数的应用:父子进程通信;匿名映射;无血缘关系进程间通信
映射实现
关键宏 | 功能 | 对应的结构体 |
---|---|---|
VIDIOC_QUERYBUF | Query the status of a buffer | struct v4l2_buffer(传入传出) |
VIDIOC_QBUF | Exchange a buffer with the driver | struct v4l2_buffer(传入) |
struct v4l2_buffer {
__u32 index; // 设置好索引
__u32 type; // 设置好类型
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;
union {
__u32 offset;
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length;
__u32 reserved2;
union {
__s32 request_fd;
__u32 reserved;
};
};
unsigned char *mptr[4];//保存映射后用户空间的首地址,之后通过该指针取数据即可
unsigned int size[4];
struct v4l2_buffer mapbuffer;
//初始化type, index
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for(int i=0; i<4; i++)
{
mapbuffer.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//从内核空间中查询一个空间做映射
if(ret < 0)
{
perror("查询内核空间队列失败");
}
mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, mapbuffer.m.offset);
size[i]=mapbuffer.length;
//通知使用完毕--‘放回去’
ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
if(ret < 0)
{
perror("放回失败");
}
}
5. 数据采集
此部分包含开始采集、采集数据、停止采集
关键宏 | 功能 | 对应的结构体 |
---|---|---|
VIDIOC_STREAMON | 开始采集,写数据到队列 | const int * |
VIDIOC_DQBUF | 告诉内核我要某一数据,内核不可以修改 | struct v4l2_buffer |
VIDIOC_QBUF | 告诉内核我已经使用完毕 | struct v4l2_buffer |
VIDIOC_STREAMOFF | 停止采集,不再向队列中写数据 | const int * |
// 开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("开启失败");
}
//从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
if(ret < 0)
{
perror("提取数据失败");
}
FILE *file=fopen("my.jpg", "w+");
fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
fclose(file);
//通知内核已经使用完毕
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if(ret < 0)
{
perror("放回队列失败");
}
// 停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
6. 释放映射
for(int i=0; i<4; i++)
munmap(mptr[i], size[i]);
7. 关闭设备
close(fd);