【嵌入式linux基础】举例说明V4L2_MEMORY_USERPTR的用法

下面是一个更完整的示例程序,它不仅包括了启动和停止视频流的过程,还包含了如何使用 VIDIOC_DQBUFVIDIOC_QBUF 来循环处理图像帧。此外,我还添加了对多平面格式的支持,并且提供了一个简单的机制来显示图像(这里以保存到文件为例)。这个例子假设你有一个可以保存为 PPM 文件的函数 save_image_ppm()

完整示例代码

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

#define DEVICE_NAME "/dev/video0"
#define BUFFER_COUNT 4

struct buffer {
    void *start;
    size_t length;
};

void save_image_ppm(const char *filename, unsigned char *data, int width, int height) {
    // 这里只是一个占位符,用于说明保存图像的方法。
    // 实际实现应该根据你的需求来编写。
}

int main() {
    int fd, ret;
    struct v4l2_requestbuffers reqbufs;
    struct v4l2_format fmt;
    struct v4l2_buffer buf;
    struct v4l2_plane planes[BUFFER_COUNT];
    struct pollfd fds[1];
    struct buffer *buffers[BUFFER_COUNT];
    unsigned int i, j;

    // 打开设备
    fd = open(DEVICE_NAME, O_RDWR);
    if (fd == -1) {
        perror("Opening video device");
        return errno;
    }

    // 设置视频格式
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    fmt.fmt.pix_mp.width = 640;
    fmt.fmt.pix_mp.height = 480;
    fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420M; // 使用多平面格式
    fmt.fmt.pix_mp.num_planes = 3; // YUV420M有三个平面

    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
    if (ret == -1) {
        perror("VIDIOC_S_FMT");
        close(fd);
        return errno;
    }

    // 请求缓冲区
    memset(&reqbufs, 0, sizeof(reqbufs));
    reqbufs.count = BUFFER_COUNT;
    reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    reqbufs.memory = V4L2_MEMORY_USERPTR;

    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbufs);
    if (ret == -1) {
        perror("VIDIOC_REQBUFS");
        close(fd);
        return errno;
    }

    // 分配用户空间缓冲区
    for (i = 0; i < BUFFER_COUNT; ++i) {
        buffers[i] = malloc(sizeof(struct buffer));
        buffers[i]->length = fmt.fmt.pix_mp.plane_fmt[0].sizeimage + 
                             fmt.fmt.pix_mp.plane_fmt[1].sizeimage + 
                             fmt.fmt.pix_mp.plane_fmt[2].sizeimage;
        buffers[i]->start = malloc(buffers[i]->length);

        if (!buffers[i]->start) {
            perror("malloc");
            goto error;
        }
    }

    // 将用户指针加入队列
    for (i = 0; i < BUFFER_COUNT; ++i) {
        memset(&buf, 0, sizeof(buf));
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_USERPTR;
        buf.m.userptr = (unsigned long)buffers[i]->start;
        buf.length = buffers[i]->length;

        for (j = 0; j < fmt.fmt.pix_mp.num_planes; j++) {
            buf.m.planes[j].bytesused = fmt.fmt.pix_mp.plane_fmt[j].sizeimage;
            buf.m.planes[j].length = fmt.fmt.pix_mp.plane_fmt[j].sizeimage;
            buf.m.planes[j].data_offset = 0;
            buf.m.planes[j].m.userptr = (unsigned long)((char *)buffers[i]->start + 
                (j > 0 ? fmt.fmt.pix_mp.plane_fmt[0].sizeimage : 0) +
                (j > 1 ? fmt.fmt.pix_mp.plane_fmt[1].sizeimage : 0));
        }

        ret = ioctl(fd, VIDIOC_QBUF, &buf);
        if (ret == -1) {
            perror("VIDIOC_QBUF");
            goto error;
        }
    }

    // 启动流
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret == -1) {
        perror("VIDIOC_STREAMON");
        goto error;
    }

    printf("Stream started. Press Enter to stop.\n");

    // 设置poll结构体
    fds[0].fd = fd;
    fds[0].events = POLLIN;

    while (getchar() != '\n') { // 等待用户输入换行键结束循环
        ret = poll(fds, 1, -1); // 阻塞直到有数据可读
        if (ret < 0) {
            perror("poll");
            break;
        }

        // 取出已填充的缓冲区
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_USERPTR;
        buf.m.planes = planes;

        ret = ioctl(fd, VIDIOC_DQBUF, &buf);
        if (ret == -1) {
            perror("VIDIOC_DQBUF");
            continue;
        }

        // 此处可以添加处理图像数据的代码
        // 例如:保存图像到文件、显示图像等
        // 注意,对于多平面格式,需要处理多个平面的数据
        // 下面是一个保存图像到PPM文件的例子:
        char filename[256];
        snprintf(filename, sizeof(filename), "frame_%d.ppm", buf.index);
        save_image_ppm(filename, (unsigned char *)buf.m.planes[0].m.userptr, 
                       fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height);

        // 重新排队缓冲区以供再次使用
        ret = ioctl(fd, VIDIOC_QBUF, &buf);
        if (ret == -1) {
            perror("VIDIOC_QBUF");
            break;
        }
    }

    // 停止流
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    if (ret == -1) {
        perror("VIDIOC_STREAMOFF");
        goto error;
    }

error:
    // 清理资源
    for (i = 0; i < BUFFER_COUNT; ++i) {
        if (buffers[i]) {
            free(buffers[i]->start);
            free(buffers[i]);
        }
    }
    close(fd);
    return ret == -1 ? errno : 0;
}

关键点解释

  • 设置视频格式:在请求缓冲区之前,我们首先设置了期望的视频格式,这里选择了多平面 YUV420 格式(V4L2_PIX_FMT_YUV420M),并指定了分辨率。

  • 分配用户空间缓冲区:根据视频格式信息,为每个缓冲区分配足够的内存来存储一个完整帧的数据。注意,对于多平面格式,我们需要为所有平面的数据预留空间。

  • 将用户指针加入队列:使用 VIDIOC_QBUF 将每个缓冲区的用户指针信息传递给驱动程序,并指定每个平面的具体信息。

  • 启动流:调用 VIDIOC_STREAMON 开始视频流传输。

  • 轮询机制:使用 poll() 函数等待设备准备好新的图像帧。这有助于避免忙等待,并允许程序响应其他事件。

  • 取出和处理图像帧:一旦 poll() 返回,表示有新的图像帧可用,我们就可以使用 VIDIOC_DQBUF 获取该帧,并对其进行处理。在这个例子中,我们将图像保存为 PPM 文件,实际应用中可以根据需要进行不同的处理。

  • 重新排队缓冲区:处理完图像后,使用 VIDIOC_QBUF 将缓冲区重新放回队列中,以便它可以被再次使用来接收新的图像帧。

  • 停止流:当用户按下 Enter 键时,程序会停止视频流,并清理所有分配的资源。

请根据实际情况调整代码中的参数(如设备名称、分辨率、像素格式等)以及图像处理逻辑。上述代码提供了一个完整的框架,你可以在此基础上构建更复杂的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值