通过 V4L2 读取摄像头

通过 V4L2 读取摄像头


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h> // libv4l-dev


#define VIDEO_DEV "/dev/video0"

int main()
{
    // 打开设备
    int videoFd = open(VIDEO_DEV, O_RDWR);
    if(videoFd < 0)
    {
        fprintf(stderr, "open %s failed: %s\n", VIDEO_DEV, strerror(errno));
        return EXIT_FAILURE;
    }

    // 读取设备属性
    struct v4l2_capability videoCap;
    if(ioctl(videoFd, VIDIOC_QUERYCAP, &videoCap) < 0)
    {
        close(videoFd);
        fprintf(stderr, "ioctl VIDIOC_QUERYCAP failed: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    printf("%s\n", videoCap.card);

    // 判断是否是摄像头
    if(videoCap.capabilities & V4L2_CAP_VIDEO_CAPTURE != V4L2_CAP_VIDEO_CAPTURE)
    {
        fprintf(stderr, "%s doesn't support video recording\n", VIDEO_DEV);
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 读取支持的格式
    bool supportYUYV = false;
    struct v4l2_fmtdesc fmtdesc;
    fmtdesc.index=0;
    fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("Support format:\n");
    while(ioctl(videoFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
    {
        printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
        fmtdesc.index++;
        if(fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV)
        {
            supportYUYV = true;
        }
    }

    if(supportYUYV == false)
    {
        fprintf(stderr, "YUYV 4:2:2 not supported\n");
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 设置帧格式
    struct v4l2_format videoFormat;
    videoFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    videoFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    if(ioctl(videoFd, VIDIOC_S_FMT, &videoFormat) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_S_FMT failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 申请缓冲
    struct v4l2_requestbuffers request;
    request.count = 3; //三帧缓冲
    request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    request.memory = V4L2_MEMORY_MMAP;
    if(ioctl(videoFd, VIDIOC_REQBUFS, &request) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_REQBUFS failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 获取缓冲
    struct v4l2_buffer buffer;
    memset(&buffer, 0, sizeof(buffer));
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_MMAP;
    buffer.index = 0;
    if(ioctl(videoFd, VIDIOC_QUERYBUF, &buffer) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_QUERYBUF failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }
    void* bufPtr = mmap(NULL, buffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, videoFd, buffer.m.offset);
    if(bufPtr == NULL)
    {
        fprintf(stderr, "mmap failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 创建一个帧缓冲
    if(ioctl(videoFd, VIDIOC_QBUF, &buffer) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_QBUF failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 开始采集
    enum v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
    if(ioctl(videoFd, VIDIOC_STREAMON, &bufType) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_STREAMON failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 等待采集完成
    fd_set fds;
    FD_ZERO(&fds); 
    FD_SET(videoFd,  &fds); 
    struct timeval   tv; 
    tv.tv_sec = 2;
    tv.tv_usec = 0;
    select(1, &fds, NULL, NULL, &tv);

    // 读取一帧图像并删除帧缓冲
    if(ioctl(videoFd, VIDIOC_DQBUF, &buffer) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_DQBUF failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 停止采集
    ioctl(videoFd, VIDIOC_STREAMOFF, &bufType);

    // 创建一个PPM文件
    FILE* ppmFptr = fopen("output.ppm", "wb");
    if(ppmFptr == NULL)
    {
        fprintf(stderr, "%s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 写入PPM header
    fprintf(ppmFptr, "P3\n%d %d\n255\n", videoFormat.fmt.pix.width, videoFormat.fmt.pix.height);

    // 读取像素,写入PPM文件
    for(size_t i = 0; i+3 < buffer.length; i+=4)
    {
        uint8_t Y1 = ((uint8_t*)bufPtr)[i];
        uint8_t U = ((uint8_t*)bufPtr)[i+1];
        uint8_t Y2 = ((uint8_t*)bufPtr)[i+2];
        uint8_t V = ((uint8_t*)bufPtr)[i+3];

        uint8_t B =  1.164 * (Y1 - 16) +  2.018 * (U - 128);
        uint8_t G =  1.164 * (Y1 - 16) -  0.391 * (U - 128) - 0.813 * (V - 128);
        uint8_t R =  1.164 * (Y1 - 16)                      + 1.596 * (V - 128);
        fprintf(ppmFptr, "%u %u %u ", R, G, B);

        B =  1.164 * (Y2 - 16) +  2.018 * (U - 128);
        G =  1.164 * (Y2 - 16) -  0.391 * (U - 128) - 0.813 * (V - 128);
        R =  1.164 * (Y2 - 16)                      + 1.596 * (V - 128);
        fprintf(ppmFptr, "%u %u %u ", R, G, B);
    }
    
    close(videoFd);
    fclose(ppmFptr);
    return EXIT_SUCCESS;
}
作者: PlanC
2024-12-18 20:03:48+08:00