正点原子V4L2 捕获摄像头视频帧

首先ls -l /dev/video* 查看视频节点

然后选择对应的节点 在代码里修改即可下面我附上的是完整的代码 编译后直接运行

总体过程就是 先需要使用VIDIOC_QUERYCAP查询一下能力

看一下是能力是单面还是多面  

然后 列出支持的格式 以及格式对应的分辨率

列出之后,逐个尝试设置,看哪个可以 设置完成后

再get一下看对不对

然后申请缓存区 映射缓存区  将缓存区放到队列 

完成之后就是开始捕获视频

使用select 抓fd 抓一帧 (这里是判断数据来没来)

来了之后,把队列拿下来 然后取数据  

取完数据 存数据 验证数据  然后把buff放回队列  停止捕获 释放缓存区(申请的)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/videodev2.h>

#define DEVICE_PATH "/dev/video1"
#define NUM_BUFFERS 4
#define MAX_FORMATS 16

// 列出设备支持的格式
void list_supported_formats(int fd) {
    struct v4l2_fmtdesc fmtdesc = {0};
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    
    printf("设备支持的格式:\n");
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
        printf("  %c%c%c%c: %s\n",
               fmtdesc.pixelformat & 0xFF,
               (fmtdesc.pixelformat >> 8) & 0xFF,
               (fmtdesc.pixelformat >> 16) & 0xFF,
               (fmtdesc.pixelformat >> 24) & 0xFF,
               fmtdesc.description);
        
        // 列出此格式支持的分辨率
        struct v4l2_frmsizeenum frmsize = {0};
        frmsize.pixel_format = fmtdesc.pixelformat;
        frmsize.index = 0;
        
        printf("    支持的分辨率:\n");
        while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) {
            if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
                printf("      %dx%d\n", 
                       frmsize.discrete.width, 
                       frmsize.discrete.height);
            } else if (frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
                printf("      连续范围: %dx%d - %dx%d\n", 
                       frmsize.stepwise.min_width, 
                       frmsize.stepwise.min_height,
                       frmsize.stepwise.max_width, 
                       frmsize.stepwise.max_height);
            } else if (frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
                printf("      步进范围: %dx%d - %dx%d (步长: %dx%d)\n", 
                       frmsize.stepwise.min_width, 
                       frmsize.stepwise.min_height,
                       frmsize.stepwise.max_width, 
                       frmsize.stepwise.max_height,
                       frmsize.stepwise.step_width,
                       frmsize.stepwise.step_height);
            }
            frmsize.index++;
        }
        
        fmtdesc.index++;
    }
}

int main(int argc, char *argv[]) {
    int fd;
    struct v4l2_format fmt;
    struct v4l2_requestbuffers req;
    struct v4l2_buffer buf;
    struct v4l2_plane planes[NUM_BUFFERS];
    unsigned char *buffers[NUM_BUFFERS] = {0};
    enum v4l2_buf_type type;
    int buffer_count;
    fd_set fds;
    struct timeval timeout;
    
    // 优先尝试的像素格式列表
    unsigned int pixel_formats[MAX_FORMATS] = {
        V4L2_PIX_FMT_MJPEG,     // 优先使用MJPEG (JPEG压缩)
        V4L2_PIX_FMT_NV12,      // 其次NV12 (YUV 4:2:0)
        V4L2_PIX_FMT_UYVY,      // 然后UYVY (YUV 4:2:2)
        V4L2_PIX_FMT_YUYV       // 最后YUYV (YUV 4:2:2)
    };
    
    // 尝试的分辨率列表
    struct {
        int width;
        int height;
    } resolutions[] = {
        {640, 480},
        {800, 600},
        {1280, 720},
        {1920, 1080}
    };
    
    int resolution_count = sizeof(resolutions) / sizeof(resolutions[0]);

    // 打开设备
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("打开设备失败");
        return -1;
    }

    // 查询设备能力
    struct v4l2_capability cap;
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
        perror("查询设备能力失败");
        close(fd);
        return -1;
    }
    
    printf("设备: %s, 驱动: %s\n", cap.card, cap.driver);
    printf("视频捕获支持: %s\n", 
           (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ? "是" : "否");
    printf("多平面支持: %s\n", 
           (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) ? "是" : "否");

    // 列出支持的格式
    list_supported_formats(fd);

    // 设置格式 (尝试多种格式和分辨率)
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    
    int format_found = 0;
    int resolution_found = 0;
    
    // 尝试所有格式和分辨率组合
    for (int i = 0; i < MAX_FORMATS; i++) {
        if (pixel_formats[i] == 0) break;
        
        for (int r = 0; r < resolution_count; r++) {
            fmt.fmt.pix_mp.width = resolutions[r].width;
            fmt.fmt.pix_mp.height = resolutions[r].height;
            fmt.fmt.pix_mp.pixelformat = pixel_formats[i];
            fmt.fmt.pix_mp.field = V4L2_FIELD_NONE;
            fmt.fmt.pix_mp.num_planes = 1;
            
            if (ioctl(fd, VIDIOC_S_FMT, &fmt) == 0) {
                printf("成功设置格式: %c%c%c%c, %dx%d\n",
                       fmt.fmt.pix_mp.pixelformat & 0xFF,
                       (fmt.fmt.pix_mp.pixelformat >> 8) & 0xFF,
                       (fmt.fmt.pix_mp.pixelformat >> 16) & 0xFF,
                       (fmt.fmt.pix_mp.pixelformat >> 24) & 0xFF,
                       fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height);
                format_found = 1;
                resolution_found = 1;
                break;
            } else {
                perror("设置格式失败,尝试下一种");
            }
        }
        
        if (format_found) break;
    }

    if (!format_found) {
        fprintf(stderr, "错误: 无法设置任何支持的格式\n");
        close(fd);
        return -1;
    }

    // 验证实际设置的格式
    if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) {
        perror("获取格式失败");
        close(fd);
        return -1;
    }
    
    printf("实际格式: %c%c%c%c, %dx%d, %u 平面\n",
           fmt.fmt.pix_mp.pixelformat & 0xFF,
           (fmt.fmt.pix_mp.pixelformat >> 8) & 0xFF,
           (fmt.fmt.pix_mp.pixelformat >> 16) & 0xFF,
           (fmt.fmt.pix_mp.pixelformat >> 24) & 0xFF,
           fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height,
           fmt.fmt.pix_mp.num_planes);

    // 申请缓冲区
    memset(&req, 0, sizeof(req));
    req.count = NUM_BUFFERS;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    req.memory = V4L2_MEMORY_MMAP;

    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("申请缓冲区失败");
        close(fd);
        return -1;
    }

    buffer_count = req.count;
    printf("分配了 %d 个缓冲区\n", buffer_count);

    // 映射缓冲区
    for (int i = 0; i < buffer_count; i++) {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        buf.m.planes = planes;
        buf.length = 1;

        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
            perror("查询缓冲区失败");
            goto cleanup;
        }

        buffers[i] = mmap(NULL, planes[0].length, PROT_READ | PROT_WRITE,
                          MAP_SHARED, fd, planes[0].m.mem_offset);

        if (buffers[i] == MAP_FAILED) {
            perror("映射缓冲区失败");
            goto cleanup;
        }

        printf("缓冲区 %d: 大小=%zu 字节\n", i, planes[0].length);

        // 将缓冲区放入队列
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            perror("缓冲区入队失败");
            munmap(buffers[i], planes[0].length);
            goto cleanup;
        }
    }

    // 开始捕获
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
        perror("启动捕获失败");
        goto cleanup;
    }

    printf("等待视频数据...\n");

    // 使用select()等待数据可用
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    int r = select(fd + 1, &fds, NULL, NULL, &timeout);
    if (r < 0) {
        perror("select失败");
        goto cleanup;
    } else if (r == 0) {
        printf("select超时\n");
        goto cleanup;
    }

    // 捕获一帧
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.m.planes = planes;
    buf.length = 1;

    if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
        perror("获取缓冲区失败");
        goto cleanup;
    }

    printf("捕获到一帧: 缓冲区索引=%d, 大小=%zu 字节\n", 
           buf.index, planes[0].bytesused);

    // 根据格式保存数据
    const char *extension = "yuv";
    if (fmt.fmt.pix_mp.pixelformat == V4L2_PIX_FMT_MJPEG) {
        extension = "jpg";
    }
    
    char filename[32];
    sprintf(filename, "frame.%s", extension);
    
    FILE *fp = fopen(filename, "wb");
    if (!fp) {
        perror("打开文件失败");
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0)
            perror("缓冲区回队失败");
        goto cleanup;
    }

    size_t written = fwrite(buffers[buf.index], 1, planes[0].bytesused, fp);
    fclose(fp);
    printf("已保存 %zu 字节到 %s\n", written, filename);

    // 验证数据有效性
    if (fmt.fmt.pix_mp.pixelformat == V4L2_PIX_FMT_MJPEG) {
        fp = fopen(filename, "rb");
        if (fp) {
            unsigned char signature[2];
            if (fread(signature, 1, 2, fp) == 2) {
                if (signature[0] == 0xFF && signature[1] == 0xD8) {
                    printf("验证: 确认为JPEG格式文件\n");
                } else {
                    printf("警告: 文件不是有效的JPEG格式\n");
                }
            }
            fclose(fp);
        }
    }

    // 将缓冲区重新放入队列
    if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
        perror("缓冲区回队失败");
    }

    // 停止捕获
    if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
        perror("停止捕获失败");
    }

    // 释放缓冲区
    for (int i = 0; i < buffer_count; i++) {
        if (buffers[i]) {
            munmap(buffers[i], planes[0].length);
        }
    }

    close(fd);
    return 0;

cleanup:
    // 清理资源
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    ioctl(fd, VIDIOC_STREAMOFF, &type);
    
    for (int i = 0; i < buffer_count; i++) {
        if (buffers[i]) {
            munmap(buffers[i], planes[0].length);
        }
    }
    
    close(fd);
    return -1;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值