嵌入式 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 屏幕或其他设备上显示。

相关文章
|
6月前
|
Linux Android开发
嵌入式linux中Framebuffer 驱动程序框架分析
嵌入式linux中Framebuffer 驱动程序框架分析
67 0
|
2月前
|
Linux API 芯片
平台设备和驱动程序 【ChatGPT】
平台设备和驱动程序 【ChatGPT】
|
6月前
|
编解码 芯片
嵌入式中常见的显示屏接口有哪些?
嵌入式中常见的显示屏接口有哪些?
180 0
|
6月前
|
存储 数据挖掘 数据处理
嵌入式中逻辑分析仪的基本使用方法
嵌入式中逻辑分析仪的基本使用方法
97 0
|
6月前
|
Java Linux Android开发
嵌入式Android系统耳机驱动基本知识
嵌入式Android系统耳机驱动基本知识
78 0
|
编译器 Linux
嵌入式 QT usb camera库驱动摄像头
嵌入式 QT usb camera库驱动摄像头
|
编解码 监控 Linux
嵌入式Linux MIPI接口LCD调试-关于DRM显示与应用调试的干货浓缩
嵌入式Linux MIPI接口LCD调试-关于DRM显示与应用调试的干货浓缩
1160 0
|
机器学习/深度学习 人工智能 算法
嵌入式端音频开发(Unisound篇)之 7.1 蜂鸟M离线语音芯片简介
嵌入式端音频开发(Unisound篇)之 7.1 蜂鸟M离线语音芯片简介
618 0
嵌入式端音频开发(Unisound篇)之 7.1 蜂鸟M离线语音芯片简介
|
移动开发 网络协议 Linux
嵌入式之一款GPRS模块的应用
嵌入式之一款GPRS模块的应用
188 0
嵌入式之一款GPRS模块的应用
|
存储 编解码 Linux
嵌入式Linux下LCD应用编程: 调用giflib库解码显示GIF动态图
嵌入式Linux下LCD应用编程: 调用giflib库解码显示GIF动态图
772 0
嵌入式Linux下LCD应用编程: 调用giflib库解码显示GIF动态图