嵌入式 V4L2 应用编程(1):抓取 uvc camrea 图像

简介: 嵌入式 V4L2 应用编程(1):抓取 uvc camrea 图像

V4L2 (video For Linux Two) 是 linux 内核提供给应用程序访问音、视频驱动的统一接口。其相关定义包含在头文件中。


工作流程


  • 打开设备


  • 检查设备属性


  • 设置帧格式


  • 设置输入输出方法


  • 循环获取数据


  • 关闭设备


设备打开和关闭


相关接口和数据结构:


  • 接口:


#include<fcntl.h>
#include<unistd.h>
// 打开
int open(constchar*device_name, int flags);
// 关闭
int close(fd);


Step1: 打开设备


/*Step1: open /dev/video0 :uvc 摄像头 插入后自动生成 /dev/video0*/
int fd = -1;
fd = open(dev_name, O_RDWR | O_NONBLOCK, 0);
if(fd < 0) {
    printf("ERROR(%s): can not open %s \n", MODULE_TAG, dev_name);
    return -1;
}


查询设备属性:VIDIOC_QUERYCAP


相关接口和数据结构:


  • 接口:


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


  • 数据结构


struct v4l2_capability
{
    u8 driver[16];    // 驱动名字
    u8 card[32];      // 设备名字
    u8 bus_info[32];  // 设备在系统中的位置
    u32 version;      // 驱动版本号
    u32 capabilities; // 设备支持的操作.常用值V4L2_CAP_VIDEO_CAPTURE:是否支持图像获取
    u32 reserved[4];  // 保留字段
};


Step2: get capabilities


int ret = -1;
struct v4l2_capability cap;
    memset(&cap, 0, sizeof(cap));
    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if( ret < 0) {
    printf("ERROR(%s): ioctl VIDIOC_QUERYCAP error \n", MODULE_TAG);
    return -1;
} else{
// Print capability infomations
    printf("Capability Informations:\n");
    printf("\tdriver: %s\n", cap.driver);
    printf("\tcard: %s\n", cap.card);
    printf("\tbus_info: %s\n", cap.bus_info);
    printf("\tversion: %08X\n", cap.version);
    printf("\tcapabilities: %08X\n", cap.capabilities);
}


查询所有支持的格式:VIDIOC_ENUM_FMT


相关接口和数据结构:


  • 接口


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


  • 数据结构


struct v4l2_fmtdesc
{
    u32 index;                  // 要查询的格式序号,应用程序设置
    enum v4l2_buf_type type;    // 帧类型,应用程序设置
    u32 flags;                  // 是否为压缩格式
    u8 description[32];         // 格式名称:YUYV/MJPEG
    u32 pixelformat;            // 格式
    u32 reserved[4];            // 保留
};


Step3: get support fmt


struct v4l2_fmtdesc fmtdest;
memset(&fmtdest, 0, sizeof(fmtdest));
fmtdest.index = 0;
fmtdest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("\nSupport FMT: \n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdest) != -1)
{
    printf("\t%d.%s\n", fmtdest.index + 1, fmtdest.description);
    fmtdest.index++;
}


查询或设置当前帧信息:


VIDIOC_G_FMT/VIDIOC_S_FMT:


相关接口和数据结构:


  • 接口:


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


  • 数据结构:


struct v4l2_format
{
    enum v4l2_buf_type type;        // 帧类型,应用程序设置
    union fmt
    {
        struct v4l2_pix_format pix; // 视频设备使用
        struct v4l2_window win;
        struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
        u8 raw_data[200];
    };
};
struct v4l2_pix_format
{
    u32 width;             // 帧宽,单位像素
    u32 height;            // 帧高,单位像素
    u32 pixelformat;       // 帧格式
    enum v4l2_field field;
    u32 bytesperline;
    u32 sizeimage;
    enum v4l2_colorspace colorspace;
    u32 priv;
};


Step4: set fmt:


int ret = -1;
struct v4l2_format fmt;
    CLEAR(fmt);
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = FRAME_WIDTH;
    fmt.fmt.pix.height = FRAME_HEIGHT;
#ifdef CAPTURE_YUV
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
#else
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
#endif
/* fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; */
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
if( ret < 0) {
    printf("ERROR(%s): ioctl VIDIOC_S_FMT error \n", MODULE_TAG);
    return -1;
}


申请和管理缓冲区


相关接口和数据结构


  • 接口:


// 向设备申请缓冲区:VIDIOC_REQBUFS
int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
// 获取缓冲区地址长度:VIDIOC_QUERYBUF
int ioctl(int fd, int request, struct v4l2_buffer *argp);
//内存映射
#include<sys/mman.h>
void*mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void*addr, size_t length);// 断开映射


  • 数据结构:


//1.1
struct v4l2_requestbuffers
{
    u32 count;                // 缓冲区内缓冲帧的数目
    enum v4l2_buf_type type;  // 缓冲帧数据格式
    enum v4l2_memory memory;  // 区别是内存映射还是用户指针方式.V4L2_MEMORY_MMAP, V4L2_MEMORY_USERPTR
    u32 reserved[2];
};
//1.2
struct v4l2_buffer
{
    u32 index;                       //buffer 序号
    enum v4l2_buf_type type;         //buffer 类型
    u32 byteused;                    //buffer 中已使用的字节数
    u32 flags;                       // 区分是MMAP 还是USERPTR
    enum v4l2_field field;
    struct timeval timestamp;        // 获取第一个字节时的系统时间
    struct v4l2_timecode timecode;
    u32 sequence;                    // 队列中的序号
    enum v4l2_memory memory;         //IO 方式,被应用程序设置
    union m
    {
        u32 offset;                  // 缓冲帧地址,只对MMAP 有效
        unsignedlong userptr;
    };
    u32 length;                      // 缓冲帧长度
    u32 input;
    u32 reserved;
};
//1.3
struct buffer
{
    void* start;
    unsignedint length;
}*buffers;


Step5: 申请缓冲


struct v4l2_requestbuffers req;
    CLEAR(req);
    req.count = 4;//1
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(fd, VIDIOC_REQBUFS, &req); // 申请4个缓冲帧
if( ret < 0) {
    printf("ERROR(%s): ioctl VIDIOC_REQBUFS error \n", MODULE_TAG);
    goto err_exit;
}
if(req.count < 1) {
    printf("Insufficient buffer memory\n");
}
/* 将四个申请的缓冲帧映射到应用程序*/
uvc_ctx.buffers = (struct buffer*)calloc(req.count, sizeof(*(uvc_ctx.buffers)));
struct v4l2_buffer buf;
for(uvc_ctx.n_buffers = 0; uvc_ctx.n_buffers < req.count; ++uvc_ctx.n_buffers)
{
    CLEAR(buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = uvc_ctx.n_buffers;
/* 查询序号为n_buffers 的缓冲区, 得到起始物理地址和大小*/
if(ioctl(fd, VIDIOC_QUERYBUF, &buf)) {
      printf("ERROR(%s) : ioctl VIDIOC_QUERYBUF error\n", MODULE_TAG);
      goto err_free;
}
uvc_ctx.buffers[uvc_ctx.n_buffers].length = buf.length;
/* 映射内存 */
uvc_ctx.buffers[uvc_ctx.n_buffers].start = mmap(NULL ,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED ,fd, buf.m.offset);
if(MAP_FAILED == uvc_ctx.buffers[uvc_ctx.n_buffers].start) {
    printf("ERROR(%s): mmap failed\n", MODULE_TAG);
    goto err_free;
}
/* 将4个缓冲区放入队列 */
if(ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
    printf("ERROR(%s) : ioctl VIDIOC_QBUF failed\n", MODULE_TAG);
    goto err_unmap;
    }
}


开始获取数据流:


VIDIOCSTREAMON:启动 VIDIOCSTREAMOFF:停止


相关接口和数据结构:


  • 接口:


// 在开始之前应把缓冲帧放入缓冲队列
//VIDIOC_QBUF:把帧放入队列
//VIDIOC_DQBUF:从队列中取出帧
int ioctl(int fd, int request, constint*argp);


Step6:开启视频流


enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
    printf("ERROR(%s): ioctl VIDIOC_STREAMON failed\n", MODULE_TAG);
    goto err_unmap;
}


帧处理


// 循环处理
while(!uvc_ctx.stop) {
    fd_set fds;
    struct timeval tv;
    int r;
    FD_ZERO (&fds);
    FD_SET (fd, &fds);
    tv.tv_sec = 2;
    tv.tv_usec = 0;
    r = select (fd + 1, &fds, NULL, NULL, &tv);
    if(-1== r)
    {
        if(EINTR == errno) {
            printf ("select err\n");
            continue;
        }
    }
    if(0== r)
    {
        fprintf (stderr, "select timeout\n");
        exit (EXIT_FAILURE);
    }
    /* 取出一帧 */
    read_frame(uvc_ctx.fd);
}
//单帧处理
static int read_frame (int fd)
{
    struct v4l2_buffer buf;
    CLEAR (buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    /* 取出一帧 : VIDIOC_DQBUF*/
    int ret = ioctl(fd, VIDIOC_DQBUF, &buf);
    if( ret < 0) {
        printf("ERROR(%s): ioctl VIDIOC_DQBUF error \n", __func__);
        return -1;
    }
    assert (buf.index < uvc_ctx.n_buffers);
#if 1
    FILE*file_fd;
#ifdef CAPTURE_YUV
    file_fd = fopen("frame.yuv", "w");
    if(file_fd < 0) {
        printf("ERROR(%s): fopen frame.yuv error \n", __func__);
        return -1;
    }
#else
    file_fd = fopen("frame.jpg", "w");
    if(file_fd < 0) {
        printf("ERROR(%s): fopen frame.jpg error \n", __func__);
    return-1;
    }
#endif
    fwrite(uvc_ctx.buffers[buf.index].start, uvc_ctx.buffers[buf.index].length, 1, file_fd);
    fclose(file_fd);
#endif
//save image or  display_lcd
    usleep(500);
    ret = ioctl (fd, VIDIOC_QBUF, &buf);
    if( ret < 0) {
        printf("ERROR(%s)ioctl VIDIOC_QBUF\n", __func__);
        return -1;
    }
     return
 0
;
}


总结


本文主要介绍了V4L2 应用编程基本流程,基本操作就是从usb 摄像头获取指定的视频帧 MPEG 或者 YUYV,保存成对应的图片。读者也可以根据需要,转换格式放到 lcd 屏幕或其他设备上显示。

相关文章
|
JavaScript
Qt视频播放器[QMediaPlayer+QVideowidget]
本代码在Window10下运行,利用qMediaPlayer和qvideowidget实现视频文件mp4的播放,并且提供进度显示,还可以通过拖动进度条来变换播放位置
1471 0
|
Ubuntu Linux Windows
Linux下音频开发: 读取声卡PCM数据保存到文件(alsa-lib库)
Linux下音频开发: 读取声卡PCM数据保存到文件(alsa-lib库)
2196 0
Linux下音频开发: 读取声卡PCM数据保存到文件(alsa-lib库)
|
Linux C语言 ice
编译安装 tree 命令
编译安装 tree 命令
267 0
|
传感器 编解码 Linux
V4L2框架 | MIPI Camera指令调试笔记
V4L2框架 | MIPI Camera指令调试笔记
6776 2
|
JavaScript 前端开发 Java
基于springboot的留守儿童爱心网站
这是一个基于SpringBoot的留守儿童爱心网站,包含管理员和用户两种角色。管理员负责用户、新闻、志愿活动、捐赠等管理;用户可进行登录注册、爱心捐赠及活动报名。项目采用SpringBoot与Mybatis作为后端框架,前端则使用HTML和VUE。适用于JDK1.8、IDEA/Eclipse、MySQL5.7/8.x,无需特定Tomcat或Maven版本,支持Windows系统。
264 13
基于springboot的留守儿童爱心网站
|
编解码 监控 网络协议
如何用魔法般的步骤实现RTSP推送H.264与H.265(HEVC),打造震撼视听盛宴,让每一帧都充满魔力!
【9月更文挑战第3天】实现RTSP流媒体服务推送H.264和H.265编码视频是现代视频监控及直播平台的关键技术。本文详细介绍环境搭建、编码配置及服务器与客户端实现方法。首先,通过FFmpeg捕获视频并编码成RTSP流,接着使用VLC等工具接收播放。此外,还提供了C++示例代码,演示如何利用libv4l2和FFmpeg自定义服务器端实现。希望本文能帮助读者成功搭建RTSP视频流系统。
2263 1
|
存储 并行计算 Ubuntu
Nvidia Jetson Orin系列配置教程
本文是Nvidia Jetson Orin系列的配置教程,介绍了两种安装方法:通过Nvidia SDK Manager进行安装和通过本地镜像烧录进行安装。第一种方法包括下载SDK Manager、安装和使用工具进行Jetson系列硬件的配置。第二种方法包括下载官方镜像、使用Etcher烧录镜像、安装镜像、安装开发环境以及检查开发环境是否配置成功。文中还提供了CUDA、cuDNN、TensorRT和OpenCV的检查命令和预期结果。
3172 0
Nvidia Jetson Orin系列配置教程
|
存储 传感器 编解码
【Camera基础(二)】摄像头驱动原理和开发&&V4L2子系统驱动架构
【Camera基础(二)】摄像头驱动原理和开发&&V4L2子系统驱动架构
|
Ubuntu Linux 芯片
linux系统中wifi驱动的配置与编译实现方法
linux系统中wifi驱动的配置与编译实现方法
1601 1
|
JavaScript 前端开发 安全
【QML 与 C++ 之间的通讯机制】QML 与 Qt 通讯:讲解如何在QML 中使用C++类,以及如何在C++ 中获取QML的内容
【QML 与 C++ 之间的通讯机制】QML 与 Qt 通讯:讲解如何在QML 中使用C++类,以及如何在C++ 中获取QML的内容
1810 1