V4L2应用程序开发
V4L2介绍
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少,
本机摄像头编码格式的获取
arm-buildroot-linux-gnueabihf-gcc -o videotest video_test.c交叉编译后再开发板上运行获取
或者gcc -o videotest1 video_test.c编译后直接在ubantu下运行获取
#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>
/* ./video_test </dev/video0> */
int main(int argc, char **argv)
{
int fd;
/*struct v4l2_fmtdesc fmtdesc;: 这个结构体用于描述视频格式。
它包含了与视频格式相关的信息,比如格式标识符、格式描述等。
*/
struct v4l2_fmtdesc fmtdesc;
/*这个结构体用于描述视频帧的大小。它包含了与视频帧尺寸相关的信息,比如宽度、高度、帧尺寸等。
*/
struct v4l2_frmsizeenum fsenum;
int fmt_index = 0;
int frame_index = 0;
if (argc != 2)
{
printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
return -1;
}
/* open */
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[1]);
return -1;
}
while (1)
{
/* 枚举格式 */
fmtdesc.index = fmt_index; // 比如从0开始
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获"
if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
break;
frame_index = 0;
while (1)
{
/* 枚举这种格式所支持的帧大小 */
memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
fsenum.pixel_format = fmtdesc.pixelformat;
fsenum.index = frame_index;
if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
{
printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
}
else
{
break;
}
frame_index++;
}
fmt_index++;
}
return 0;
}
获取如下所示
YUV 4:2:2是一种常见的视频像素格式,也称为YUV422。这种格式使用Y、U、V三个分量来表示每个像素的颜色信息,其中Y表示亮度(Luma),U和V表示色度(Chrominance)。数字4:2:2表示对色度分量(U和V)的抽样比例,其中每4个Y像素共享一对UV像素。这种格式在视频处理和传输中被广泛使用,因为它能够提供较高的图像质量和较小的数据量。
Motion-JPEG(M-JPEG)是一种视频编码格式,它将每一帧视频都作为单独的JPEG图像进行编码。每一帧都是独立的JPEG图像,这使得Motion-JPEG格式的视频非常容易进行编辑和处理,因为每一帧都可以单独解码。然而,由于每一帧都是独立的JPEG图像,Motion-JPEG通常会产生较大的文件大小,因此在网络传输和存储方面可能不太高效。这种格式在某些应用场景下仍然很流行,特别是在需要快速编辑和处理视频的情况下。
用V4L2获取图片数据的流程
#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 <poll.h>
#include <sys/mman.h>
/* ./video_test </dev/video0> */
int main(int argc, char **argv)
{
int fd;
struct v4l2_fmtdesc fmtdesc;
struct v4l2_frmsizeenum fsenum;
int fmt_index = 0;
int frame_index = 0;
int i;
void *bufs[32];
int buf_cnt;
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
struct pollfd fds[1];
char filename[32];
int file_cnt = 0;
if (argc != 2)
{
printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
return -1;
}
/* open */
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[1]);
return -1;
}
/* 查询能力 */
struct v4l2_capability cap;
memset(&cap, 0, sizeof(struct v4l2_capability));
if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
{
if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
fprintf(stderr, "Error opening device %s: video capture not supported.\n",
argv[1]);
return -1;
}
if(!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf(stderr, "%s does not support streaming i/o\n", argv[1]);
return -1;
}
}
else
{
printf("can not get capability\n");
return -1;
}
while (1)
{
/* 枚举格式 */
fmtdesc.index = fmt_index; // 比如从0开始
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获"
if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
break;
frame_index = 0;
while (1)
{
/* 枚举这种格式所支持的帧大小 */
memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
fsenum.pixel_format = fmtdesc.pixelformat;
fsenum.index = frame_index;
if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
{
printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
}
else
{
break;
}
frame_index++;
}
fmt_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 = 768;
//枚举得到支持的编码格式后选择合适的编码格式V4L2_PIX_FMT_MJPEG
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//V4L2_PIX_FMT_YUYV;设置像素格式为 YUYV 4:2:2
//fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (0 == ioctl(fd, VIDIOC_S_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;
}
/*
* 申请buffer
*/
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
rb.count = 32;
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb))// 申请视频缓冲区
{
/* 申请成功后, mmap这些buffer */
buf_cnt = rb.count;
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;
//VIDIOC_QUERYBUF ioctl 调用,以获取缓冲区的信息,然后使用mmap将缓冲区映射到用户空间。
if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
{
/* mmap */
bufs[i] = mmap(0 /* start anywhere */ ,
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;
}
}
printf("map %d buffers ok\n", buf_cnt);
}
else
{
printf("can not request buffers\n");
return -1;
}
/* 把所有buffer放入"空闲链表" */
for(i = 0; i < buf_cnt; ++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;
//执行VIDIOC_QBUF调用将缓冲区加入到视频采集队列中。
if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
{
perror("Unable to queue buffer");
return -1;
}
}
printf("queue buffers ok\n");
/* 启动摄像头 */
if (0 != ioctl(fd, VIDIOC_STREAMON, &type))
{
perror("Unable to start capture");
return -1;
}
printf("start capture ok\n");
while (1)
{
/* poll
使用poll()函数来监听文件描述符数组fds中的第一个文件描述符
该文件描述符是视频设备文件描述符fd。第二个参数1表示监听的文件描述符数量为1。
第三个参数-1表示poll()函数将一直阻塞,直到有事件发生。*/
memset(fds, 0, sizeof(fds));
fds[0].fd = fd;
fds[0].events = POLLIN;
if (1 == poll(fds, 1, -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 (0 != ioctl(fd, VIDIOC_DQBUF, &buf))
{
perror("Unable to dequeue buffer");
return -1;
}
/* 把buffer的数据存为文件 */
sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
//0666 是文件权限,表示文件所有者、所属组和其他用户都有读写权限。
int fd_file = open(filename, O_RDWR | O_CREAT, 0666);
if (fd_file < 0)
{
printf("can not create file : %s\n", filename);
}
printf("capture to %s\n", filename);
write(fd_file, bufs[buf.index], buf.bytesused);
close(fd_file);
/* 把buffer放入队列 */
if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
{
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;
}
poll的用法
poll()函数是一个用于监听文件描述符上事件的系统调用,其原型如下:
c
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:一个指向struct pollfd结构体数组的指针,每个结构体描述了一个待监听的文件描述符及其关注的事件。
nfds:数组中结构体的数量。
timeout:超时时间,以毫秒为单位。如果设置为负值(例如-1),则poll()将一直阻塞,直到有事件发生;如果设置为0,则poll()将立即返回,不会阻塞;如果设置为正值,则poll()将等待指定的毫秒数后返回,即使没有事件发生。
struct pollfd结构体定义如下:
c
struct pollfd {
int fd; // 待监听的文件描述符
short events; // 关注的事件(如POLLIN、POLLOUT等)
short revents; // 实际发生的事件(由系统填充)
};
events和revents字段是位掩码,可以使用以下宏定义:
POLLIN:表示有数据可读。
POLLOUT:表示可以写入数据。
POLLERR:表示发生错误。
POLLHUP:表示挂起连接。
POLLNVAL:表示文件描述符不合法。
poll()函数会检查fds数组中每个文件描述符指定的事件,然后返回发生事件的文件描述符数量。在函数返回后,通过检查revents字段来确定哪些事件实际发生了。
加上控制摄像头亮度的代码
#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 <poll.h>
#include <sys/mman.h>
#include <pthread.h>
/* ./video_test </dev/video0> */
static void *thread_brightness_control (void *args)
{
int fd = (intptr_t)args;
unsigned char c;
int brightness;
int delta;
struct v4l2_queryctrl qctrl;
memset(&qctrl, 0, sizeof(qctrl));
qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
if (0 != ioctl(fd, VIDIOC_QUERYCTRL, &qctrl))
{
printf("can not query brightness\n");
return NULL;
}
printf("brightness min = %d, max = %d\n", qctrl.minimum, qctrl.maximum);
delta = (qctrl.maximum - qctrl.minimum) / 10;
struct v4l2_control ctl;
ctl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
ioctl(fd, VIDIOC_G_CTRL, &ctl);
while (1)
{
c = getchar();
if (c == 'u' || c == 'U')
{
ctl.value += delta;
}
else if (c == 'd' || c == 'D')
{
ctl.value -= delta;
}
if (ctl.value > qctrl.maximum)
ctl.value = qctrl.maximum;
if (ctl.value < qctrl.minimum)
ctl.value = qctrl.minimum;
ioctl(fd, VIDIOC_S_CTRL, &ctl);
}
return NULL;
}
int main(int argc, char **argv)
{
int fd;
struct v4l2_fmtdesc fmtdesc;
struct v4l2_frmsizeenum fsenum;
int fmt_index = 0;
int frame_index = 0;
int i;
void *bufs[32];
int buf_cnt;
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
struct pollfd fds[1];
char filename[32];
int file_cnt = 0;
if (argc != 2)
{
printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
return -1;
}
/* open */
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[1]);
return -1;
}
/* 查询能力 */
struct v4l2_capability cap;
memset(&cap, 0, sizeof(struct v4l2_capability));
if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
{
if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
fprintf(stderr, "Error opening device %s: video capture not supported.\n",
argv[1]);
return -1;
}
if(!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf(stderr, "%s does not support streaming i/o\n", argv[1]);
return -1;
}
}
else
{
printf("can not get capability\n");
return -1;
}
while (1)
{
/* 枚举格式 */
fmtdesc.index = fmt_index; // 比如从0开始
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获"
if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
break;
frame_index = 0;
while (1)
{
/* 枚举这种格式所支持的帧大小 */
memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
fsenum.pixel_format = fmtdesc.pixelformat;
fsenum.index = frame_index;
if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
{
printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
}
else
{
break;
}
frame_index++;
}
fmt_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 = 768;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (0 == ioctl(fd, VIDIOC_S_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;
}
/*
* 申请buffer
*/
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
rb.count = 32;
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb))
{
/* 申请成功后, mmap这些buffer */
buf_cnt = rb.count;
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_QUERYBUF, &buf))//查询视频缓冲区的信息,并将结果存储在buf结构体中
{
/* mmap */
bufs[i] = mmap(0 /* start anywhere */ ,
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;
}
}
printf("map %d buffers ok\n", buf_cnt);
}
else
{
printf("can not request buffers\n");
return -1;
}
/* 把所有buffer放入"空闲链表" */
for(i = 0; i < buf_cnt; ++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))
{
perror("Unable to queue buffer");
return -1;
}
}
printf("queue buffers ok\n");
/* 启动摄像头 */
if (0 != ioctl(fd, VIDIOC_STREAMON, &type))
{
perror("Unable to start capture");
return -1;
}
printf("start capture ok\n");
/* 创建线程用来控制亮度 */
pthread_t thread;
pthread_create(&thread, NULL, thread_brightness_control, (void *)fd);
while (1)
{
/* poll */
memset(fds, 0, sizeof(fds));
fds[0].fd = fd;
fds[0].events = POLLIN;
if (1 == poll(fds, 1, -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 (0 != ioctl(fd, VIDIOC_DQBUF, &buf))
{
perror("Unable to dequeue buffer");
return -1;
}
/* 把buffer的数据存为文件 */
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("capture to %s\n", filename);
write(fd_file, bufs[buf.index], buf.bytesused);
close(fd_file);
/* 把buffer放入队列 */
if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
{
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;
}