文章目录

  • 1 V4L2
    • 1.1 V4L2特点
    • 1.2 V4L2设备
  • 2 V4L2设备访问接口
    • 2.1设备访问
      • 2.1.1 查询设备属性
      • 2.1.2 查询设备输出格式
      • 2.1.3 设置帧输出格式
      • 2.1.4 申请帧缓存
      • 2.1.5 内核内存转换
      • 2.1.6 缓冲帧内存入队操作
      • 2.1.7 启动视频流采集
      • 2.1.8 读取数据帧内存序号
      • 2.1.9 关闭视频流采集
    • 2.2 视频流读取
  • 3 V4L2应用开发流程
  • 4 参考文章

1 V4L2

  V4L2全称是Video for Linux two(Video4Linux2),是V4L2改进衍生版本。V4L2是linux操作系统下的一个标注化的音频、视频设备驱动框架,向下屏蔽底层设备的差异,向上提供标准统一的访问接口,提高用户在音视频方面的应用开发效率。只要底层音视频设备(如摄像头)兼容V4L2标准,则应用程序可以无缝接入该音视频设备。本文主要描述V4L2视频设备(摄像头)的应用。

1.1 V4L2特点

  屏蔽底层差异,向上提供标准的用户访问接口,更换支持V4L2标准的物理音视频设备,原则上应用程序可以完全兼容。V4L2对用户提供的接口包括:

  • 视频采集接口

  • 视频输出接口

  • 视频覆盖/预览接口

  • 视频输出覆盖接口

  • 编解码接口

1.2 V4L2设备

  linux思想是一切皆文件。V4L2设备接入并且驱动被加载成功时在"/dev"生成设备文件,名称为"videoX"(/dev/videoX),X为设备设备序号;一般按接入的设备顺序排序,只有一个设备时为video0。V4L2应用程序通过调用linux标准的设备文件系统接口,访问音视频设备。

2 V4L2设备访问接口

  V4L2音视频设备可通过标准文件系统接口open/read/ioctl/close访问,设备访问分为两大部分,分别是设备访问和视频流获取。关于V4L2的接口声明、宏定义、枚举类型可以在“include/uapi/linux/videodev2.h”头文件中查阅。

2.1设备访问

  设备访问包括获取和设置设备信息,如设备驱动信息、设备属性、图像帧格式、控制图像捕获等。这些都通过ioctl来实现访问,访问格式如下。

ioctl(fd, cmd, param); /* 文件描述符, 命令字, 参数信息 */

  常用命令字:

VIDIOC_QUERYCAP     /* 查询设备属性 */
VIDIOC_ENUM_FMT     /* 查询设备支持的输出格式*/
VIDIOC_G_FMT        /* 查询设备输出帧格式 */
VIDIOC_S_FMT        /* 设置设备输出帧格式 */
VIDIOC_REQBUFS      /* 申请帧缓存 */
VIDIOC_QUERYBUF     /* 获取申请的帧缓存 */
VIDIOC_QBUF         /* 将帧缓存加入视频流采集队列 */
VIDIOC_DQBUF        /* 获取已采集视频流的缓存帧 */
VIDIOC_STREAMON     /* 开启视频流采集 */
VIDIOC_STREAMOFF    /* 关闭视频流采集 */

2.1.1 查询设备属性

函数原型:

int ioctl(int fd, int cmd, struct v4l2_capability *cap);
  • cmd,命令字:VIDIOC_QUERYCAP

  • cap,设备属性数据结构:

struct v4l2_capability{__u8 driver[16];      /* driver驱动名字 */__u8 card[32];        /* device设备名字 */__u8 bus_info[32];    /* 设备在系统中的位置 */__u32 version;        /* 驱动版本号 */__u32 capabilities;   /* 设备支持的操作 */__u32 device_caps;    /* 特殊设备信息 */__u32 reserved[4];    /* 保留字段 */
};

【1】capabilities,视频设备支持的操作,以每一位表示,常用类型宏位于“/uapi/linux/videodev2.h”定义。

/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE      0x00000001  /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT       0x00000002  /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY      0x00000004  /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE        0x00000010  /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT         0x00000020  /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040  /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT  0x00000080  /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE        0x00000100  /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200  /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK       0x00000400  /* Can do hardware frequency seek  */
#define V4L2_CAP_RDS_OUTPUT         0x00000800  /* Is an RDS encoder */

查询设备属性伪代码:

struct v4l2_capability   cap = {0};ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);

2.1.2 查询设备输出格式

函数原型:

int ioctl(int fd, int cmd, struct v4l2_fmtdesc *fmt);
  • cmd,命令字:VIDIOC_ENUM_FMT

  • fmt,设备输出格式数据结构:

struct v4l2_fmtdesc {__u32 index;             /* 设置信息,查询格式序号 */__u32 type;              /* 设置信息,设备类型 */__u32 flags;__u8 description[32];   /* 返回信息,格式描述 */__u32 pixelformat;      /* 返回信息,格式 */__u32 reserved[4];        /* 保留字段 */
};

【1】index,查询序号,从0开始查询

【2】type,设备类型,实际类型为enum v4l2_buf_type,位于“/uapi/linux/videodev2.h”定义;如果是camera设备,则设置为V4L2_BUF_TYPE_VIDEO_CAPTURE

【3】flags,一般不用

循环获取设备输出格式伪代码:

struct v4l2_fmtdesc fmtdesc = {0};  fmtdesc.index = 0 ;
fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("the video device support format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{   printf("%d.%s\n", fmtdesc.index+1, fmtdesc.description);fmtdesc.index++;
}

2.1.3 设置帧输出格式

  图像帧输出格式基本参数包括:

  • 像素宽度
  • 像素高度
  • 数据类型

函数原型:

int ioctl(int fd, int cmd, struct v4l2_format *format);
  • cmd,命令字:VIDIOC_S_FMT

  • format,设置输出格式数据结构:

struct v4l2_format {__u32     type;/* 设备类型 */union {struct v4l2_pix_format   pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE 设备(camera)使用 */struct v4l2_pix_format_mplane pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */struct v4l2_window    win;         /* V4L2_BUF_TYPE_VIDEO_OVERLAY */struct v4l2_vbi_format    vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */struct v4l2_sliced_vbi_formatsliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */struct v4l2_sdr_format      sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */struct v4l2_meta_format     meta;    /* V4L2_BUF_TYPE_META_CAPTURE */__u8   raw_data[200];               /* 保留字段 */} fmt;
};

【1】type,设备类型,实际类型为enum v4l2_buf_type,如果是camera设备,则设置为V4L2_BUF_TYPE_VIDEO_CAPTURE

【2】fmt,一个共用体,不同设备的具体配置参数,以camera为例,其数据结构struct v4l2_pix_format如下

struct v4l2_pix_format {__u32            width;  /* 像素宽度 */__u32         height; /* 像素高度 */__u32         pixelformat;/* 输出格式,根据camera支持的格式选择,JPG或YUV */__u32           field;      /* enum v4l2_field */__u32           bytesperline;  /* for padding, zero if unused */__u32              sizeimage;__u32         colorspace; /* enum v4l2_colorspace */__u32         priv;       /* private data, depends on pixelformat */__u32         flags;      /* format flags (V4L2_PIX_FMT_FLAG_*) */__u32           ycbcr_enc;  /* enum v4l2_ycbcr_encoding */__u32         quantization;   /* enum v4l2_quantization */__u32           xfer_func;  /* enum v4l2_xfer_func */
};

2.1.4 申请帧缓存

函数原型:

int ioctl(int fd, int cmd, struct v4l2_requestbuffers *reqbuf);
  • cmd,命令字:VIDIOC_REQBUFS

  • reqbuf,申请内存数据结构:

struct v4l2_requestbuffers {__u32    count;      /* 内存块数目 */__u32    type;       /* 设备类型 */__u32 memory;     /* 内存用途 */__u32 reserved[2];/* 保留字段 */
};

【1】count,申请内存块数目,至少为1

【2】type,设备类型,实际类型为enum v4l2_buf_type,如果是camera设备,则设置为V4L2_BUF_TYPE_VIDEO_CAPTURE

【3】memory,内存用途,实际类型为enum v4l2_memory,一般用作内存映射V4L2_MEMORY_MMAP

enum v4l2_memory {V4L2_MEMORY_MMAP             = 1,  /* 内存映射 */V4L2_MEMORY_USERPTR          = 2,  /* 用户指针 */V4L2_MEMORY_OVERLAY          = 3,  /* 内存覆盖 */V4L2_MEMORY_DMABUF           = 4,  /* DMA映射 */
};

申请帧缓存伪代码:

struct v4l2_requestbuffers req_buf = {0};req_buf.count = 1;
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);

2.1.5 内核内存转换

  通VIDIOC_S_FMT命令字申请的内核态内存,还需转换为物理内存,用于映射到用户态,这样用户能直接从物理内存中获取视频流数据,提高效率。

函数原型:

int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
  • cmd,命令字:VIDIOC_QUERYBUF

  • buf,返回内存数据结构:

struct v4l2_buffer {__u32            index;  /* 内存块序号 */__u32            type;   /* 类型,与申请的类型一致 */__u32           bytesused;/* 已使用的内存大小 */__u32           flags;__u32         field;struct timeval        timestamp;struct v4l2_timecode timecode;__u32           sequence;/* memory location */__u32         memory; /* 内存用途,一般用内存映射 */union {__u32           offset; /* 内存块可用偏移地址,对于内存映射有效 */unsigned long   userptr;struct v4l2_plane *planes;__s32      fd;} m;__u32            length; /* 内存块大小 */__u32            config_store;__u32          reserved;
};

应用伪代码:

struct v4l2_requestbuffers req_buf = {0};
struct v4l2_buffer buf = {0};req_buf.count = 1;
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);for(i=0; i<req_buf.count; i++)
{buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index  = i;ret = ioctl(pdevice->fd, VIDIOC_QUERYBUF, &buf);pdevice->mmap_buf[i].size = buf.length;pdevice->mmap_buf[i].addr = (char *)mmap(NULL, buf.length, PROT_READ|PROT_WRITE,MAP_PRIVATE, pdevice->fd, buf.m.offset);if(MAP_FAILED == pdevice->mmap_buf[i].addr){perror("mmap failed");}
}

  关于内存映射mmap使用,参考文章mmap内存映射。

2.1.6 缓冲帧内存入队操作

  该操作是将缓冲内存加入V4L2驱动的采集队列中,视频设备采集完成,数据存于该内存中。

函数原型:

int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
  • cmd,命令字:VIDIOC_QBUF
  • buf,帧缓存数据结构

应用代码:

int set_video_device_stream_queue(struct _v4l2_video *pdevice, int index)
{int ret = 0;struct v4l2_buffer buf = {0};/* 将内核缓存放入队列 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = index;ret = ioctl(pdevice->fd, VIDIOC_QBUF, &buf);if(ret < 0){perror("ioctl call \'VIDIOC_QBUF\' failed");return -1;}
}

2.1.7 启动视频流采集

  执行该命令,视频设备执行数据采集,采集完成,将视频流数据存于指定的内存空间。

函数原型:

int ioctl(int fd, int cmd, enum v4l2_buf_type *type);
  • cmd,命令字:VIDIOC_STREAMON
  • type,设备(缓存帧)类型,camera设备为V4L2_BUF_TYPE_VIDEO_CAPTURE

2.1.8 读取数据帧内存序号

  实质上,执行“开启采集”命令成功后,视频流数据会存于预先申请的物理内存空间。然而,如果申请了多个数量的帧缓存,此时需知道视频流存于哪个帧缓存中,用户根据序号访问内存块。通过该命令可以获取存放视频流数据帧内存序号。

函数原型:

int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
  • cmd,命令字:VIDIOC_DQBUF
  • buf,帧缓存数据结构

应用代码:

int read_video_device_stream_frame(struct _v4l2_video *pdevice, int *out_buf_index)
{int ret = 0;int i;struct v4l2_buffer buf = {0};/* 从队列取出数据 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;ret = ioctl(pdevice->fd, VIDIOC_DQBUF, &buf);if(ret < 0){perror("ioctl call \'VIDIOC_DQBUF\' failed");return -1;}if (buf.index > pdevice->mmap_buf_cnt){printf("buf overflow[%d]\n", buf.index);}*out_buf_index = buf.index;return 0;
}

注:

即使只申请一个帧缓存空间,也需要执行该命令,以确定数据是否正确采集完成,否则内存中数据是未知状态。

2.1.9 关闭视频流采集

  结束视频流采集后,调用VIDIOC_STREAMOFF关闭视频流采集。

函数原型:

int ioctl(int fd, int cmd, enum v4l2_buf_type *type);
  • cmd,命令字:VIDIOC_STREAMOFF
  • type,设备(缓存帧)类型,camera设备为V4L2_BUF_TYPE_VIDEO_CAPTURE

2.2 视频流读取

  视频流读取有两种方式。

  • 通过标准文件系统接口read读取
  • 将V4L2设备内核态映射(mmap)到用户态,直接从物理内存获取视频流
  • 用户指针模式,内存由用户分配,与内存映射类似,使用较少

  通过read函数读取视频流,需经过物理内存到内核态、内核态到用户态两个内存拷贝过程,效率比较低,一般用于静态图像的采集获取,一般比较少使用。内存映射是常用的方式, 省去两个拷贝过程,效率高。

3 V4L2应用开发流程

  应用程序访问一个V4L2设备的的总体流程如下图。

关键步骤分析:

  • 查询设备属性,包驱动信息、支持视频流格式,以方便后续设置视频输出属性

  • 设置设备属性,根据获取的设备属性设置,主要是设置视频流输出格式,如制式、宽度、高度、编码方式

  • 帧缓存申请,用于存放驱动采集的视频流,并映射到用户态

  • 帧缓存加入采集队列,如果需循环采集视频流,每次获取视频流后都需执行将帧缓存加入采集队列

  • 结束过程,包括关闭视频流采集、释放内存映射、关闭设备描述符

示例:

  • 获取摄像头信息
  • 获取视频流,保存为图片信息
#include <linux/videodev2.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/types.h>
#include <malloc.h>
#include <math.h>
#include <errno.h>
#include <assert.h>struct _mmap_buf
{void *addr;int size;
};struct _v4l2_video
{int fd;struct _mmap_buf *mmap_buf;int  mmap_buf_cnt;
};int open_video_device(const char *device_name)
{int fd = 0;if (device_name == NULL){return -1;}fd = open(device_name, O_RDWR); /* 以读写方式打开;以只读方式打开导致内存映射出错 */if(fd < 0){  perror("open video device failed");return -1;};return fd;
}int close_video_device(struct _v4l2_video *pdevice)
{int ret = 0;int i;for(i = 0; i<pdevice->mmap_buf_cnt; i++){if (pdevice->mmap_buf[i].addr != 0x00){ret = munmap(pdevice->mmap_buf[i].addr, pdevice->mmap_buf[i].size);if(ret < 0){perror("munmap failed");continue;}}}if (pdevice->mmap_buf != 0x00){free(pdevice->mmap_buf);}if (pdevice->fd != 0x00){ret = close(pdevice->fd);if(ret < 0){perror("close fd failed");}}return ret;
}int query_video_device_cap(struct _v4l2_video *pdevice)
{int ret = 0;struct v4l2_capability    cap = {0};struct v4l2_fmtdesc  fmtdesc = {0};      /* 查询摄像头信息 */ret = ioctl(pdevice->fd, VIDIOC_QUERYCAP, &cap);if(ret < 0){perror("ioctl call \'VIDEO_QUERYCAP\' failed \n");return -1;}printf("video driver name:%s\n", cap.driver);printf("video device name:%s\n", cap.card);printf("video bus information:%s\n", cap.bus_info);printf("video driver version:%d\n", cap.version);printf("video capabilities:%x\n", cap.capabilities);/* 检查设备是否支持视频捕获 */if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE){printf("the video device support capture\n");}/* 检查设备是否支持数据流 */if(!(cap.capabilities & V4L2_CAP_STREAMING)){printf("the video device support stream\n");}/* 查询设备支持的输出格式 */fmtdesc.index = 0 ;                fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;printf("the video device support format:\n");while(ioctl(pdevice->fd,VIDIOC_ENUM_FMT,&fmtdesc) != -1){ printf("%d.%s\n", fmtdesc.index+1, fmtdesc.description);fmtdesc.index++;}return ret;
}int set_video_device_par(struct _v4l2_video *pdevice)
{int ret = 0; struct v4l2_format format;/* 设置帧输出格式 */format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;       format.fmt.pix.width = 640;                        /* 像素宽度 */format.fmt.pix.height = 480;                 /* 像素高度 */format.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;  /* 输出格式,前提是摄像头支持该格式,V4L2_PIX_FMT_YYUV */format.fmt.pix.field = V4L2_FIELD_NONE;           ret = ioctl(pdevice->fd, VIDIOC_S_FMT, &format);if (ret < 0) {perror("ioctl call \'VIDIOC_S_FMT\' failed");}return ret;
}int set_video_device_mmap(struct _v4l2_video *pdevice)
{int ret = 0;int i = 0;struct v4l2_requestbuffers req_buf = {0};struct v4l2_buffer buf = {0};/* 申请内核缓存区 */pdevice->mmap_buf_cnt = 1;req_buf.count = 1;                 /* 缓存数目 */req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    req_buf.memory = V4L2_MEMORY_MMAP;               ret = ioctl(pdevice->fd, VIDIOC_REQBUFS, &req_buf);if(ret < 0){perror("ioctl call \'VIDIOC_REQBUFS\' failed");return -1;}pdevice->mmap_buf = malloc(req_buf.count * sizeof(struct _mmap_buf));if(pdevice->mmap_buf  == NULL){perror("malloc memory failed");return -1;}/* 将内核态内存映射到用户态 */for(i=0; i<req_buf.count; i++){buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index  = i;ret = ioctl(pdevice->fd, VIDIOC_QUERYBUF, &buf);if(ret < 0){perror("ioctl call \'VIDIOC_QUERYBUF\' failed");return -1;}pdevice->mmap_buf[i].size = buf.length;pdevice->mmap_buf[i].addr = (char *)mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, pdevice->fd, buf.m.offset);if(MAP_FAILED == pdevice->mmap_buf[i].addr){perror("mmap failed");return -1;}}return 0;
}int set_video_device_stream_queue(struct _v4l2_video *pdevice, int index)
{int ret = 0;struct v4l2_buffer buf = {0};/* 将内核缓存放入队列 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = index;ret = ioctl(pdevice->fd, VIDIOC_QBUF, &buf);if(ret < 0){perror("ioctl call \'VIDIOC_QBUF\' failed");return -1;}}int set_video_device_stream_on(struct _v4l2_video *pdevice)
{int ret = 0;int i;struct v4l2_buffer buf = {0};enum v4l2_buf_type type;/* 将内核缓存放入队列 */for (i=0; i<pdevice->mmap_buf_cnt; i++){set_video_device_stream_queue(pdevice, i);if(ret < 0){return -1;}}/* 开启数据流 */type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(pdevice->fd, VIDIOC_STREAMON, &type);if(ret < 0){perror("ioctl call \'VIDIOC_STREAMON\' failed");return 0;}return 0;
}int read_video_device_stream_frame(struct _v4l2_video *pdevice, int *out_buf_index)
{int ret = 0;int i;struct v4l2_buffer buf = {0};/* 从队列取出数据 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;ret = ioctl(pdevice->fd, VIDIOC_DQBUF, &buf);if(ret < 0){perror("ioctl call \'VIDIOC_DQBUF\' failed");return -1;}if (buf.index > pdevice->mmap_buf_cnt){printf("buf overflow[%d]\n", buf.index);}*out_buf_index = buf.index;return 0;
}int set_video_device_stream_off(struct _v4l2_video *pdevice)
{int ret = 0;enum v4l2_buf_type type;type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(pdevice->fd, VIDIOC_STREAMOFF, &type);    /* 关闭数据流 */if(ret < 0){perror("ioctl call \'VIDIOC_STREAMOFF\' failed");return -1;}return 0;
}int main(int argc, int **argv)
{FILE *fp = NULL;int index = 0;int ret = 0;int i = 0;char buf[12] = {0};struct _v4l2_video video;fd_set fds;struct timeval tv;if (argc < 2){printf("parameter invalid\n");return -1;}video.fd = open_video_device((const char*)argv[1]);if (video.fd < 0){return -1;}ret = query_video_device_cap(&video);if (ret < 0){goto __exit;}ret = set_video_device_par(&video);if (ret < 0){goto __exit;}ret = set_video_device_mmap(&video);if (ret < 0){goto __exit;}ret = set_video_device_stream_on(&video);if (ret < 0){goto __exit;}for (i=0; i<5; i++){/* 采集5张(帧)图片 */FD_ZERO(&fds);FD_SET(video.fd,&fds);tv.tv_sec = 5;    /* wait time */tv.tv_usec = 0;ret = select(video.fd + 1, &fds, NULL, NULL, &tv);if(ret < 0){perror("select error");goto __exit;}else if(ret == 0){printf("select timeout\n");goto __exit;}ret = read_video_device_stream_frame(&video, &index);if (ret < 0){goto __exit;}sprintf(buf, "./image%d.jpg", i);fp = fopen(buf, "wb");   /* 保存为图片文件 */if(fp == NULL){perror("open image file failed\n");goto __exit;}printf("save %s \n", buf);fwrite(video.mmap_buf[index].addr, video.mmap_buf[index].size, 1, fp);fclose(fp);set_video_device_stream_queue(&video, index);usleep(1000);}
__exit:set_video_device_stream_off(&video);close_video_device(&video);return 0;
}

编译测试

  • 系统:Ubuntu16
  • 摄像头:笔记本自带摄像头
acuity@ubuntu:/mnt/hgfs/LSW/STHB/camera$ gcc v4l2_base.c -o v4l2_base
acuity@ubuntu:/mnt/hgfs/LSW/STHB/camera$ ./v4l2_base /dev/video0
video driver name:uvcvideo
video device name:Integrated Camera: Integrated C
video bus information:usb-0000:03:00.0-2.1
video driver version:266002
video capabilities:84200001
the video device support capture
the video device support format:
1.YUYV 4:2:2
2.Motion-JPEG
save ./image0.jpg
save ./image1.jpg
save ./image2.jpg
save ./image3.jpg
save ./image4.jpg

  从执行结果来看,此笔记本自带的摄像头是USB接口的,支持YUV和JPEG格式输出。程序执行后在当前目录生成5张.jpg格式的图片文件。

acuity@ubuntu:/mnt/hgfs/LSW/STHB/camera$ tree
.
├── image0.jpg
├── image1.jpg
├── image2.jpg
├── image3.jpg
├── image4.jpg
├── v4l2_base
└── v4l2_base.c

4 参考文章

【1】linux v4l2摄像头应用层编程介绍

【2】和菜鸟一起学linux之V4L2摄像头应用流程

【音视频】V4L2摄像头应用编程相关推荐

  1. 【正点原子Linux连载】第二十章 V4L2摄像头应用编程-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  2. V4L2摄像头应用编程

    Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版.V4L2是linux操作系统下用于采集图片.视频和音频数据的API接口,配合适当的视频采集设备和相应的驱 ...

  3. linux的v4l2运行源码,linux v4l2摄像头应用层编程介绍

    一.前言 最近项目需要做一个工业条形读码器,在底层应该会适配linux v4l2框架,就自己研究了一下在应用层怎么使用v4l2进行编程,查阅了相关资料,主要是网上的博客资料,有: https://ww ...

  4. C++音视频编程探秘

    C++音视频编程探秘(C++ Audio and Video Programming Unveiled) 一.引言(Introduction) C++音视频编程简介(Overview of C++ A ...

  5. linux摄像头 sdl,Linux音视频(SDL与YUV)

    SDL是一个比较底层的音视频处理库,很多UI系统的内核都用到它,我们还可以用它来处理摄像头中YUV数据. 拓展: SDL(Simple DirectMedia Layer)是一个跨平台的底层开发库,提 ...

  6. uniapp怎么调起摄像头拍视频_抖音视频怎么拍?我们总结了10个手机视频拍摄小技巧...

    抖音的很多功能与小咖秀类似,但不同的是,抖音用户可以通过视频拍摄的快慢.视频编辑和特效等技术让作品更具创造性,而不是简单地对嘴型. 抖音短视频的10个拍摄技巧,帮助你方便.快捷地制作出更加优质的短视频 ...

  7. RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播

    RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播 摘要: RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播 RTMP(Real Time Messaging Pro ...

  8. 从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用

    从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用 原文地址:http://blog.yundiantech.com/?log=blog&id=10 前面介绍了使用F ...

  9. 从零开始学习音视频编程技术(六) FFMPEG Qt视频播放器之显示图像

    从零开始学习音视频编程技术(六) FFMPEG Qt视频播放器之显示图像 原文地址:http://blog.yundiantech.com/?log=blog&id=9 前面讲解了如何用FFM ...

最新文章

  1. STM32之独立版USB(Host)驱动+MSC+Fatfs移植
  2. python基础之文件操作,集合,函数
  3. 【拨云见日】全面云化时代,如何选择适合自己的“云”?
  4. iptables官方文档
  5. 预训练新范式!为什么Prompt会更有效?
  6. 各型号英特尔CUP的功率
  7. C语言调用拼多多api,vb CommonDialog 属性
  8. [Gamma阶段]第四次Scrum Meeting
  9. markdownpad2渲染组件出错——Awesomium sdk组件下载
  10. python自动填表单_用python-webdriver实现自动填表
  11. 利用pandas处理二级office的Excel试题(一)
  12. 最简单的python爬虫案例,适合入门学习
  13. 2022小米红米手机最新最全MIUI刷机教程内测版到稳定版 不清除数据(线刷、卡刷)
  14. 老师一天表情大全,也太搞笑了!
  15. FineReport报表设计基础
  16. Ubuntu18.04开机自动开启小键盘
  17. asp微信点餐系统源码,asp扫码点餐代码,支持连接飞鹅云打印机
  18. 近期有面试的必看!带你手撸红黑树,终获offer
  19. 苹果7p服务器维护中,苹果7p无服务怎么解决
  20. BibTeX的使用方法

热门文章

  1. XOI2003赛后题解
  2. Zeppplin的安装,配置与使用
  3. C++ 全局变量 静态全局变量 傻傻分不清
  4. mac pro 2015 升级1T固态硬盘极简版本(三星970 evo plus)
  5. 网易汪源:网易产品体验好,离不开AI驱动
  6. stat() /root/xxx/index.html failed (13: Permission denied)
  7. GitHub上热门的Java开源项目
  8. Py网络编程及应用(urllib、socket/selectors)
  9. 做游戏,学编程(C语言) 15 太鼓达人
  10. 哥谭第一季/全集Gotham迅雷下载