【问题标题】:Capturing YUYV in c++ using v4l2使用 v4l2 在 C++ 中捕获 YUYV
【发布时间】:2021-09-19 05:12:05
【问题描述】:

我有一个通过 USB 连接到 beaglebone 的网络摄像头。我正在用 C++ 编码,我的目标是从网络摄像头捕获原始未压缩图片。 首先我通过命令v4l2-ctl --list-formats检查了支持哪些格式,结果是:

        Index       : 0
        Type        : Video Capture
        Pixel Format: 'MJPG' (compressed)
        Name        : Motion-JPEG

        Index       : 1
        Type        : Video Capture
        Pixel Format: 'YUYV'
        Name        : YUYV 4:2:2

因此,我假设如果我尝试使用 YUYV 格式,它必须有可能获得未压缩的图片。

知道了这一点,我开始用 C++ 编写程序。我成功编写了一个程序来捕获压缩图片,但是当尝试使用 YUYV 格式捕获时它不起作用,我真的需要一些帮助才能完成这项工作。

这是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <libv4l2.h>


template <typename typeXX>
void clear_memmory(typeXX* x) {
    memset(x, 0, sizeof(*x));
    }
    
void xioctl(int cd, int request, void *arg){
    int response;
    do{
        //ensures we get the correct response.
        response = v4l2_ioctl(cd, request, arg);
        }
    while (response == -1 && ((errno == EINTR) || (errno == EAGAIN)));

    if (response == -1) {
        fprintf(stderr, "error %d, %s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
        }
    }
    
    struct LMSBBB_buffer{
    void*  start;
    size_t length;
    };

int main(){
    
    const char* dev_name = "/dev/video0";
    int width=1920;
    int height=1080;
    
    int fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
    
    struct v4l2_format format = {0};
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    format.fmt.pix.width = width;
    format.fmt.pix.height = height;
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;//V4L2_PIX_FMT_YUYV //V4L2_PIX_FMT_RGB24
    format.fmt.pix.field = V4L2_FIELD_NONE; //V4L2_FIELD_NONE
    xioctl(fd, VIDIOC_S_FMT, &format);

        
    printf("Device initialized.\n");
    
    
    ///request buffers  
    struct v4l2_requestbuffers req = {0};
    req.count = 2;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    xioctl(fd, VIDIOC_REQBUFS, &req);

    printf("Buffers requested.\n");
    

    ///mapping buffers  
    struct v4l2_buffer buf;
    LMSBBB_buffer* buffers;
    unsigned int i;
    buffers = (LMSBBB_buffer*) calloc(req.count, sizeof(*buffers));
    for (i = 0; i < req.count; i++) {
    clear_memmory(&(buf));

    (buf).type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    (buf).memory      = V4L2_MEMORY_MMAP;
    (buf).index       = i;

    xioctl(fd, VIDIOC_QUERYBUF, &buf);

    buffers[i].length = (buf).length;
    printf("A buff has a len of: %i\n",buffers[i].length);
    buffers[i].start = v4l2_mmap(NULL, (buf).length, PROT_READ | PROT_WRITE, MAP_SHARED,fd, (buf).m.offset);
    
    if (MAP_FAILED == buffers[i].start) {
        perror("Can not map the buffers.");
        exit(EXIT_FAILURE);
        }
    }
    printf("Buffers mapped.\n");    
    
    for (i = 0; i < req.count; i++) {
        clear_memmory(&(buf));
        (buf).type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        (buf).memory = V4L2_MEMORY_MMAP;
        (buf).index = i;
        ioctl(fd,VIDIOC_QBUF, &(buf));
        }
    enum v4l2_buf_type type;
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd,VIDIOC_STREAMON, &type);
    
    
    printf("buffers queued and streaming.\n");
    
    
    
    int pic_count=0;
    ///CAPTURE
    fd_set fds;
    struct timeval tv;
    int r;
    char out_name[256];
    FILE* fout;
        

        
    do {
        FD_ZERO(&fds);
        FD_SET(fd, &fds);

        // Timeout.
        tv.tv_sec = 2;
        tv.tv_usec = 0;

        r = select(fd + 1, &fds, NULL, NULL, &tv);
        } while ((r == -1 && (errno = EINTR)));
    if (r == -1) {
        perror("select");
        exit(EXIT_FAILURE);
        }

    clear_memmory(&(buf));
    (buf).type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    (buf).memory = V4L2_MEMORY_MMAP;
    xioctl(fd,VIDIOC_DQBUF, &(buf));
    
    printf("Buff index: %i\n",(buf).index);
    sprintf(out_name, "image%03d.ppm",pic_count);
    fout = fopen(out_name, "w");
    if (!fout) {
        perror("Cannot open image");
        exit(EXIT_FAILURE);
        }
    fprintf(fout, "P6\n%d %d 255\n",width, height);
    fwrite(buffers[(buf).index].start, (buf).bytesused, 1, fout);
    fclose(fout);
    pic_count++;
    
    clear_memmory(&(buf));
    (buf).type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    (buf).memory = V4L2_MEMORY_MMAP;
    xioctl(fd,VIDIOC_DQBUF, &(buf));
    printf("Buff index: %i\n",(buf).index);
    sprintf(out_name, "image%03d.ppm",pic_count);
    fout = fopen(out_name, "w");
    if (!fout) {
        perror("Cannot open image");
        exit(EXIT_FAILURE);
        }
    fprintf(fout, "P6\n%d %d 255\n",width, height);
    fwrite(buffers[(buf).index].start, (buf).bytesused, 1, fout);
    fclose(fout);
    pic_count++;
    
    
    ///xioctl(fd,VIDIOC_QBUF, &(buf));
    
    
    return 0;
    }

在第 50 行,我可以在 V4L2_PIX_FMT_YUYV 和 V4L2_PIX_FMT_RGB24 之间选择格式。 对于 V4L2_PIX_FMT_RGB24 我得到了图片,但是当使用 V4L2_PIX_FMT_YUYV 我得到这个错误:

libv4l2: error dequeuing buf: Resource temporarily unavailable
libv4l2: error dequeuing buf: Resource temporarily unavailable
libv4l2: error dequeuing buf: Resource temporarily unavailable
libv4l2: error dequeuing buf: Resource temporarily unavailable
libv4l2: error dequeuing buf: Resource temporarily unavailable

错误行一直存在,直到我手动结束程序。

有人知道该怎么做吗?我花了两个多星期的时间,我不能从这里搬到任何地方。我真的很感激任何建议。

【问题讨论】:

    标签: c++ camera v4l2 image-capture


    【解决方案1】:

    据我所知,您正在从相机请求 YUYV 格式的 FullHD (1920x1080) 缓冲区。您没有提及相机类型/型号/规格,但如果它是通用 USB 连接硬件,您很可能不会获得原始 FullHD YUYV 缓冲区作为输出,只有 MJPEG 缓冲区(如果您可以将其解码为 YUV你用 libjpeg 破解)或解码的 RGB 缓冲区(这几乎是经过 YUV->RGB 转换的解码 MJPEG),它没有被映射。

    此命令可以请求具有帧速率的格式的确切列表,这可能会告诉您它不提供 1920x1080 YUYV,只提供更小的格式,例如 640x480:

    v4l2-ctl --list-formats
    

    如果您需要对原始 YUYV 相机帧进行“真正”零拷贝访问的视频处理,则首先需要直接访问硬件和特定硬件。一旦你的软件和相机之间有了 USB 接口,你就会得到一个额外的间接性,这意味着速度会下降。想一想,1920x1080 的 YUYV 帧占用了大约 4 MB 的内存。在 30 FPS 时,这是每秒 120 兆字节(或 960 兆位)的总线吞吐量。如果您有 USB2.0 摄像头,则没有带宽支持(因此需要 MJPEG)。即使在 15FPS 下,这也是 480 兆比特,这还不包括 USB 延迟和协议开销。

    为了提供一些“可操作的反馈”,我建议首先专注于您想要应用于图像的算法(可能,您只是不想在第一步就降低处理速度)。相机输入和基本图像处理不用犹豫,以后可以切换到一些硬件接口和手写算法。

    获取原始帧的更简单方法是使用 Android 的相机接口,并尝试使用 GL_TEXTURE_EXTERNAL_OES 扩展通过 GLSL 着色器处理传入帧,有关该扩展的信息和代码示例可用。在那里,您可以将 GL 纹理连接到 AHardwareBuffer 实例,然后使用 AHardwareBuffer_lock 函数获取原始指针。确切支持的格式也可能因硬件而异,所以不要指望这会超级简单。

    【讨论】:

    • 如果我使用:v4l2-ctl -d /dev/video0 --list-formats-ext 我看到 yuyv 以 5 fps 支持 1920x1080。结果部分是这样的:Index : 1 Type : Video Capture Pixel Format: 'YUYV' Name : YUYV 4:2:2 Size: Discrete 1920x1080 Interval: Discrete 0.200s (5.000 fps) 采用这种格式的目的是不要在压缩时丢失任何数据,因为我想使用图片进行图像处理,并且需要准确。 FPS可以根据需要尽可能低,只要图片准确就可以了。
    • 这说明了很多事情。也许,您没有为您的摄像头设置帧速率限制,或者驱动程序可能不支持您的网络摄像头的 mmap'ed I/O。我最初的建议保持不变 - 尝试使用 OpenCV 进行捕获(使用 videoCapture.set(CV_CAP_PROP_CONVERT_RGB, false) 就像在这个问题中使用 stackoverflow.com/questions/27820970 以防止自动转换为 RGB)来查看您的相机实际工作,而不是深入研究 OpenCV 的相机捕获代码,或者尝试调查用于帧速率/输出格式调整的 v4l2 示例。
    • 您也可以在 v4l2 代码中尝试使用 IO_METHOD_USERPTR 而不是 IO_METHOD_MMAP,但这需要在开始时进行一些提取预分配。调用 xioctl(fd, VIDIOC_QUERYCAP, &cap) 后检查 cap.capabilities 中的 V4L2_CAP_STREAMING。如果这不可用,则无法映射帧。
    • 同时检查 IO_METHOD_READ - 一旦框架可用,您只需读取 (fd, my_buffer, frame_size)。
    【解决方案2】:

    我最近遇到了类似的问题。在我的情况下,相机驱动程序需要 VIDIOC_S_PARM ioctl 来设置帧速率并为选定的捕获模式初始化相机。

    您可以尝试在 VIDIOC_S_FMT 之后添加此代码,看看它是否也适合您:

    struct v4l2_streamparm streamparam;
    memset(&streamparam, 0, sizeof(streamparam));
    streamparam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    xioctl(fd, VIDIOC_G_PARM, &streamparam);
    streamparam.parm.capture.timeperframe.numerator = 1;
    streamparam.parm.capture.timeperframe.denominator = 5;
    xioctl(fd, VIDIOC_S_PARM, &streamparam);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-16
      • 1970-01-01
      相关资源
      最近更新 更多