当前位置:   article > 正文

v4l2采集视频

v4l2采集视频

        Video4Linux2(v4l2)是用于Linux系统的视频设备驱动框架,它允许用户空间应用程序直接与视频设备(如摄像头、视频采集卡等)进行交互。

        linux系统下一切皆文件,对视频设备的操作就像对文件的操作一样,使用类似读取、写入文件的方式来进行,v4l2也都是通过open()、ioctl()、read()、close()来实现对视频设备的操作。

        以下是使用v4l2获取视频流的一般流程:

        1、打开设备:

        首先,应用程序需要打开要使用的视频设备。通常,这可以通过调用open()系统调用来完成,传递设备文件的路径作为参数。例如,摄像头通常会以/dev/videoX的形式出现,其中X是数字。

  1. // O_NONBLOCK以非阻塞方式打开
  2. fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);

        2、查询设备能力、设置视频格式:

        一旦设备打开,应用程序通常会查询设备的能力,例如支持的视频格式、分辨率、帧率等信息。这可以通过调用ioctl()系统调用来执行VIDIOC_QUERYCAP操作来完成。根据设备的能力和应用程序的需求,可以设置所需的视频格式。这包括像素格式、分辨率、帧率等。通常,可以使用VIDIOC_S_FMT操作来设置视频格式。

        ioctl第二个参数表示命令类型,第三个参数是变参,不同请求命令对应不同的参数

int ioctl(int fd, unsigned long request, ...);

        v4l2请求命令和请求参数在/usr/include/linux/videodev2.h里面定义

        查询设备信息:

  1. /*
  2. * struct v4l2_capability {
  3. * __u8 driver[16]; // 驱动模块的名称(例如 "bttv")
  4. * __u8 card[32]; // 设备的名称(例如 "Hauppauge WinTV")
  5. * __u8 bus_info[32]; // 总线的名称(例如 "PCI:" + pci_name(pci_dev))
  6. * __u32 version; // KERNEL_VERSION
  7. * __u32 capabilities; // 整个物理设备的功能
  8. * __u32 device_caps; // 通过此特定设备(节点)访问的功能
  9. * __u32 reserved[3]; // 保留字段,用于未来扩展
  10. * };
  11. */
  12. struct v4l2_capability cap;
  13. if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
  14. perror("VIDIOC_QUERYCAP");
  15. return -1;
  16. }

        查询摄像头支持的格式:

  1. /*
  2. * struct v4l2_fmtdesc {
  3. * __u32 index; // 格式编号,摄像头可能支持多种格式,查看的时候需要指定格式编号,循环遍历获取所有支持的格式
  4. * __u32 type; // 枚举 v4l2_buf_type
  5. * __u32 flags;
  6. * __u8 description[32]; // 描述字符串
  7. * __u32 pixelformat; // 格式 FourCC,通常由四个ASCII字符组成,用于唯一地标识特定的数据格式或编码方式
  8. * __u32 reserved[4];
  9. * };
  10. */
  11. struct v4l2_fmtdesc fmtdesc;
  12. fmtdesc.index = 0;
  13. fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  14. while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
  15. printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
  16. fmtdesc.index++;
  17. }

        设置采集的视频格式:

  1. struct v4l2_format fmt;
  2. CLEAN(fmt);
  3. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4. fmt.fmt.pix.width = WIDTH;
  5. fmt.fmt.pix.height = HEIGHT;
  6. fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
  7. if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
  8. printf("VIDIOC_S_FMT IS ERROR! LINE:%d\n", __LINE__);
  9. //return -1;
  10. }

        struct v4l2_format是v4l2中一个很重要的结构体,用于视频格式设置,定义如下:

  1. struct v4l2_format {
  2. __u32 type;
  3. union {
  4. struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
  5. struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
  6. struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
  7. struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
  8. struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
  9. struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
  10. struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */
  11. __u8 raw_data[200]; /* user-defined */
  12. } fmt;
  13. };

        type是枚举类型enum v4l2_buf_type用于表示数据流格式,定义如下:

  1. enum v4l2_buf_type {
  2. V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, // 视频捕获类型,用于从视频设备捕获图像数据(从摄像头获取实时视频、视频编解码)
  3. V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, // 视频输出类型,用于将图像数据输出到视频设备(视频编解码)
  4. V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, // 视频叠加类型,用于叠加图像或视频
  5. V4L2_BUF_TYPE_VBI_CAPTURE = 4, // 垂直空白间隔(VBI)捕获类型,用于捕获VBI数据
  6. V4L2_BUF_TYPE_VBI_OUTPUT = 5, // VBI输出类型,用于输出VBI数据
  7. V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, // 切片VBI捕获类型,用于捕获切片VBI数据
  8. V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, // 切片VBI输出类型,用于输出切片VBI数据
  9. V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, // 视频输出叠加类型,用于输出叠加图像或视频
  10. V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9, // 多平面视频捕获类型,用于从多平面视频设备捕获图像数据(视频编解码)
  11. V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10, // 多平面视频输出类型,用于将图像数据输出到多平面视频设备(视频编解码)
  12. V4L2_BUF_TYPE_SDR_CAPTURE = 11, // SDR捕获类型,用于从SDR设备捕获数据
  13. V4L2_BUF_TYPE_SDR_OUTPUT = 12, // SDR输出类型,用于将数据输出到SDR设备
  14. V4L2_BUF_TYPE_META_CAPTURE = 13, // 元数据捕获类型,用于从设备捕获元数据
  15. /* 已废弃,请勿使用 */
  16. V4L2_BUF_TYPE_PRIVATE = 0x80, // 私有类型,用于自定义和扩展目的
  17. };

       v4l2_format中的fmt是联合体,不同的type使用不用类型的结构体,V4L2_BUF_TYPE_VIDEO_CAPTURE使用v4l2_pix_format。定义如下:

  1. struct v4l2_pix_format {
  2. __u32 width; // 图像宽度
  3. __u32 height; // 图像高度
  4. __u32 pixelformat; // 像素格式,使用 FOURCC 表示
  5. __u32 field; // 视频场类型,枚举 v4l2_field
  6. __u32 bytesperline; // 每行字节数,用于填充,如果未使用则为零
  7. __u32 sizeimage; // 图像数据大小
  8. __u32 colorspace; // 颜色空间,枚举 v4l2_colorspace
  9. __u32 priv; // 私有数据,依赖于像素格式
  10. __u32 flags; // 格式标志(V4L2_PIX_FMT_FLAG_*
  11. union {
  12. // YCbCr 编码
  13. __u32 ycbcr_enc;
  14. // HSV 编码
  15. __u32 hsv_enc;
  16. };
  17. __u32 quantization; // 量化方式,枚举 v4l2_quantization
  18. __u32 xfer_func; // 传输函数,枚举 v4l2_xfer_func
  19. };

        V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE使用struct v4l2_pix_format_mplane,一般用于v4l2视频编解码中,定义如下:

  1. struct v4l2_pix_format_mplane {
  2. __u32 width; // 图像宽度
  3. __u32 height; // 图像高度
  4. __u32 pixelformat; // 图像像素格式 小端四字符代码(FourCC)
  5. __u32 field; // 图像字段顺序 enum v4l2_field; 字段顺序(用于隔行扫描视频)
  6. __u32 colorspace; // 图像色彩空间 enum v4l2_colorspace; 与 pixelformat 相关的补充信息
  7. struct v4l2_plane_pix_format plane_fmt[VIDEO_MAX_PLANES]; // 平面格式数组 每个平面的信息
  8. __u8 num_planes; // 平面数量 此格式的平面数量
  9. __u8 flags; // 格式标志(V4L2_PIX_FMT_FLAG_*
  10. union {
  11. __u8 ycbcr_enc; // enum v4l2_ycbcr_encoding, Y'CbCr 编码
  12. __u8 hsv_enc; // enum v4l2_quantization, 色彩空间量化
  13. };
  14. __u8 quantization; // 色彩空间量化
  15. __u8 xfer_func; // enum v4l2_xfer_func, 色彩空间传输函数
  16. __u8 reserved[7]; // 保留字段
  17. } __attribute__ ((packed));
  18. struct v4l2_plane_pix_format {
  19. __u32 sizeimage; // 用于此平面的数据所需的最大字节数
  20. __u32 bytesperline; // 相邻两行中最左侧像素之间的字节距离
  21. __u16 reserved[6]; // 保留字段
  22. } __attribute__ ((packed));

        3、请求和分配缓冲区:

        应用程序需要请求并分配用于存储视频数据的缓冲区。这可以通过调用VIDIOC_REQBUFS操作来请求缓冲区,并使用VIDIOC_QUERYBUF操作来获取每个缓冲区的详细信息。然后,应用程序将缓冲区映射到当前进程空间。

        向内核申请多个缓冲区:

  1. /*
  2. *struct v4l2_requestbuffers {
  3. * __u32 count; // 请求的缓冲区数量
  4. * __u32 type; // 数据流类型,枚举 v4l2_buf_type
  5. * __u32 memory; // 缓冲区的内存类型,枚举 v4l2_memory
  6. * __u32 reserved[2]; /* 保留字段,用于未来扩展
  7. *};
  8. */
  9. struct v4l2_requestbuffers req;
  10. CLEAN(req);
  11. req.count = 4;
  12. req.memory = V4L2_MEMORY_MMAP; // 使用内存映射缓冲区
  13. req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  14. // 申请4个帧缓冲区,在内核空间中
  15. if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
  16. printf("VIDIOC_REQBUFS IS ERROR! LINE:%d\n", __LINE__);
  17. return -1;
  18. }

      把内核缓冲区映射到当前进程空间,这样进程就可以直接读写这个地址的数据,之后缓冲区入内核队列,准备采集视频:

  1. /*
  2. * typedef struct BufferSt {
  3. * void *start;
  4. * unsigned int length;
  5. * } BufferSt;
  6. */
  7. buffer = (BufferSt *)calloc(req.count, sizeof(BufferSt));
  8. if (buffer == NULL) {
  9. printf("calloc is error! LINE:%d\n", __LINE__);
  10. return -1;
  11. }
  12. struct v4l2_buffer buf;
  13. int buf_index = 0;
  14. for (buf_index = 0; buf_index < req.count; buf_index++) {
  15. CLEAN(buf);
  16. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  17. buf.index = buf_index;
  18. buf.memory = V4L2_MEMORY_MMAP;
  19. if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) // 获取每个帧缓冲区的信息 如length和offset
  20. {
  21. printf("VIDIOC_QUERYBUF IS ERROR! LINE:%d\n", __LINE__);
  22. return -1;
  23. }
  24. // 将内核空间中的帧缓冲区映射到用户空间
  25. buffer[buf_index].length = buf.length;
  26. buffer[buf_index].start = mmap(NULL, // 由内核分配映射的起始地址
  27. buf.length, // 长度
  28. PROT_READ | PROT_WRITE, // 可读写
  29. MAP_SHARED, // 可共享
  30. fd,
  31. buf.m.offset);
  32. if (buffer[buf_index].start == MAP_FAILED) {
  33. printf("MAP_FAILED LINE:%d\n", __LINE__);
  34. return -1;
  35. }
  36. // 将帧缓冲区放入视频输入队列
  37. if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
  38. printf("VIDIOC_QBUF IS ERROR! LINE:%d\n", __LINE__);
  39. return -1;
  40. }
  41. printf("Frame buffer :%d address :0x%x length:%d\n", buf_index, (__u32)buffer[buf_index].start, buffer[buf_index].length);
  42. }

          struct v4l2_buffer是v4l2中另一个重要的结构体,用来定义缓冲区:

  1. struct v4l2_buffer {
  2. __u32 index; // 缓冲区的ID号
  3. __u32 type; // 枚举 v4l2_buf_type; 缓冲区类型(type == *_MPLANE 表示多平面缓冲区)
  4. __u32 bytesused; // 枚举 v4l2_field; 缓冲区中图像的场序
  5. __u32 field; // 缓冲区中图像的场序
  6. struct timeval timestamp; // 帧时间戳
  7. struct v4l2_timecode timecode; // 帧时间码
  8. __u32 sequence; // 本帧的序列计数
  9. /* 内存位置 */
  10. __u32 memory; // 枚举 v4l2_memory; 传递实际视频数据的方法
  11. union {
  12. __u32 offset; // 对于 memory == V4L2_MEMORY_MMAP 的非多平面缓冲区;从设备内存开始的偏移量
  13. unsigned long userptr; // 对于 memory == V4L2_MEMORY_USERPTR 的非多平面缓冲区;指向该缓冲区的用户空间指针
  14. struct v4l2_plane *planes; // 对于多平面缓冲区;指向该缓冲区的平面信息结构数组的用户空间指针
  15. __s32 fd; // 对于 memory == V4L2_MEMORY_DMABUF 的非多平面缓冲区;与该缓冲区相关联的用户空间文件描述符
  16. } m;
  17. __u32 length; // 单平面缓冲区的缓冲区大小(而不是有效载荷)的字节数(当 type != *_MPLANE 时) 对于多平面缓冲区,表示平面数组中的元素数(当 type == *_MPLANE 时)
  18. __u32 reserved2;
  19. __u32 reserved;
  20. };

       当是多平面是,v4l2_buffer的m使用struct v4l2_plane,这个一般在v4l2视频编解码中使用,用于存储原始视频的Y U V分量,定义如下:

  1. struct v4l2_plane {
  2. __u32 bytesused; // 平面中数据所占用的字节数(有效载荷)
  3. __u32 length; // 该平面的大小(而不是有效载荷)的字节数
  4. union {
  5. __u32 mem_offset; // 当 memory 为 V4L2_MEMORY_MMAP 时,从设备内存开始的偏移量
  6. unsigned long userptr; // 当 memory 为 V4L2_MEMORY_USERPTR 时,指向该平面的用户空间指针
  7. __s32 fd; // 当 memory 为 V4L2_MEMORY_DMABUF 时,与该平面相关联的用户空间文件描述符
  8. } m;
  9. __u32 data_offset; // 平面中数据开始的偏移量
  10. __u32 reserved[11];
  11. };

         4、开始捕获视频:

        一旦缓冲区准备就绪,应用程序可以调用VIDIOC_STREAMON操作来开始捕获视频流。此时,设备将开始向分配的缓冲区写入视频数据。

  1. enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  2. if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
  3. printf("VIDIOC_STREAMON IS ERROR! LINE:%d\n", __LINE__);
  4. exit(1);
  5. }

        5、获取和处理视频帧:

        应用程序可以轮询或使用异步IO等机制从缓冲区中获取视频帧数据。获取数据后,应用程序可以对视频帧进行处理,例如显示、存储或传输等操作。

  1. static int read_frame()
  2. {
  3. struct v4l2_buffer buf;
  4. int ret = 0;
  5. CLEAN(buf);
  6. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  7. buf.memory = V4L2_MEMORY_MMAP;
  8. if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
  9. printf("VIDIOC_DQBUF! LINEL:%d\n", __LINE__);
  10. return -1;
  11. }
  12. ret = write(out_fd, buffer[buf.index].start, buf.bytesused);
  13. if (ret == -1) {
  14. printf("write is error !\n");
  15. return -1;
  16. }
  17. if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
  18. printf("VIDIOC_QBUF! LINE:%d\n", __LINE__);
  19. return -1;
  20. }
  21. return 0;
  22. }
  23. static int capture_frame()
  24. {
  25. struct timeval tvptr;
  26. int ret;
  27. tvptr.tv_usec = 0;
  28. tvptr.tv_sec = 2;
  29. fd_set fdread;
  30. FD_ZERO(&fdread);
  31. FD_SET(fd, &fdread);
  32. ret = select(fd + 1, &fdread, NULL, NULL, &tvptr);
  33. if (ret == -1) {
  34. perror("select");
  35. exit(1);
  36. }
  37. if (ret == 0) {
  38. printf("timeout! \n");
  39. return -1;
  40. }
  41. read_frame();
  42. }

        过程为:从内核队列中取出准备好buf、从buf映射到进程空间的内存中读取视频数据、buf重新送入内核缓冲队列中,因为open的时候使用的非阻塞IO,所以这里使用select监听。

        6、停止捕获视频:

        当视频采集完成时,应用程序可以调用VIDIOC_STREAMOFF操作来停止视频捕获。

  1. enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  2. if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
  3. printf("VIDIOC_STREAMOFF IS ERROR! LINE:%d\n", __LINE__);
  4. exit(1);
  5. }

        7、解除缓冲区内存映射:

        结束内核缓冲区到当前进程空间的内存映射

  1. int i = 0;
  2. for (i = 0; i < 4; i++) {
  3. munmap(buffer[i].start, buffer[i].length);
  4. }
  5. free(buffer);

        8、关闭设备:

        最后,应用程序应该关闭视频设备,释放所有相关的资源。这可以通过调用close()系统调用来完成。

close(fd);

        9、完整代码:

        代码流程图如下:

        保存的视频用yuvplayer播放。

  1. #include <asm/types.h>
  2. #include <assert.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #include <getopt.h>
  6. #include <linux/fb.h>
  7. #include <linux/videodev2.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <sys/ioctl.h>
  12. #include <sys/mman.h>
  13. #include <sys/stat.h>
  14. #include <unistd.h>
  15. #define CLEAN(x) (memset(&(x), 0, sizeof(x)))
  16. #define WIDTH 640
  17. #define HEIGHT 480
  18. typedef struct BufferSt {
  19. void *start;
  20. unsigned int length;
  21. } BufferSt;
  22. int fd;
  23. int out_fd;
  24. static BufferSt *buffer = NULL;
  25. static int query_set_format()
  26. {
  27. // 查询设备信息
  28. struct v4l2_capability cap;
  29. if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
  30. perror("VIDIOC_QUERYCAP");
  31. return -1;
  32. }
  33. printf("DriverName:%s\nCard Name:%s\nBus info:%s\nDriverVersion:%u.%u.%u\n",
  34. cap.driver, cap.card, cap.bus_info, (cap.version >> 16) & 0xFF, (cap.version >> 8) & 0xFF, (cap.version) & 0xFF);
  35. // 查询帧格式
  36. struct v4l2_fmtdesc fmtdesc;
  37. fmtdesc.index = 0;
  38. fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  39. while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
  40. printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
  41. fmtdesc.index++;
  42. }
  43. // 设置帧格式
  44. struct v4l2_format fmt;
  45. CLEAN(fmt);
  46. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  47. fmt.fmt.pix.width = WIDTH;
  48. fmt.fmt.pix.height = HEIGHT;
  49. fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
  50. if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
  51. printf("VIDIOC_S_FMT IS ERROR! LINE:%d\n", __LINE__);
  52. //return -1;
  53. }
  54. // 上面设置帧格式可能失败,这里需要查看一下实际帧格式
  55. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  56. if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
  57. printf("VIDIOC_G_FMT IS ERROR! LINE:%d\n", __LINE__);
  58. return -1;
  59. }
  60. printf("width:%d\nheight:%d\npixelformat:%c%c%c%c\n",
  61. fmt.fmt.pix.width, fmt.fmt.pix.height,
  62. fmt.fmt.pix.pixelformat & 0xFF,
  63. (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
  64. (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
  65. (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
  66. return 0;
  67. }
  68. static int request_allocate_buffers()
  69. {
  70. // 申请帧缓冲区
  71. struct v4l2_requestbuffers req;
  72. CLEAN(req);
  73. req.count = 4;
  74. req.memory = V4L2_MEMORY_MMAP; // 使用内存映射缓冲区
  75. req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  76. // 申请4个帧缓冲区,在内核空间中
  77. if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
  78. printf("VIDIOC_REQBUFS IS ERROR! LINE:%d\n", __LINE__);
  79. return -1;
  80. }
  81. // 获取每个帧信息,并映射到用户空间
  82. buffer = (BufferSt *)calloc(req.count, sizeof(BufferSt));
  83. if (buffer == NULL) {
  84. printf("calloc is error! LINE:%d\n", __LINE__);
  85. return -1;
  86. }
  87. struct v4l2_buffer buf;
  88. int buf_index = 0;
  89. for (buf_index = 0; buf_index < req.count; buf_index++) {
  90. CLEAN(buf);
  91. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  92. buf.index = buf_index;
  93. buf.memory = V4L2_MEMORY_MMAP;
  94. if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) // 获取每个帧缓冲区的信息 如length和offset
  95. {
  96. printf("VIDIOC_QUERYBUF IS ERROR! LINE:%d\n", __LINE__);
  97. return -1;
  98. }
  99. // 将内核空间中的帧缓冲区映射到用户空间
  100. buffer[buf_index].length = buf.length;
  101. buffer[buf_index].start = mmap(NULL, // 由内核分配映射的起始地址
  102. buf.length, // 长度
  103. PROT_READ | PROT_WRITE, // 可读写
  104. MAP_SHARED, // 可共享
  105. fd,
  106. buf.m.offset);
  107. if (buffer[buf_index].start == MAP_FAILED) {
  108. printf("MAP_FAILED LINE:%d\n", __LINE__);
  109. return -1;
  110. }
  111. // 将帧缓冲区放入视频输入队列
  112. if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
  113. printf("VIDIOC_QBUF IS ERROR! LINE:%d\n", __LINE__);
  114. return -1;
  115. }
  116. printf("Frame buffer :%d address :0x%x length:%d\n", buf_index, (__u32)buffer[buf_index].start, buffer[buf_index].length);
  117. }
  118. }
  119. static void start_capture()
  120. {
  121. enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  122. if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
  123. printf("VIDIOC_STREAMON IS ERROR! LINE:%d\n", __LINE__);
  124. exit(1);
  125. }
  126. }
  127. static void end_capture()
  128. {
  129. enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  130. if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
  131. printf("VIDIOC_STREAMOFF IS ERROR! LINE:%d\n", __LINE__);
  132. exit(1);
  133. }
  134. }
  135. static int read_frame()
  136. {
  137. struct v4l2_buffer buf;
  138. int ret = 0;
  139. CLEAN(buf);
  140. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  141. buf.memory = V4L2_MEMORY_MMAP;
  142. if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
  143. printf("VIDIOC_DQBUF! LINEL:%d\n", __LINE__);
  144. return -1;
  145. }
  146. ret = write(out_fd, buffer[buf.index].start, buf.bytesused);
  147. if (ret == -1) {
  148. printf("write is error !\n");
  149. return -1;
  150. }
  151. if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
  152. printf("VIDIOC_QBUF! LINE:%d\n", __LINE__);
  153. return -1;
  154. }
  155. return 0;
  156. }
  157. static void unmap_buffer()
  158. {
  159. int i = 0;
  160. for (i = 0; i < 4; i++) {
  161. munmap(buffer[i].start, buffer[i].length);
  162. }
  163. free(buffer);
  164. }
  165. static int capture_frame()
  166. {
  167. struct timeval tvptr;
  168. int ret;
  169. tvptr.tv_usec = 0;
  170. tvptr.tv_sec = 2;
  171. fd_set fdread;
  172. FD_ZERO(&fdread);
  173. FD_SET(fd, &fdread);
  174. ret = select(fd + 1, &fdread, NULL, NULL, &tvptr);
  175. if (ret == -1) {
  176. perror("select");
  177. exit(1);
  178. }
  179. if (ret == 0) {
  180. printf("timeout! \n");
  181. return -1;
  182. }
  183. read_frame();
  184. }
  185. int main(int argc, char *argv[])
  186. {
  187. // 1、打开摄像头
  188. fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
  189. if (fd == -1) {
  190. printf("can not open '%s'\n", "/dev/video0");
  191. return -1;
  192. }
  193. out_fd = open("./out.yuv", O_RDWR | O_CREAT, 0777);
  194. if (out_fd == -1) {
  195. printf("open out file is error!\n");
  196. return -1;
  197. }
  198. // 2、查询设备、设置视频格式
  199. query_set_format();
  200. // 3、请求和分配缓冲区
  201. request_allocate_buffers();
  202. // 4 、开始捕获视频
  203. start_capture();
  204. // 5、获取和处理视频帧
  205. for (int i = 0; i < 20; i++) {
  206. capture_frame();
  207. printf("frame:%d\n", i);
  208. }
  209. // 6、停止捕获视频
  210. end_capture();
  211. // 7、解除缓冲区内存映射
  212. unmap_buffer();
  213. // 8、关闭摄像头
  214. close(fd);
  215. close(out_fd);
  216. return 0;
  217. }

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号