使用Linux内核自带的V4L2设备驱动 采集图像

news2024/12/23 14:38:30

一、定义

V4L2代表Video for Linux Two,它是Linux内核的一部分,提供了一种统一的方式来访问各种视频输入/输出设备,如摄像头、电视卡等。

二、工作流程(重点)

打开设备-> 检查和设置设备属性-> 设置帧格式-> 设置一种输入输出方法(缓冲 区管理)-> 循环获取数据-> 关闭设备。

1. 首先是打开摄像头设备;

2. 查询设备的属性或功能;

3. 设置设备的参数,譬如像素格式、 帧大小、 帧率;

4. 申请帧缓冲、 内存映射;

5. 帧缓冲入队;

6. 开启视频采集;

7. 帧缓冲出队、对采集的数据进行处理;

8. 处理完后,再次将帧缓冲入队,往复;

9. 结束采集

三、相关接口

1.设备的打开和关闭

#include <fcntl.h>
int open(const char *device_name, int flags);
#include <unistd.h>
int close(int fd);

int fd=open(“/dev/video0”,O_RDWR); // 打开设备

close(fd); // 关闭设备

注意:V4L2 的相关定义包含在头文件<linux/videodev2.h> 中.

2.查询设备属性

VIDIOC_QUERYCAP  这个控制命令用来查询设备的属性

ioctl()函数是一个通用的I/O控制函数,在Linux中用于与设备进行通信。

ioctl()对于设备文件来说是一个非常重要的系统调用, 凡是涉及到配置设备、获取设备配置等操作都会使用 ioctl 来完成;但对于普通文件来说, ioctl()几乎没什么用。

int ioctl(int fd, unsigned long request, ... /* variable argument list */ );
V4L2 指令描述
VIDIOC_QUERYCAP查询设备的属性/能力/功能
VIDIOC_ENUM_FMT枚举设备支持的像素格式
VIDIOC_G_FMT获取设备当前的帧格式信息
VIDIOC_S_FMT设置帧格式信息
VIDIOC_REQBUFS申请帧缓冲
VIDIOC_QUERYBUF查询帧缓冲
VIDIOC_QBUF帧缓冲入队操作
VIDIOC_DQBUF帧缓冲出队操作
VIDIOC_STREAMON开启视频采集
VIDIOC_STREAMOFF关闭视频采集
VIDIOC_G_PARM获取设备的一些参数
VIDIOC_S_PARM设置参数
VIDIOC_TRY_FMT尝试设置帧格式、用于判断设备是否支持该格式
VIDIOC_ENUM_FRAMESIZES枚举设备支持的视频采集分辨率
VIDIOC_ENUM_FRAMEINTERVALS枚举设备支持的视频采集帧


 例如

int ioctl(int fd, int request, struct v4l2_capability *argp);

fd是设备文件描述符,request是请求号,argp是一个指向结构体的指针,用于传递额外的参数。在这个例子中,request是VIDIOC_QUERYCAP,表示查询设备属性。

argp是一个指向struct v4l2_capability结构体的指针,用于存储设备能力的相关信息。

struct v4l2_capability

{

u8 driver[16]; // 驱动名字

u8 card[32]; // 设备名字

u8 bus_info[32]; // 设备在系统中的位置

u32 version; // 驱动版本号

u32 capabilities; // 设备支持的操作

u32 reserved[4]; // 保留字段

};

显示设备信息

struct v4l2_capability cap;

ioctl(fd,VIDIOC_QUERYCAP,&cap);

printf(“Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\n”,
                cap.driver,
                cap.card,
                cap.bus_info,
                (cap.version>>16)&0XFF,
                (cap.version>>8)&0XFF,
                cap.version&0XFF);

3.设置视频帧格式 

int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);

struct v4l2_fmtdesc结构体的指针,用于存储格式描述信息

int ioctl(int fd, int request, struct v4l2_format *argp);

4l2_format结构体的指针,用于设置或获取格式信息

v4l2_format 的各个域,如 type(传输流类型),

fmt.pix.width(宽),

fmt.pix.heigth(高),

fmt.pix.field(采样区域,如隔行采样),

fmt.pix.pixelformat(采样类型,如 YUV4:2:2),

然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式

四、用V4L2 查询设备信息以及 用它驱动摄像头拍摄一张JPEG格式的照片。

#if 0
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>

#define DEVICE "/dev/video0"


//  Driver Name: PC Camera
//  Driver Version: 132864
//  Current Format:
//  Width: 320
//  Height: 240
//  Pixel Format: JPEG
//  Supported Formats:
//  JPEG

void print_device_info(int fd) {   //fd 是表示视频设备的文件描述符
    struct v4l2_capability cap; // 用于存储设备的能力信息
    struct v4l2_format fmt;//用于存储设备的输出格式
    struct v4l2_fmtdesc fmt_desc;//用于枚举视频格式
    int i, ret;

    memset(&cap, 0, sizeof(cap));//  初始化清零
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {//使用ioctl函数查询设备能力,如果失败则输出错误信息并返回
        perror("VIDIOC_QUERYCAP");
        return;
    }
    printf("Driver Name: %s\n", cap.card);//打印设备名称
    printf("Driver Version: %u\n", cap.version);//打印设备版本信息

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//  表示视频捕获
    if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
        perror("VIDIOC_G_FMT");
        return;
    }
    printf("Current Format:\n");
    printf("  Width: %d\n", fmt.fmt.pix.width);//当前视频格式 宽和高
    printf("  Height: %d\n", fmt.fmt.pix.height);
    printf("  Pixel Format: %c%c%c%c\n",//打印当前视频格式的像素格式  
           fmt.fmt.pix.pixelformat & 0xFF,//fmt.fmt.pix.pixelformat 的从低到高的24位
           (fmt.fmt.pix.pixelformat >> 8) & 0xFF,//
           (fmt.fmt.pix.pixelformat >> 16) & 0xFF,//
           (fmt.fmt.pix.pixelformat >> 24) & 0xFF);//

    memset(&fmt_desc, 0, sizeof(fmt_desc));//初始化
    fmt_desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//存储的格式类型
    printf("Supported Formats:\n");//支持的类型
    for (i = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmt_desc) == 0; i++) {
        printf("  %c%c%c%c\n",
               fmt_desc.pixelformat & 0xFF,
               (fmt_desc.pixelformat >> 8) & 0xFF,
               (fmt_desc.pixelformat >> 16) & 0xFF,
               (fmt_desc.pixelformat >> 24) & 0xFF);
        fmt_desc.index++;
    }
}

int main() {
    int fd = open(DEVICE, O_RDWR);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    print_device_info(fd);

    close(fd);
    return 0;
}
#endif
#if 1
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <jpeglib.h>  // 需要安装 libjpeg-dev

#define DEVICE "/dev/video0"
#define NB_BUFFER 4

typedef struct videoDeviceParam {      
    int iFd;   //文件描述符  用于标示打开的文件或设备
    int iWidth;
    int iHeight;
    int iBpp;//  表示位深度   每个像素占用的位数
    int pixelformat;// 表示视频帧的像素格式
    int iVideoBufCnt;//  标示视频缓冲区的数量
    int iVideoBufCurIndex;// 表示当前视频缓冲区的索引
    int iVideoBufMaxLen;//标示最大视频缓冲区长度
    unsigned char *pucVideBuf[NB_BUFFER];// 指针数组 用于存储视频缓冲区的指针
} T_videoDeviceParam, *PT_videoDeviceParam;//将结构体重命名  同时定义了指向该结构体的指针

//
int V4l2InitDevice(char *videoDevName, PT_videoDeviceParam pt_videoDeviceParam) {
    int i;
    int iFd;
    int iError;

    struct v4l2_capability tV4l2Cap;
    struct v4l2_format tV4l2Fmt;//声明一个 v4l2_format 结构体 用于设置视频格式
    struct v4l2_requestbuffers tV4l2ReqBuffs;//  用于请求缓冲区
    struct v4l2_buffer tV4l2Buf;//用于操作缓冲区

    iFd = open(videoDevName, O_RDWR);//打开视频设备
    if (iFd < 0) {
        perror("Video OPEN Failed");
        return -1;
    }
    pt_videoDeviceParam->iFd = iFd;

    memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));//初始化
    iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
    if (iError) {
        perror("Unable to query device");
        close(iFd);
        return -1;
    }

    memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
    tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type成员指定视频设备的操作类型 
	                         //V4L2_BUF_TYPE_VIDEO_CAPTURE 表示对视频进行视频捕捉操作
    tV4l2Fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; // Example format  设置像素格式为JPEG
    tV4l2Fmt.fmt.pix.width = 320;
    tV4l2Fmt.fmt.pix.height = 240;
    tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;//设置场域为任意

    iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);//  VIDIOC_S_FMT 控制指令 用于设置视频设备的格式
    if (iError) {
        perror("Unable to set format");
        close(iFd);
        return -1;
    }

    pt_videoDeviceParam->iWidth = tV4l2Fmt.fmt.pix.width;
    pt_videoDeviceParam->iHeight = tV4l2Fmt.fmt.pix.height;

    memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
    tV4l2ReqBuffs.count = NB_BUFFER;//设置缓冲区数量
    tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置缓冲区类型
    tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;//设置内存映射类型
    iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);//求情缓冲区
    if (iError) {
        perror("Unable to request buffers");
        close(iFd);
        return -1;
    }

    pt_videoDeviceParam->iVideoBufCnt = tV4l2ReqBuffs.count;//将缓冲区数量赋值

    if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {// 如果设备支持流式传输
        for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
            memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));//清零
            tV4l2Buf.index = i;//设置缓冲区索引
            tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            tV4l2Buf.memory = V4L2_MEMORY_MMAP;
            iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);//查询缓冲区
            if (iError) {
                perror("Unable to query buffer");
                close(iFd);
                return -1;
            }

            pt_videoDeviceParam->iVideoBufMaxLen = tV4l2Buf.length;//将缓冲区最大长度赋值
            pt_videoDeviceParam->pucVideBuf[i] = mmap(0,
                tV4l2Buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, iFd,
                tV4l2Buf.m.offset);//将缓冲区地址赋值给结构体中的相应元素
            if (pt_videoDeviceParam->pucVideBuf[i] == MAP_FAILED) {//如果映射失败 报错
                perror("Unable to map buffer");
                close(iFd);
                return -1;
            }
        }

        for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
            memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
            tV4l2Buf.index = i;//
            tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//
            tV4l2Buf.memory = V4L2_MEMORY_MMAP;//
            iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);//将缓冲区加入队列
            if (iError) {
                perror("Unable to queue buffer");
                close(iFd);
                return -1;
            }
        }

        // Start streaming
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(iFd, VIDIOC_STREAMON, &type) < 0) {//开始流式传输
            perror("Unable to start streaming");
            close(iFd);
            return -1;
        }

        // Capture a single frame
        memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        tV4l2Buf.memory = V4L2_MEMORY_MMAP;
        if (ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf) < 0) {//从队列中取出一个缓冲区
            perror("Unable to dequeue buffer");
            ioctl(iFd, VIDIOC_STREAMOFF, &type);
            close(iFd);
            return -1;
        }

        unsigned char *frame_data = pt_videoDeviceParam->pucVideBuf[tV4l2Buf.index];//获取缓冲区数据指针
        printf("Captured a frame of length %d\n", tV4l2Buf.bytesused);//打印捕获到的帧长度

        // Save the frame as JPEG
        FILE *file = fopen("capture.jpg", "wb");//打开*.jpg文件进行写入  wb表示以二进制写入的方式打开文件
        if (!file) {
            perror("Unable to open file");
            ioctl(iFd, VIDIOC_STREAMOFF, &type);
            close(iFd);
            return -1;
        }

        fwrite(frame_data, 1, tV4l2Buf.bytesused, file);//写入帧数据到文件
        fclose(file);

        if (ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf) < 0) {//将缓冲区重新加入队列
            perror("Unable to queue buffer");
            ioctl(iFd, VIDIOC_STREAMOFF, &type);
            close(iFd);
            return -1;
        }

        // Stop streaming
        if (ioctl(iFd, VIDIOC_STREAMOFF, &type) < 0) {//停止流式传输
            perror("Unable to stop streaming");
        }
    }

    // Cleanup
    for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
        munmap(pt_videoDeviceParam->pucVideBuf[i], pt_videoDeviceParam->iVideoBufMaxLen);//释放缓冲区内存
    }
    close(iFd);

    return 0;
}

int main() {
    T_videoDeviceParam videoDeviceParam;
    if (V4l2InitDevice(DEVICE, &videoDeviceParam) == 0) {// 调用 V4l2InitDevice 函数  
//函数接受 一个设备名称字符串 和一个 指向 T_videoDeviceParam 结构体的指针作为参数 成功返回0 失败返回-1
        printf("Device initialized successfully.\n");
    } else {
        printf("Failed to initialize device.\n");
    }
    return 0;
}
#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2048296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elasticsearch-关键词随机查询(8.x)

目录 一、查询语句 二、Java代码实现 基础介绍&#xff1a; ES自定义评分机制:function_score查询详解-阿里云开发者社区ES自定义评分机制:function_score查询详解https://developer.aliyun.com/article/1054571 开发版本详见&#xff1a;Elasticsearch-经纬度查询(8.x-半径…

面向对象程序设计(C++)之 vector(初阶)

1. vector 的构造 vector 需要显式实例化类模版&#xff0c;在创建 vector 类型的容器时可以直接创建&#xff0c;也可以进行初始化&#xff0c;例如 v2 &#xff0c;也可以使用迭代器的方式创建&#xff0c;具体关于更多vector的知识: vector //模版类只能显式实例化 vector&l…

Linux ubuntu 24.04 安装运行《帝国时代3》免安装绿色版游戏,解决 “Could not load DATAP.BAR”等问题

Linux ubuntu 24.04 安装运行《帝国时代3》游戏&#xff0c;解决 “Could not load DATAP.BAR" 等问题 《帝国时代 3》是一款比较经典的即时战斗游戏&#xff0c;伴随了我半个高中时代&#xff0c;周末有时间就去泡网吧&#xff0c;可惜玩的都是简单人机&#xff0c;高难…

构建具有音频功能的中英翻译器:一个Python应用程序的旅程

在当今的全球化世界中&#xff0c;语言翻译工具变得越来越重要。作为一名软件开发者&#xff0c;我最近完成了一个有趣的项目&#xff1a;一个结合了翻译、文字转语音和数据管理功能的中英翻译器。在这篇博客中&#xff0c;我将分享这个应用程序的主要特性和开发过程中的一些见…

CSC7261BH PD20瓦快充芯片

CSC7261BH是一款20瓦内置高压MOS的高性能、多工作模式的PWM控制芯片&#xff0c;内置多种保护机制。当系统为空载和轻载时&#xff0c;CSC7261BH 采用Burst和Green控制模式可有效地减少了空载和轻载时的损耗。当系统为中载和重载时&#xff0c;CSC7261BH采用CCM模式可有效提升电…

【kubernetes】K8S常见的发布方式

一、K8S常见的发布方式 蓝绿发布 两套环境交替升级&#xff0c;旧版本保留一定时间便于回滚 优点&#xff1a;对用户无感&#xff0c;是最安全的发布方式&#xff0c;业务稳定 缺点&#xff1a;需要两套系统&#xff0c;对资源要求比较高&#xff0c;成本特别高 灰度发布&…

STM32标准库学习笔记-3.外部中断

参考教程&#xff1a;【STM32入门教程-2023版 细致讲解 中文字幕】 中断 中断含义&#xff1a;在计算机执行主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&…

使用docker compose一键部署 Portainer

使用docker compose一键部署 Portainer Portainer 是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 1、创建安装目录 mkdir /data/partainer/ -p && cd /data/partainer2、创建docker…

【C语言篇】数组和函数的实践:扫雷游戏(附源码)

文章目录 前言扫雷游戏的分析和设计扫雷游戏的功能说明游戏的分析和设计文件结构设计 扫雷游戏的代码实现初始化棋盘打印棋盘布置雷排查雷 扫雷游戏的拓展 前言 源码在最后 扫雷游戏的分析和设计 经典扫雷游戏 扫雷游戏的功能说明 使⽤控制台实现经典的扫雷游戏 游戏可以通…

8月14日微语报,星期三,农历七月十一

8月14日微语报&#xff0c;星期三&#xff0c;农历七月十一&#xff0c;工作愉快&#xff0c;生活喜乐&#xff01; 一份微语报&#xff0c;众览天下事&#xff01; 1、巴黎奥运会&#xff1a;32项次世界纪录被刷新&#xff0c;125项次奥运纪录被改写。 2、国家邮政局&#…

鸿蒙应用程序框架基础

鸿蒙应用程序框架基础 应用程序包基础知识应用的多Module设计机制Module类型 Stage模型应用程序包结构开发态包结构编译包形态发布台包结构选择合适的包类型 应用程序包基础知识 应用的多Module设计机制 **支持模块化开发&#xff1a;**一个应用通常会包含多种功能&#xff0…

【stm32项目】多功能智能家居室内灯光控制系统设计与实现(完整工程资料源码)

多功能智能家居室内灯光控制系统设计与实现 目录&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、项目背景与目标 二、国内外研究现状&#xff1a; 2.1 国内研究现状&#xff1a; 2.2 国外研究现状&#xff1a; 2.3 发展趋势 三、硬件电路设计 3.1 总体概述 3.2 硬件连接总…

[NSSCTF 2022 Spring Recruit]babyphp

if嵌套&#xff0c;先过第一个if&#xff0c;需要a不含数字而且intval取整数 intval:通过使用指定的进制 base 转换&#xff08;默认是十进制&#xff09;&#xff0c;返回变量 value 的 int 数值。 intval() 不能用于 object&#xff0c;否则会产生 E_WARNING 错误并返回 1。…

微服务实战系列之玩转Docker(十)

前言 我们知道Docker的“使命”是为了快速完成应用的迁移和部署。为提升它的战斗能力&#xff0c;Docker官方携手发布了Docker Swarm—— 一个快速完成Docker集群构建的利器。那么请先回忆一下本系列第八篇&#xff08;重点compose&#xff09;和第九篇&#xff08;重点networ…

歌曲爬虫下载

本次编写一个程序要爬取歌曲音乐榜https://www.onenzb.com/ 里面歌曲。有帮到铁子的可以收藏和关注起来&#xff01;&#xff01;&#xff01;废话不多说直接上代码。 1 必要的包 import requests from lxml import html,etree from bs4 import BeautifulSoup import re impo…

Kaggle竞赛——心脏病患者预测与可视化

目录 准备工作1. 特征解释2. 生成探索性数据分析报告2.1 数据集导入2.2 生成数据分析报告 3. 可视化分析3.1 特征相关性分析3.2 患病人数统计3.3 特征与是否患病之间的关系 4. 数据处理4.1 定类数据处理4.2 独热编码 5. 模型搭建5.1 随机森林模型5.2 可视化决策树5.3 特征重要性…

SourceTree基础使用教程

SourceTree基础使用 在B站上搜了不少关于SourceTree的教学视频&#xff0c;多家比较后下面的视频干活比较多 sourcetree使用教程 大概就二十几分钟&#xff0c;跟着学了一个小时&#xff0c;边看边操作&#xff0c;印象更深刻一些 主要涉及以下操作&#xff1a; 1、新建分支…

vue-echarts---折线图 (直接cv就行,全注释)

1、效果展示 2、完整代码&#xff0c;直接cv即可跑起来 <template><div id"mainend"></div> </template><script> import * as echarts from echarts; export default {name: LineChart,mounted() {this.initChart();this.registerUs…

NXP i.MX8系列平台开发讲解 - 4.1.1 GNSS篇(一) - 定位基础知识

专栏文章目录传送门&#xff1a;返回专栏目录 Hi, 我是你们的老朋友&#xff0c;主要专注于嵌入式软件开发&#xff0c;有兴趣不要忘记点击关注【码思途远】 文章目录 目录 专用名词&#xff1a; 1. GNSS 概述 1.2 四大GNSS 概述 1.2.1 GPS 1.2.2 BeiDou 1.2.3 GLONASS…

vue3 antdv3 将vxe-grid的头二行改为一行,解决最后一列不能自动根据屏幕的宽度自动缩放的问题。

1、先上个图&#xff1a; 官方地址&#xff1a; Vxe Table v4.6 header搞了二层&#xff0c;然后反馈过来&#xff0c;让调整&#xff1a; {title: 通讯地址,width: 200,showOverflow: tooltip,align: center,sortable: true,filters: companyOptions,filterRender: { name:…