音视频之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
相关推荐
且从容.3 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道3 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
希和20276 小时前
免费AI视频生成工具汇总(国内优先,适合做漫剧、短视频,Obsidian笔记版)
人工智能·笔记·音视频
且从容.6 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落8 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨8 小时前
WSL2的安装与使用
linux·wsl
微小的鱼8 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng9 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
謓泽9 小时前
【插件】用写网页的方式做视频?HyperFrames 让我重新理解了“做视频“这件事!
gpt·ai·大模型·音视频·媒体·claude·视频
Android系统攻城狮9 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频