V4L2采集摄像头视频

V4L2(Video for Linux Two),是Linux内核提供的一组API,用于与视频设备(如网络摄像头、电视卡、USB摄像头等)交互,支持视频的采集和输出。

一、基本实现流程

        1.打开设备

        在linux系统中,摄像头以文件的形式存在,一般是在/dev文件夹下,通常以‘video+数字’作为文件名。所以开启摄像头设备其实就是打开相应的设备文件。

fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);

        2.设置视频的数据格式

        通过ioctl系统调用接口设置要采集视频的数据格式,一般至少需要包括视频的宽高,像素格式。ioctl函数出现错误时返回-1,可以据此对它做错误处理。

struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
ioctl(fd, VIDIOC_S_FMT, &fmt);

        需要额外注意的时,这时的设置未必一定有效,而未生效时,则由系统自动设置无法生效的属性,而且此时没有错误提示。所以在完成设置用最好再查看一下有没有生效。

    ioctl(fd, VIDIOC_G_FMT, &fmt;
    printf("width:%d\nheight:%d\npixelformat:%c%c%c%c\n",
           fmt.fmt.pix.width, fmt.fmt.pix.height,
           fmt.fmt.pix.pixelformat & 0xFF,
           (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
           (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
           (fmt.fmt.pix.pixelformat >> 24) & 0xFF);

        3.请求并分配缓冲区

        在设备采集之前需要为采集到的数据分配缓冲区,内核和驱动会负责填充这些缓冲区。但是这些缓冲区数据是保存在内核空间中,可以使用内存映射数据映射到用户空间。

    typedef struct BufferSt
    {
        void *start;
        unsigned int length;
    } BufferSt;        //用于管理地址映射的用户空间地址
    struct v4l2_requestbuffers req;
    struct v4l2_buffer buf;
    req.count = 4;
    req.memory = V4L2_MEMORY_MMAP;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl(fd, VIDIOC_REQBUFS, &req))
    {
        perror("VIDIOC_REQBUFS ERROR");
        close(fd);
        exit(-1);
    }
    buffer = (BufferSt *)calloc(req.count, sizeof(BufferSt));
    for (int buf_index = 0; buf_index < req.count; buf_index++)
    {
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.index = buf_index;
        buf.memory = V4L2_MEMORY_MMAP;
        if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
        {
            perror("VIDIOC_QUERYBUF error\n");
            goto error;
        }
        buffer[buf_index].length = buf.length;
        buffer[buf_index].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffer[buf_index].start == MAP_FAILED)
        {
            perror("Mapping buffer\n");
            goto error;
        }
        if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
        {
            perror("VIDIOC_QBUF error\n");
            goto error;
        }
    }

        4.开启流采集

        分配完内存后,就可以开始采集视频数据。开启流的方式非常简单,使用ioctl函数设置VIDIOC_STREAMON即可。

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);

        5.读数据

        前面已经完成了内存地址映射,所以当采集到数据后可以直接从用户空间取出数据。为了能够知道什么时候去取数据,可以使用io复用监听是否有可读事件的发生。

    struct pollfd pfd = {fd, POLL_IN};
    while (1)
    {
        ret = poll(&pfd, 1, -1);
        if (ret < 0)
        {
            perror("Polling for frame\n");
            break;
        }

        for (unsigned int i = 0; i < req.count; ++i)
        {
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index = i;
            ret = ioctl(fd, VIDIOC_DQBUF, &buf); // 出队列获取数据
            if (ret != -1)
                fwrite(buffer[buf.index].start, 1, buf.length, outfp);    //将数据写入文件
            ioctl(fd, VIDIOC_QBUF, &buf);
        }
    }

        6.关闭流采集

        完成采集工作之后需要手动关闭流,并释放缓冲区资源,同时需要注意文件符的关闭和内存清理等工作。

    ioctl(fd, VIDIOC_STREAMOFF, &type);
    ioctl(fd, VIDIOC_REQBUFS, &buf); // 释放所有缓冲区

二、完整代码

#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <poll.h>
#include <signal.h>

#define WIDTH 640
#define HEIGHT 480

#define CLEAR(x) memset(&(x), 0, sizeof(x))

typedef struct BufferSt
{
    void *start;
    unsigned int length;
} BufferSt;

void alarm_handler(int signum)
{
    printf("Alarm signal received!\n");
    exit(0);
}

int main()
{
    int fd = -1;
    struct v4l2_format fmt;
    struct v4l2_buffer buf;
    enum v4l2_buf_type type;
    int ret;
    BufferSt *buffer = nullptr;
    FILE *outfp;
    struct sigaction sa;
    sa.sa_handler = alarm_handler;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGALRM, &sa, NULL) == -1)
    {
        perror("sigaction");
        return 1;
    }
    // 打开视频设备
    fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
    struct pollfd pfd = {fd, POLL_IN};
    alarm(10);
    if (fd < 0)
    {
        perror("Opening video device");
        return 1;
    }

    // 设置视频采集的格式
    CLEAR(fmt);
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
    {
        perror("VIDIOC_S_FMT ERROR");
        close(fd);
        exit(-1);
    }

    if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)
    {
        printf("VIDIOC_G_FMT IS ERROR! LINE:%d\n", __LINE__);
        return -1;
    }
    printf("width:%d\nheight:%d\npixelformat:%c%c%c%c\n",
           fmt.fmt.pix.width, fmt.fmt.pix.height,
           fmt.fmt.pix.pixelformat & 0xFF,
           (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
           (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
           (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
    struct v4l2_requestbuffers req;
    CLEAR(req);
    req.count = 4;
    req.memory = V4L2_MEMORY_MMAP;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl(fd, VIDIOC_REQBUFS, &req))
    {
        perror("VIDIOC_REQBUFS ERROR");
        close(fd);
        exit(-1);
    }
    buffer = (BufferSt *)calloc(req.count, sizeof(BufferSt));
    for (int buf_index = 0; buf_index < req.count; buf_index++)
    {
        CLEAR(buf);
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.index = buf_index;
        buf.memory = V4L2_MEMORY_MMAP;
        if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
        {
            perror("VIDIOC_QUERYBUF error\n");
            goto error;
        }
        buffer[buf_index].length = buf.length;
        buffer[buf_index].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffer[buf_index].start == MAP_FAILED)
        {
            perror("Mapping buffer\n");
            goto error;
        }
        if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
        {
            perror("VIDIOC_QBUF error\n");
            goto error;
        }
    }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMON, &type);

    // 打开文件准备写入YUV数据
    outfp = fopen("output.yuv", "wb");
    if (!outfp)
    {
        perror("Opening output file\n");
        goto error;
    }

    // 使用poll等待视频数据
    while (1)
    {
        ret = poll(&pfd, 1, -1);
        if (ret < 0)
        {
            perror("Polling for frame\n");
            break;
        }

        CLEAR(buf);
        for (unsigned int i = 0; i < req.count; ++i)
        {
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index = i;
            ret = ioctl(fd, VIDIOC_DQBUF, &buf); // 出队列获取数据
            if (ret != -1)
                fwrite(buffer[buf.index].start, 1, buf.length, outfp);
            ioctl(fd, VIDIOC_QBUF, &buf);
        }
    }

error:
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMON, &type);
    if (outfp)
    {
        fclose(outfp); // 关闭输出文件
        outfp = nullptr;
    }
    for (unsigned int i = 0; i < req.count; ++i)
    {
        munmap(buffer[i].start, buf.length);
    }
    if (fd > 0)
    {
        close(fd);
        fd = -1;
    }
    ioctl(fd, VIDIOC_STREAMOFF, &type);
    ioctl(fd, VIDIOC_REQBUFS, &buf); // 释放所有缓冲区

    return 0;
}

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值