音视频之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
相关推荐
wowocpp10 分钟前
ubuntu 22.04 硬件配置 查看 显卡
linux·运维·ubuntu
山河君22 分钟前
ubuntu使用DeepSpeech进行语音识别(包含交叉编译)
linux·ubuntu·语音识别
鹏大师运维26 分钟前
【功能介绍】信创终端系统上各WPS版本的授权差异
linux·wps·授权·麒麟·国产操作系统·1024程序员节·统信uos
筱源源29 分钟前
Elasticsearch-linux环境部署
linux·elasticsearch
pk_xz1234562 小时前
Shell 脚本中变量和字符串的入门介绍
linux·运维·服务器
小珑也要变强2 小时前
Linux之sed命令详解
linux·运维·服务器
Lary_Rock4 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
云飞云共享云桌面6 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq7 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮8 小时前
Linux 使用中的问题
linux·运维