v4l2

介绍

v4l2 是 用户 从设备获取 音视频数据的方法。
用户通过 v4l2 可以知道 设备支持 哪些操作 和 支持 哪些数据格式。
通常使用 内存映射,传输数据。
用户预先建立多个内存映射,将空内存输队,等待kernel将数据填充到空内存块,装有数据的内存块出队,用户操作数据。

操作逻辑

  1. 设置设备数据格式等参数
  2. 申请内存块,并建立和应用空间的内存映射
  3. 开始捕获,并入队 内存块
  4. 出队内存块
  5. 处理数据
  6. 入队无用内存块

示例

V4L2_BUF_TYPE_VIDEO_CAPTURE

V4L2_BUF_TYPE_VIDEO_CAPTURE : 一个内存块存储一帧数据的捕获模式
代码出处:https://www.jianshu.com/p/0ac427d267d4

/*v4l2_example.c*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <sys/time.h>

#define TRUE            (1)
#define FALSE           (0)

#define FILE_VIDEO      "/dev/video0"
#define IMAGE           "./img/demo"

#define IMAGEWIDTH      640
#define IMAGEHEIGHT     480

#define FRAME_NUM       4

int fd;
struct v4l2_buffer buf;

struct buffer
{
    void * start;
    unsigned int length;
    long long int timestamp;
} *buffers;

int v4l2_init()
{
    struct v4l2_capability cap;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_format fmt;
    struct v4l2_streamparm stream_para;

    //打开摄像头设备
    if ((fd = open(FILE_VIDEO, O_RDWR)) == -1) 
    {
        printf("Error opening V4L interface\n");
        return FALSE;
    }

    //查询设备属性
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) 
    {
        printf("Error opening device %s: unable to query device.\n",FILE_VIDEO);
        return FALSE;
    }
    else
    {
        printf("driver:\t\t%s\n",cap.driver);
        printf("card:\t\t%s\n",cap.card);
        printf("bus_info:\t%s\n",cap.bus_info);
        printf("version:\t%d\n",cap.version);
        printf("capabilities:\t%x\n",cap.capabilities);
        
        if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) 
        {
            printf("Device %s: supports capture.\n",FILE_VIDEO);
        }

        if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) 
        {
            printf("Device %s: supports streaming.\n",FILE_VIDEO);
        }
    }


    //显示所有支持帧格式
    fmtdesc.index=0;
    fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("Support format:\n");
    while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
    {
        printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
        fmtdesc.index++;
    }

    //检查是否支持某帧格式
    struct v4l2_format fmt_test;
    fmt_test.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt_test.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32;
    if(ioctl(fd,VIDIOC_TRY_FMT,&fmt_test)==-1)
    {
        printf("not support format RGB32!\n");      
    }
    else
    {
        printf("support format RGB32\n");
    }


    //查看及设置当前格式
    printf("set fmt...\n");
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; //jpg格式
    //fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//yuv格式

    fmt.fmt.pix.height = IMAGEHEIGHT;
    fmt.fmt.pix.width = IMAGEWIDTH;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    printf("fmt.type:\t\t%d\n",fmt.type);
    printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
    printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
    printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
    printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
    if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
    {
        printf("Unable to set format\n");
        return FALSE;
    }

    printf("get fmt...\n"); 
    if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)
    {
        printf("Unable to get format\n");
        return FALSE;
    }
    {
        printf("fmt.type:\t\t%d\n",fmt.type);
        printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
        printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
        printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
        printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
    }

    //设置及查看帧速率,这里只能是30帧,就是1秒采集30张图
    memset(&stream_para, 0, sizeof(struct v4l2_streamparm));
    stream_para.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
    stream_para.parm.capture.timeperframe.denominator = 30;
    stream_para.parm.capture.timeperframe.numerator = 1;

    if(ioctl(fd, VIDIOC_S_PARM, &stream_para) == -1)
    {
        printf("Unable to set frame rate\n");
        return FALSE;
    }
    if(ioctl(fd, VIDIOC_G_PARM, &stream_para) == -1)
    {
        printf("Unable to get frame rate\n");
        return FALSE;       
    }
    {
        printf("numerator:%d\ndenominator:%d\n",stream_para.parm.capture.timeperframe.numerator,stream_para.parm.capture.timeperframe.denominator);
    }
    return TRUE;
}

int v4l2_mem_ops()
{
    unsigned int n_buffers;
    struct v4l2_requestbuffers req;
    
    //申请帧缓冲
    req.count=FRAME_NUM;
    req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory=V4L2_MEMORY_MMAP;
    if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1)
    {
        printf("request for buffers error\n");
        return FALSE;
    }

    // 申请用户空间的地址列
    buffers = malloc(req.count*sizeof (*buffers));
    if (!buffers) 
    {
        printf ("out of memory!\n");
        return FALSE;
    }
    
    // 进行内存映射
    for (n_buffers = 0; n_buffers < FRAME_NUM; n_buffers++) 
    {
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;
        //查询
        if (ioctl (fd, VIDIOC_QUERYBUF, &buf) == -1)
        {
            printf("query buffer error\n");
            return FALSE;
        }

        //映射
        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffers[n_buffers].start == MAP_FAILED)
        {
            printf("buffer map error\n");
            return FALSE;
        }
    }
    return TRUE;    
}

int v4l2_frame_process()
{
    unsigned int n_buffers;
    enum v4l2_buf_type type;
    char file_name[100];
    char index_str[10];
    long long int extra_time = 0;
    long long int cur_time = 0;
    long long int last_time = 0;

    //入队和开启采集
    for (n_buffers = 0; n_buffers < FRAME_NUM; n_buffers++)
    {
        buf.index = n_buffers;
        ioctl(fd, VIDIOC_QBUF, &buf);
    }
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMON, &type);

    //出队,处理,写入yuv文件,入队,循环进行
    int loop = 0;
    while(loop < 15)
    {
        for(n_buffers = 0; n_buffers < FRAME_NUM; n_buffers++)
        {
            //出队
            buf.index = n_buffers;
            ioctl(fd, VIDIOC_DQBUF, &buf);

            //查看采集数据的时间戳之差,单位为微妙
            buffers[n_buffers].timestamp = buf.timestamp.tv_sec*1000000+buf.timestamp.tv_usec;
            cur_time = buffers[n_buffers].timestamp;
            extra_time = cur_time - last_time;
            last_time = cur_time;
            printf("time_deta:%lld\n\n",extra_time);
            printf("buf_len:%d\n",buffers[n_buffers].length);

            //处理数据只是简单写入文件,名字以loop的次数和帧缓冲数目有关
            printf("grab image data OK\n");
            memset(file_name,0,sizeof(file_name));
            memset(index_str,0,sizeof(index_str));
            sprintf(index_str,"%d",loop*4+n_buffers);
            strcpy(file_name,IMAGE);
            strcat(file_name,index_str);
            strcat(file_name,".jpg");
            //strcat(file_name,".yuv");
            FILE *fp2 = fopen(file_name, "wb");
            if(!fp2)
            {
                printf("open %s error\n",file_name);
                return(FALSE);
            }
            fwrite(buffers[n_buffers].start, IMAGEHEIGHT*IMAGEWIDTH*2,1,fp2);
            fclose(fp2);
            printf("save %s OK\n",file_name);

            //入队循环
            ioctl(fd, VIDIOC_QBUF, &buf);       
        }

        loop++;
    }
    return TRUE;    
}

int v4l2_release()
{
    unsigned int n_buffers;
    enum v4l2_buf_type type;

    //关闭流
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMON, &type);
    
    //关闭内存映射
    for(n_buffers=0;n_buffers<FRAME_NUM;n_buffers++)
    {
        munmap(buffers[n_buffers].start,buffers[n_buffers].length);
    }
    
    //释放自己申请的内存
    free(buffers);
    
    //关闭设备
    close(fd);
    return TRUE;
}

/*int v4l2_video_input_output()
{
    struct v4l2_input input;
    struct v4l2_standard standard;

    //首先获得当前输入的index,注意只是index,要获得具体的信息,就的调用列举操作
    memset (&input,0,sizeof(input));
    if (-1 == ioctl (fd, VIDIOC_G_INPUT, &input.index)) {
        printf("VIDIOC_G_INPUT\n");
        return FALSE;
    }
    //调用列举操作,获得 input.index 对应的输入的具体信息
    if (-1 == ioctl (fd, VIDIOC_ENUMINPUT, &input)) {
        printf("VIDIOC_ENUM_INPUT \n");
        return FALSE;
    }
    printf("Current input %s supports:\n", input.name);


    //列举所有的所支持的 standard,如果 standard.id 与当前 input 的 input.std 有共同的
    //bit flag,意味着当前的输入支持这个 standard,这样将所有驱动所支持的 standard 列举一个
    //遍,就可以找到该输入所支持的所有 standard 了。
    memset(&standard,0,sizeof (standard));
    standard.index = 0;
    while(0 == ioctl(fd, VIDIOC_ENUMSTD, &standard)) {
        if (standard.id & input.std){
            printf ("%s\n", standard.name);
        }
        standard.index++;
    }
    // EINVAL indicates the end of the enumeration, which cannot be empty unless this device falls under the USB exception. 

    if (errno != EINVAL || standard.index == 0) {
        printf("VIDIOC_ENUMSTD\n");
        return FALSE;
    }

}*/

int main(int argc, char const *argv[])
{
    printf("begin....\n");
    sleep(10);

    v4l2_init();
    printf("init....\n");
    sleep(10);

    v4l2_mem_ops();
    printf("malloc....\n");
    sleep(10);

    v4l2_frame_process();
    printf("process....\n");
    sleep(10);

    v4l2_release();
    printf("release\n");
    sleep(20);
    
    return TRUE;
}

V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE

V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : 多个内存块存储一帧数据

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <assert.h>

#include "display.h"


#define TRUE            (1)
#define FALSE           (0)

#define FILE_VIDEO      "/dev/video0"
#define IMAGE           "./img/demo"

#define IMAGEWIDTH      1080 
#define IMAGEHEIGHT     720 

#define FRAME_NUM       4

#define CLEAR(item) memset(item, 0x0, sizeof(*(item)))

int fd;

#define FMT_NUM_PLANES  1
struct v4l2_requestbuffers reqbuf;
/* Our current format uses 3 planes per buffer */

struct {
	void *start[FMT_NUM_PLANES];
	size_t length[FMT_NUM_PLANES];
} *buffers;

struct v4l2_buffer buffer;

int v4l2_init()
{
    struct v4l2_capability cap;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_format fmt;

    if ((fd = open(FILE_VIDEO, O_RDWR)) == -1) 
    {
        printf("Error opening V4L interface\n");
        return FALSE;
    }

    // 查看设备支持的功能
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) 
    {
        printf("Error opening device %s: unable to query device.\n",FILE_VIDEO);
        return FALSE;
    }
    else
    {
        printf("driver:\t\t%s\n",cap.driver);
        printf("card:\t\t%s\n",cap.card);
        printf("bus_info:\t%s\n",cap.bus_info);
        printf("version:\t%d\n",cap.version);
        printf("capabilities:\t%x\n",cap.capabilities);
        
        if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) 
        {
            printf("Device %s: supports capture.\n",FILE_VIDEO);
        }

        if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) == V4L2_CAP_VIDEO_CAPTURE_MPLANE) 
        {
            printf("Device %s: supports mplane capture.\n",FILE_VIDEO);
        }

        if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) 
        {
            printf("Device %s: supports streaming.\n",FILE_VIDEO);
        }
    }

    // 枚举支持的所有图层格式
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    printf("Support format:\n");
    while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
    {
        printf("\t%d.%s,%d\n",fmtdesc.index+1,fmtdesc.description, 
				fmtdesc.flags);
        fmtdesc.index++;
    }

	// 显示当前的图层格式
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    printf("get fmt...\n"); 
    if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
        printf("Unable to get format\n");
		perror("VIDIOC_G_FMT");
        return FALSE;
    }
	else {
        printf("fmt.type:\t\t%d\n",fmt.type);
        printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix_mp.pixelformat & 0xFF, (fmt.fmt.pix_mp.pixelformat >> 8) & 0xFF,(fmt.fmt.pix_mp.pixelformat >> 16) & 0xFF, (fmt.fmt.pix_mp.pixelformat >> 24) & 0xFF);
        printf("pix.height:\t\t%d\n",fmt.fmt.pix_mp.height);
        printf("pix.width:\t\t%d\n",fmt.fmt.pix_mp.width);
        printf("pix.field:\t\t%d\n",fmt.fmt.pix_mp.field);
    }
	fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420;
	fmt.fmt.pix_mp.width = IMAGEWIDTH;
	fmt.fmt.pix_mp.height = IMAGEHEIGHT;
    if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
    {
        printf("Unable to set format\n");
        return FALSE;
    }

    return TRUE;
}

int v4l2_mem_ops()
{
	unsigned int i, j;

	memset(&reqbuf, 0, sizeof(reqbuf));
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	reqbuf.memory = V4L2_MEMORY_MMAP;
	reqbuf.count = 4;

	if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
		if (errno == EINVAL)
			printf("Video capturing or mmap-streaming is not supported\\n");
		else
			perror("VIDIOC_REQBUFS");

		exit(EXIT_FAILURE);
	}

	/* We want at least five buffers. */

	if (reqbuf.count < 2) {
		/* You may need to free the buffers here. */
		printf("Not enough buffer memory\\n");
		exit(EXIT_FAILURE);
	}

	buffers = calloc(reqbuf.count, sizeof(*buffers));
	assert(buffers != NULL);

	for (i = 0; i < reqbuf.count; i++) {
		struct v4l2_plane planes[FMT_NUM_PLANES];

		memset(&buffer, 0, sizeof(buffer));
		buffer.type = reqbuf.type;
		buffer.memory = V4L2_MEMORY_MMAP;
		buffer.index = i;
		/* length in struct v4l2_buffer in multi-planar API stores the size
		 * of planes array. */
		buffer.length = FMT_NUM_PLANES;
		buffer.m.planes = planes;

		if (ioctl(fd, VIDIOC_QUERYBUF, &buffer) < 0) {
			perror("VIDIOC_QUERYBUF");
			exit(EXIT_FAILURE);
		}

		/* Every plane has to be mapped separately */
		for (j = 0; j < FMT_NUM_PLANES; j++) {
			buffers[i].length[j] = buffer.m.planes[j].length; /* remember for munmap() */

			buffers[i].start[j] = mmap(NULL, buffer.m.planes[j].length,
					 PROT_READ | PROT_WRITE, /* recommended */
					 MAP_SHARED,             /* recommended */
					 fd, buffer.m.planes[j].m.mem_offset);

			if (MAP_FAILED == buffers[i].start[j]) {
				/* If you do not exit here you should unmap() and free()
				   the buffers and planes mapped so far. */
				perror("mmap");
				exit(EXIT_FAILURE);
			}
		}
	}

		printf("Success to test mem\n");
		return EXIT_FAILURE;

}

int v4l2_frame_process()
{
    unsigned int n_buffers;
    enum v4l2_buf_type type;
    char file_name[100];
    char index_str[10];
    long long int extra_time = 0;
    long long int cur_time = 0;
    long long int last_time = 0;

	printf("count = : %d\n", reqbuf.count);
    //入队和开启采集
	int i;
	for (i = 0; i < reqbuf.count; i++) {
		buffer.index = i;
		if (ioctl(fd, VIDIOC_QBUF, &buffer) < 0) {
			printf("failed enqueue buffer\n");
			perror("VIDIOC_QBUF");
			return EXIT_FAILURE;
		}
	}
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
		printf("failed start stream\n");
		perror("VIDIOC_STREAMON");
		return EXIT_FAILURE;
	}
	printf("Success to start stream\n");
    
	display_t dp = {0};

	if (display_init(&dp, IMAGEWIDTH, 
				IMAGEHEIGHT, SDL_IYUV_OVERLAY) < 0) {
		printf("Failed to init display\n");
		return -1;	
	}

    //出队,处理,写入yuv文件,入队,循环进行
    int loop = 3;
    while(1)
    {
        for(i = 0; i < reqbuf.count; i++)
        {
            //出队
            buffer.index = i;
			if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0) {
				printf("failed to dequeue\n");
				perror("ioctl");
				return EXIT_FAILURE;
			}
            
            //处理数据只是简单写入文件,名字以loop的次数和帧缓冲数目有关
            printf("grab image data OK\n");
            memset(file_name, 0, sizeof(file_name));
            memset(index_str, 0, sizeof(index_str));
            sprintf(index_str, "%d", loop * 4 + i);
            strcpy(file_name, IMAGE);
            strcat(file_name, index_str);
            strcat(file_name,".yuv");
            FILE *fp2 = fopen(file_name, "wb");
            if(!fp2)
            {
                printf("open %s error\n",file_name);
                return EXIT_FAILURE;
            }

	    int j;
		for (j = 0; j < FMT_NUM_PLANES; j++) {
			fwrite(buffers[i].start[j], buffers[i].length[j],1,fp2);
		}
            fclose(fp2);
            printf("save %s OK\n",file_name);

            //入队循环
            ioctl(fd, VIDIOC_QBUF, &buffer); 
        }

    }
    return TRUE;    
}

int v4l2_release()
{
    enum v4l2_buf_type type;

    //关闭流
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	ioctl(fd, VIDIOC_STREAMOFF, &type);
    
	int i, j;
	/* Cleanup. */
	for (i = 0; i < reqbuf.count; i++)
		for (j = 0; j < FMT_NUM_PLANES; j++)
			munmap(buffers[i].start[j], buffers[i].length[j]);

    //关闭设备
    close(fd);
    return TRUE;
}

int main(int argc, char const *argv[])
{
    printf("begin....\n");
    sleep(1);

    v4l2_init();
    printf("init....\n");
    sleep(1);

    v4l2_mem_ops();
    printf("malloc....\n");
    sleep(1);

    v4l2_frame_process();
    printf("process....\n");
    sleep(1);

    v4l2_release();
    printf("release\n");
    
    return TRUE;
}

资料推荐

http://100ask.net/pages/cfba84/#_7-2-v4l2视频采集原理
https://www.kernel.org/doc/html/v4.10/media/kapi/v4l2-intro.html

posted on 2022-02-15 17:06  开心种树  阅读(1013)  评论(0编辑  收藏  举报