深入学习Linux摄像头系列

深入学习Linux摄像头(一)v4l2应用编程

深入学习Linux摄像头(二)v4l2驱动框架

深入学习Linux摄像头(三)虚拟摄像头驱动分析

深入学习Linux摄像头(四)三星平台fimc驱动详解

深入学习Linux摄像头(一)v4l2应用编程

文章目录

  • 深入学习Linux摄像头(一)v4l2应用编程
    • 一、什么是v4l2
    • 二、v4l2 API介绍
      • 2.1 Querying Capabilities
      • 2.2 Application Priority
      • 2.3 Device Inputs and Outputs
      • 2.4 Video Standards
      • 2.5 Camera Control Reference
      • 2.6 Image Format
      • 2.7 Cropping, composing and scaling
      • 2.8 Input/Output
    • 三、v4l2设备操作流程
      • step1:打开设备
      • step 2:查询设备功能
      • step 3:设置输入设备
      • step 4:设置图像格式
      • step 5:设置缓存
      • step 6:打开设备
      • step 7:读取数据
      • step 8:关闭设备
      • libv4l2
    • 四、v4l2采集图像在frame buffer显示
    • 五、v4l2采集图像使用Qt显示

一、什么是v4l2

vl42是video for Linux 2的缩写,是一套Linux内核视频设备的驱动框架,该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)

V4L2在设计时,是要支持很多广泛的设备的,它们之中只有一部分在本质上是真正的视频设备,可以支持多种设备,它可以有以下几种接口

video capture interface:视频采集接口,这种接口应用于摄像头,v4l2在最初设计的时候就是应用于这种功能

video output interface:视频输出接口,将静止图像或图像序列编码为模拟视频信号,通过此接口,应用程序可以控制编码过程并将图像从用户空间移动到驱动程序

video overlay interface:视频直接传输接口,可以将采集到的视频数据直接传输到显示设备,不需要cpu参与,这种方式的显示图像的效率比其他方式高得多

其他接口这里就不介绍了,下面来看一下v4l2的API

二、v4l2 API介绍

对V4L2设备进行编程包括以下步骤

  • 打开设备
  • 更改设备属性,选择视频和音频输入,视频标准,图片亮度等
  • 设置数据格式
  • 设置输入/输出方法
  • 输入/输出缓存队列循环
  • 关闭设备

其中大多数操作都是通过应用层调用ioctl实现的,可以将这些ioctl分为下面几类

2.1 Querying Capabilities

查询设备的功能

由于V4L2涵盖了各种各样的设备,因此并非API的所有方面都适用于所有类型的设备,在使用v4l2设备时,必须调用此API,获得设备支持的功能(capture、output、overlay…)

ID 描述
VIDIOC_QUERYCAP 查询设备功能

:可以点击名称查看API讲解

2.2 Application Priority

应用优先级

当多个应用程序共享设备时,可能需要为它们分配不同的优先级。视频录制应用程序可以例如阻止其他应用程序改变视频控制或切换当前的电视频道。另一个目标是允许在后台工作的低优先级应用程序,这些应用程序可以被用户控制的应用程序抢占,并在以后自动重新获得对设备的控制

ID 描述
VIDIOC_G_PRIORITY 获取优先级
VIDIOC_S_PRIORITY 设置优先级

2.3 Device Inputs and Outputs

输入和输出设备

ID 描述
VIDIOC_ENUMINPUT 枚举视频输入设备
VIDIOC_G_INPUT 获取当前的视频输入设备
VIDIOC_S_INPUT 设置视频输入设备
VIDIOC_ENUMOUTPUT 枚举视频输出设备
VIDIOC_G_OUTPUT 获取当前视频输出设备
VIDIOC_S_OUTPUT 设置视频输出设备
VIDIOC_ENUMAUDIO 枚举音频输入设备
VIDIOC_G_AUDIO 获取当前音频输入设备
VIDIOC_S_AUDIO 设置音频输入设备
VIDIOC_ENUMAUDOUT 枚举音频输出设备
VIDIOC_G_OUTPUT 获取音频输出设备
VIDIOC_S_AUDOUT 设置音频输出设备

2.4 Video Standards

视频标准

ID 描述
VIDIOC_ENUMSTD 枚举设备支持的所有标准
VIDIOC_G_STD 获取当前正在使用的标准
VIDIOC_S_STD 设置视频标准
VIDIOC_QUERYSTD 有的设备支持自动侦测输入源的视频标准,此ioctl获取检测到的标准

2.5 Camera Control Reference

控制属性

ID 描述
VIDIOC_QUERYCTRL 查询指定的control详细信息
VIDIOC_QUERYMENU 查询menu
VIDIOC_G_CTRL 获取设备指定control的当前信息
VIDIOC_S_CTRL 设置设备指定的control

2.6 Image Format

图像格式

图像由多种格式YUV和RGB还有压缩格式等等,其中每种格式又分有多种格式,比如RGB:RGB565、RGB888…

所以在使用设备时,需要对格式进行设置

ID 描述
VIDIOC_ENUM_FMT 枚举设备支持的图像格式
VIDIOC_G_FMT 获取当前设备的图像格式
VIDIOC_S_FMT 设置图像格式
VIDIOC_TRY_FMT 测试设备是否支持此格式

2.7 Cropping, composing and scaling

图像裁剪、插入与缩放

ID 描述
VIDIOC_CROPCAP 获取图像裁剪缩放能力
VIDIOC_G_CROP 获取当前的裁剪矩阵
VIDIOC_S_CROP 设置裁剪矩阵

2.8 Input/Output

数据的输入和输出

内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存,一种是将内核空间的缓存映射到用户空间。在操作v4l2设备时,通过VIDIOC_QUERYCAP获取设备支持哪种方式

ID 描述
VIDIOC_REQBUFS 申请缓存
VIDIOC_QUERYBUF 获取缓存信息
VIDIOC_QBUF 将缓存放入队列中
VIDIOC_DQBUF 将缓存从队列中取出

ioctl API就先介绍到这里,还有非常多的接口这里就不一一介绍了,具体可以查看V4L2 Function Reference

下面来讲一讲如何使用这些接口

三、v4l2设备操作流程

V4L2支持多种接口:capture(捕获)output(输出)overlay(预览)等等

这里讲解如何使用capture功能,下面讲解操作流程

step1:打开设备

在Linux中,视频设备节点为/dev/videox,使用open函数将其打开

int fd = open(name, flag);
if(fd < 0)
{printf("ERR(%s):failed to open %s\n", __func__, name);return -1;
}return fd;

step 2:查询设备功能

if (ioctl(fd, VIDIOC_QUERYCAP, cap) < 0)
{printf("ERR(%s):VIDIOC_QUERYCAP failed\n", __func__);return -1;
}

看一看v4l2_capability

struct v4l2_capability {__u8 driver[16]; /* i.e. "bttv" */__u8 card[32];   /* i.e. "Hauppauge WinTV" */__u8  bus_info[32];   /* "PCI:" + pci_name(pci_dev) */__u32   version;        /* should use KERNEL_VERSION() */__u32   capabilities;   /* Device capabilities */__u32  reserved[4];
};

其中最重要的是capabilities字段,这个字段标记着v4l2设备的功能,capabilities有以下部分标记位

ID 描述符
V4L2_CAP_VIDEO_CAPTURE 设备支持捕获功能
V4L2_CAP_VIDEO_OUTPUT 设备支持输出功能
V4L2_CAP_VIDEO_OVERLAY 设备支持预览功能
V4L2_CAP_STREAMING 设备支持流读写
V4L2_CAP_READWRITE 设备支持read、write方式读写

我们可以通过这样子去判断设备的功能


if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)printf("v4l2 dev support capture\n");if(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)printf("v4l2 dev support output\n");if(cap.capabilities & V4L2_CAP_VIDEO_OVERLAY)printf("v4l2 dev support overlay\n");if(cap.capabilities & V4L2_CAP_STREAMING)printf("v4l2 dev support streaming\n");if(cap.capabilities & V4L2_CAP_READWRITE)printf("v4l2 dev support read write\n");

step 3:设置输入设备

一个设备可能有多个输入,比如:在芯片上,摄像头控制器和摄像头接口是分离的,需要选择哪一个摄像头接口作为摄像头控制器的输入源

当然,并不是所有的设备都需要设置输入,比如:uvc摄像头,一般只有一个输入,默认就会选择,不需要设置

下面介绍如何设置输入设备

  • 1.枚举输入设备

    下面这段程序枚举了该设备所有的输入源,并打印输入源的名称

    struct v4l2_input input;input.index = 0;
    while (!ioctl(fd, VIDIOC_ENUMINPUT, &input))
    {printf("input:%s\n", input.name);++input.index;
    }
    
  • 2.设置输入设备

    struct v4l2_input input;input.index = index; //指定输入设备if (ioctl(fd, VIDIOC_S_INPUT, &input) < 0)
    {printf("ERR(%s):VIDIOC_S_INPUT failed\n", __func__);return -1;
    }
    

step 4:设置图像格式

有的摄像头支持多种像素格式,有的摄像头只支持一种像素格式,在设置格式之前,要先枚举出所有的格式,看一看是否支持要设置的格式,然后再进一步设置

  • 1.枚举支持的像素格式

    struct v4l2_fmtdesc fmtdesc;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmtdesc.index = 0;while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
    {printf("fmt:%s\n", fmtdesc.description);fmtdesc.index++;
    }
    
  • 2.设置像素格式

    struct v4l2_format v4l2_fmt;memset(&v4l2_fmt, 0, sizeof(struct v4l2_format));
    v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_fmt.fmt.pix.width = width; //宽度
    v4l2_fmt.fmt.pix.height = height; //高度
    v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //像素格式
    v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;if (ioctl(fd, VIDIOC_S_FMT, &v4l2_fmt) < 0)
    {printf("ERR(%s):VIDIOC_S_FMT failed\n", __func__);return -1;
    }
    

step 5:设置缓存

v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式,具体需要看step 2的返回结果是支持V4L2_CAP_READWRITE还是V4L2_CAP_STREAMING

read方式很容易理解,就是通过read函数读取,那么streaming是什么意思呢?

streaming就是在内核空间中维护一个缓存队列,然后将内存映射到用户空间,应用读取图像数据就是一个不断地出队列入队列的过程,如下图所示

下面讲解如何去申请和映射缓存

  • 1.申请缓存

    struct v4l2_requestbuffers req;req.count = nr_bufs; //缓存数量
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
    {printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);return -1;
    }
    
  • 2.映射缓存

    为什么要映射缓存?

    因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率

    映射缓存需要先查询缓存信息,然后再使用缓存信息进行映射,下面是一个例子

    struct v4l2_buffer v4l2_buffer;
    void* addr;memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
    v4l2_buffer.index = i; //想要查询的缓存
    v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buffer.memory = V4L2_MEMORY_MMAP;/* 查询缓存信息 */
    ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
    if(ret < 0)
    {printf("Unable to query buffer.\n");return -1;
    }/* 映射 */
    addr = mmap(NULL /* start anywhere */ ,v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,fd, v4l2_buffer.m.offset);
    

    :需要将所有申请的缓存使用上述方法进行映射

  • 3.将所有的缓存放入队列

    struct v4l2_buffer v4l2_buffer;for(i = 0; i < nr_bufs; i++)
    {memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));v4l2_buffer.index = i; //想要放入队列的缓存v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_buffer.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);if(ret < 0){printf("Unable to queue buffer.\n");return -1;}
    }
    

step 6:打开设备

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{printf("ERR(%s):VIDIOC_STREAMON failed\n", __func__);return -1;
}

step 7:读取数据

获取图像数据其实就是一个不断地入队列和出队列地过程,在出队列前要调用poll等待数据准备完成

  • 1.poll

    struct pollfd poll_fds[1];poll_fds[0].fd = fd;
    poll_fds[0].events = POLLIN; //等待可读poll(poll_fds, 1, 10000);
    
  • 2.出队列

    struct v4l2_buffer buffer;buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0)
    {printf("ERR(%s):VIDIOC_DQBUF failed, dropped frame\n", __func__);return -1;
    }
    

    出队列后得到了缓存的下标buffer.index,然后找到对饮的缓存,通过映射过后的地址进行数据的读取

  • 3.入队列

    再数据读取完成后,要将buf重新放入队列中

    struct v4l2_buffer v4l2_buf;v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buf.memory = V4L2_MEMORY_MMAP;
    v4l2_buf.index = i; //指定bufif (ioctl(fd, VIDIOC_QBUF, &v4l2_buf) < 0)
    {printf("ERR(%s):VIDIOC_QBUF failed\n", __func__);return -1;
    }
    

    读取数据就是在上面这三步一直不断地循环

step 8:关闭设备

  • 1.关闭设备

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
    {printf("ERR(%s):VIDIOC_STREAMOFF failed\n", __func__);return -1;
    }
    
  • 2.取消映射

    for(i = 0; i < nr_bufs; ++i)munmap(buf[i].addr, buf[i]->length);
    
  • 关闭文件描述符

    close(fd);
    

libv4l2

v4l2设备操作起来还是比较繁琐的,为此我对其进行了封装,写了一套库,使用起来更加方便,可以从这里libv4l2获取

其中附带一个实例example_cature,通过capture /dev/video0运行程序采集一张YUYV格式的图片,采集后得到了pic.yuv,可以通过ffplay查看ffplay -pixel_format yuyv422 -f rawvideo -video_size 640x480 pic.yuv,效果图如下

四、v4l2采集图像在frame buffer显示

如何将采集图像在frame buff上显示?

  • 1.转换图像格式,将yuv格式转换成frame buff可以接收的rgb格式
  • 2.操作frame buff,通过映射frame buff的显存到用户空间,直接写显存就可以显示图像

具体的实现过程这里就不详细说了,下面给出一个例子,点击这里example_video2lcd获取

执行make编译后可以得到video2lcd,执行video2lcd /dev/video0

运行效果如下

五、v4l2采集图像使用Qt显示

如何使用qt显示,道理跟在frame buff上显示是一样的,都是采集,转化格式,显示,只是在显示部分不同而已,这里给出一个例子,点击这里example_qt获取

运行效果如下

深入学习Linux摄像头(一)v4l2应用编程相关推荐

  1. 深入学习Linux摄像头(二)v4l2驱动框架

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  2. 深入学习Linux摄像头(四)三星平台fimc驱动详解

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  3. 深入学习Linux摄像头(三)虚拟摄像头驱动分析

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  4. 第13章代码《跟老男孩学习Linux运维:Shell编程实战》

    本书历史上已出版最实战的Shell高级编程实战书籍,没有之一,和市面书籍不同,本书是作者经过18年的运维工作及教学工作后,创新类企业级实战书籍,适合所有学习及从事Linux相关工作的读者. <跟 ...

  5. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  6. linux摄像头V4L2 subdev,linux 摄像头驱动 详解linux 摄像头驱动编写

    想了解详解linux 摄像头驱动编写的相关内容吗,feixiaoxing在本文为您仔细讲解linux 摄像头驱动的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:linux,摄像头驱动,下面 ...

  7. linux环境编程 学习,学习linux环境高级编程首先学习的是文件的操作。因为有.pdf...

    学习linux环境高级编程首先学习的是文件的操作.因为有 学习 Linux 环境高级编程,首先学习的是文件的操作.因为有一句很有趣的话"Linux 下一切皆文件".所以掌握了文件操 ...

  8. Linux与C++11多线程编程(学习笔记)

    多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...

  9. linux编程学习_您需要编程技能才能学习Linux吗?

    linux编程学习 几个月前,我参加了edX提供的Linux入门课程. 这是一门18章的课程,其中包含大量阅读材料,一些视频以及随意测试知识水平的课程. 我写了关于前六章的内容,以及该课程的工作原理, ...

最新文章

  1. python内置函数sorted(x)的作用是_Python内置filter与sorted函数
  2. vba 指定列后插入列_如何用【VBA】快速批量提取多个工作表名称?
  3. appium+python自动化40-adb offline(5037端口被占)
  4. springboot学习笔记(七)
  5. mybatis generator逆向工程使用
  6. java创建变量的过程_java 对象的创建过程
  7. 【Python CheckiO 题解】Right to Left
  8. 拓端tecdat|R语言马尔可夫转换模型研究交通伤亡人数事故预测
  9. numpy.squeeze()的用法
  10. 游戏和数学笔记—常拿起来看看
  11. pyinstaller系列之十一:exe 反编译到 源码 尝试
  12. 刀片服务器接显示器,认识刀片服务器
  13. 线性混合模型(Linear Mixed Models)与R语言 lmer() 函数
  14. 百度在线语音合成API接口简单应用
  15. echarts xAxis字显示不全
  16. android media player实现一个可手势滑动控制 + 可以调节分辨率|字幕|倍速的视频播放器(MediaPlayer + ExoPlayer实现)
  17. JS实现视频录制-以Cesium为例
  18. JS学习笔记 - Extends
  19. 痴情不是罪过 忘情不是洒脱
  20. 我的勇者服务器维护3月5日,时空猎人3月5日更新维护时间和更新活动内容

热门文章

  1. 让打工人颤抖的监控系统:离职倾向、摸鱼通通都能被监测!你还敢上班摸鱼吗?...
  2. Git - 时光机穿梭
  3. RAID独立磁盘冗余阵列,对比各模式下的性能优劣
  4. 团队任务3每日立会(2018-10-23)
  5. 程序员的职业病,一定要注重身体健康才是最重要的
  6. 加装固态,重装系统(双系统)
  7. 信息武器化——认知安全的必要性
  8. 2021-04-06人事老李走了
  9. 究竟哪些语句是属于DDL?
  10. 20款免费项目管理系统推荐