音视频之V4L2的应用

V4L2是Video for Linux2的简称,是Linux操作系统中支持实时视频采集的设备驱动和一系列API的集合。V4L2驱动会在文件系统中创建/dev/video*设备节点供视频采集操作使用。

一、V4L2的用法

首先通过open()函数打开设备节点获取文件描述符,使用ioctl()函数查询设备状态、查询/设置参数、获取视频等。

二、常见命令如下

  • VIDIOC_REQBUFS:分配内存
  • VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
  • VIDIOC_QUERYCAP:查询支持的能力
  • VIDIOC_ENUM_FMT:获取支持的视频格式
  • VIDIOC_S_FMT:设置频捕获格式
  • VIDIOC_G_FMT:读取频捕获格式
  • VIDIOC_TRY_FMT:验证当前显示格式
  • VIDIOC_CROPCAP:查询驱动的裁剪能力
  • VIDIOC_S_CROP:设置视频信号的边框
  • VIDIOC_G_CROP:读取视频信号的边框
  • VIDIOC_QBUF:把数据放回缓存队列
  • VIDIOC_DQBUF:把数据从缓存中读取出来
  • VIDIOC_STREAMON:开始视频显示
  • VIDIOC_STREAMOFF:结束视频显示
  • VIDIOC_QUERYSTD:当前视频设备支持的标准,例如PAL或NTSC

三、示例:抓取一帧图像

c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <linux/videodev2.h>

int video_camera_capture_test(char *dev_name)
{
    int fd = 0;
    void *start = NULL;
    FILE *mjpeg_fp = NULL;
    enum v4l2_buf_type type;
    struct v4l2_buffer buf;
    struct v4l2_capability cap;
    struct v4l2_format fmt;
	struct v4l2_requestbuffers req;

    fd = open(dev_name, O_RDWR|O_NONBLOCK);
    if (fd < 0) {
        printf("open dev[%s] failed\n", dev_name);
        return -1;
    }

    if (ioctl(fd, VIDIOC_QUERYCAP, &cap)<0) {
        printf("Not a v4lv2 driver.\n");
        return -1;
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        printf("%s is not a video capture device\n", VIDEO_DEV);
        return -1;
    }
    
    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        printf("%s does not support streaming i/o\n", VIDEO_DEV);
        return -1;
    }

    printf("Driver is %s, version is %i\n", cap.driver, cap.version);

    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
    fmt.fmt.pix.width = 1080;
    fmt.fmt.pix.height = 720;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    if(ioctl(fd, VIDIOC_S_FMT, &fmt) != 0) {
        printf("VIDIOC_S_FMT failed: %s. Read-only driver maybe ?\n",
                strerror(errno));
    } else {
        printf("VIDIOC_S_FMT success\n");
    }

	memset(&req,0,sizeof(req));
	req.count = 1;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory = V4L2_MEMORY_MMAP;

	if (ioctl(fd, VIDIOC_REQBUFS, &req)<0) {
		printf("Error requesting info on mmap'd buffers: %s\n",
		        strerror(errno));
		return -1;
	} else {
        printf("VIDIOC_REQBUFS success\n");
    }

    memset(&buf,0,sizeof(buf));

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = 0;

    if (ioctl(fd, VIDIOC_QUERYBUF, &buf)<0){
        printf("Could not VIDIOC_QUERYBUF : %s\n",strerror(errno));
        return -1;
    }

    start = mmap(NULL /* start anywhere */,
        buf.length,
        PROT_READ | PROT_WRITE /* required */,
        MAP_SHARED /* recommended */,
        fd, buf.m.offset);

    printf("buf length:%d\n", buf.length);

    if (start==NULL){
        printf("Could not mmap: %s\n",strerror(errno));
        return -1;
    }

	memset(&buf, 0, sizeof(buf));
	buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory      = V4L2_MEMORY_MMAP;
	buf.index       = 0;
	if (-1 == ioctl (fd, VIDIOC_QBUF, &buf)){
		printf("VIDIOC_QBUF failed: %s\n", strerror(errno));
		return -1;
	}

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)){
        printf("VIDIOC_STREAMON failed: %s\n",strerror(errno));
        return -1;
    }

    memset(&buf,0,sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    int times = 10;
    while (times--) {
        if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
            switch (errno) {
                case EAGAIN:
                    usleep(20000);
                    break;
                default: 
                    printf("VIDIOC_DQBUF failed: %s\n",strerror(errno));
            }
        } else {
            printf("get picture\n");
            mjpeg_fp = fopen("./output.jpeg", "w");
            fwrite(start, buf.bytesused, 1, mjpeg_fp);
            fclose(mjpeg_fp);
            break;
        }
    }

    return 0;
}

int main(int argc, char *argv[])
{
    video_camera_capture_test("/dev/video0");

	return 0;
}

测试结果:

bash 复制代码
Driver is uvcvideo, version is 267201
VIDIOC_S_FMT success
VIDIOC_REQBUFS success
buf length:1572864
get picture
相关推荐
安大小万10 分钟前
C++ 学习:深入理解 Linux 系统中的冯诺依曼架构
linux·开发语言·c++
九品神元师26 分钟前
jupyter配置说明
linux·ide·jupyter
黯然~销魂1 小时前
root用户Linux银河麒麟服务器安装vnc服务
linux·运维·服务器
菠萝炒饭pineapple-boss2 小时前
Dockerfile另一种使用普通用户启动的方式
linux·docker·dockerfile
Zfox_3 小时前
【Linux】进程间关系与守护进程
linux·运维·服务器·c++
laimaxgg3 小时前
Linux关于华为云开放端口号后连接失败问题解决
linux·运维·服务器·网络·tcp/ip·华为云
浪小满3 小时前
linux下使用脚本实现对进程的内存占用自动化监测
linux·运维·自动化·内存占用情况监测
东软吴彦祖3 小时前
包安装利用 LNMP 实现 phpMyAdmin 的负载均衡并利用Redis实现会话保持nginx
linux·redis·mysql·nginx·缓存·负载均衡
艾杰Hydra4 小时前
LInux配置PXE 服务器
linux·运维·服务器
慵懒的猫mi4 小时前
deepin分享-Linux & Windows 双系统时间不一致解决方案
linux·运维·windows·mysql·deepin