c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <poll.h>
int main(int argc, char **argv)
{
int ret = 0;
int fd;
struct v4l2_fmtdesc fmtdesc; // 该变量是用来与v4l2设备节点交互,从而获取支持的格式的变量
struct v4l2_frmsizeenum fsenum; // 该变量是指定了format的index后,从而继续遍历获取当前v4l2设备支持的大小格式
int i = 0;
void *bufs[32]; // 定义了一个32个大小的数组,每个元素都是一个void*,也就是任意类型的指针
if (argc != 2)
{
printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[1]);
return -1;
}
fmtdesc.index = 0; // 该变量即当输出又当输入,所以指定从0开始,它别的成员变量会被内核中的驱动修改
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 制定获取的format的type为捕获,即视频输入
while (1)
{
if (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
{
break;
}
fsenum.pixel_format = fmtdesc.pixelformat; // 指定当前要遍历哪个format的size
fsenum.index = 0; // 每次进format的循环前,都默认从第一个size开始获取
while (1)
{
if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum))
{
break;
}
printf("format %s,%d framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, fsenum.index, fsenum.discrete.width, fsenum.discrete.height);
fsenum.index++;
}
fmtdesc.index++;
}
//===============设置需要的图像格式=================
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(struct v4l2_format));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1024;
fmt.fmt.pix.height = 720;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_ANY; // 扫描方式
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == 0)
{
// 驱动会修改fmt,读取fmt则可获取被驱动正确设置的格式数据
printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
}
else
{
printf("can not set format\n");
return -1;
}
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
rb.count = 32; // 表示app想申请32个buffer
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 用于视频输入的buf
rb.memory = V4L2_MEMORY_MMAP; // 表示buffer是由内核驱动申请的,然后后续会通过mmap方式映射到用户空间
if (ioctl(fd, VIDIOC_REQBUFS, &rb) == 0)
{
// 同样的,这个结构体也会被驱动修改,会返回成功申请到的buffer的信息,不一定和app发送给它的完全一致,尤其是数量可能有限制
for (i = 0; i < rb.count; i++)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == 0)
{
// 查询申请的第index个buff的信息
// 将这些buff逐个映射到用户空间
// 第一个参数是指定虚拟地址,0则是交给内核自由选择
bufs[i] = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
if (bufs[i] == MAP_FAILED)
{
perror("Unable to map buffer");
return -1;
}
}
else
{
printf("can not query buffer\n");
return -1;
}
}
}
else
{
printf("can not request buffers\n");
return -1;
}
for (i = 0; i < rb.count; i++)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (0 == ioctl(fd, VIDIOC_QBUF, &buf))
{
printf("queue buffer%d ok\n" buf.index);
}
else
{
perror("Unable to queue buffer");
return -1;
}
}
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) != 0)
{
perror("Unable to start capture");
return -1;
}
printf("start capture ok\n");
struct pollfd fds[1];
int file_cnt = 0;
while (1)
{
memset(fds, 0, sizeof(fds));
fds[0].fd = fd;
fds[0].events = POLLIN; // 有数据输入
if (poll(fds, 1, -1)) // poll 1个,时间无限
{
/* 把buffer取出队列 */
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buf) != 0)
{
perror("Unable to dequeue buffer");
return -1;
}
char filename[32];
sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
int fd_file = open(filename, O_RDWR | O_CREAT, 0666);
if (fd_file < 0)
{
printf("can not create file : %s\n", filename);
}
printf("capature to %s\n", filename);
write(fd_file, bufs[buf.index], buf.bytesused); // 将buf中真正用了的大小写到文件里面
close(fd_file);
// 记得将buffer放回空闲队列
if (ioctl(fd, VIDIOC_QBUF, &buf) != 0)
{
perror("Unable to queue buffer");
return -1;
}
}
}
if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type))
{
perror("Unable to stop capture");
return -1;
}
printf("stop capture ok\n");
close(fd);
return 0;
}
步骤如下:
1.打开v4l2注册的设备节点,即videox,剩下的所有和设备节点的操作都是通过ioctl这个驱动暴露出来的接口和用户APP交互的 ioctl带进去的结构体有的也会被内核中的驱动修改
2.使用ioctl传递v4l2_fmtdesc结构体和VIDIOC_ENUM_FMT命令查询当前video节点支持的图像格式,然后使用v4l2_frmsizeenum结构体和VIDIOC_ENUM_FRAMESIZES枚举该格式支持的分辨率大小
3.使用ioctl传递v4l2_format结构体和VIDIOC_S_FMT命令设置要使用的格式与分辨率
4.使用v4l2_requestbuffers结构体和VIDIOC_REQBUFS命令,申请缓冲区,缓冲区一般使用内核申请,然后映射到用户空间的方式
5.使用v4l2_buffer结构体和VIDIOC_QUERYBUF命令轮询所有申请好的buffer,然后将buffer映射到用户空间,可以使用一个指针数组来存储
6.再次使用4l2_buffer结构体,通过index轮询的方式和VIDIOC_QBUF命令将buffer加入到空闲队列里面去
7.使用VIDIOC_STREAMON和指定的type(捕获),开启video设备的捕获
8.使用poll机制和v4l2_buffer结构体,等poll等来数据的时候,就出队一个buffer,然后使用该buffer的index去映射好的用户空间里面读取对应的数据,读取的长度为bytesused
9.读取完数据后,要将用完的buffer再次放入空闲队列中
10.使用VIDIOC_STREAMOFF命令和type关闭video设备的捕获