繁体   English   中英

使用 v4l2 在 C++ 中捕获 YUYV

[英]Capturing YUYV in c++ using v4l2

我有一个通过 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

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

有谁知道该怎么做? 我在这上面花了 2 个多星期,我不能从这里搬到任何地方。 我真的很感激任何建议。

据我所知,您正在从相机请求 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 兆字节的内存。 在 30 FPS 时,这是每秒 120 兆字节(或 960 兆位)的总线吞吐量。 如果您有 USB2.0 相机,则没有带宽支持(因此需要 MJPEG)。 即使在 15FPS 时,这也是 480 兆位,不包括 USB 延迟和协议开销。

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

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

我最近遇到了类似的问题。 在我的例子中,相机驱动程序需要 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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM