Notas de captura de video V4L2

Captura de vídeo V4L2


Este artículo se utiliza para grabar videos sobre el aprendizaje de la colección V4L2 y es difícil escribirlo de manera aproximada. El código completo se proporciona al final del artículo.

1. Introducción a V4L2

Video para Linux dos (Video4Linux2), denominado V4L2, es una versión mejorada de V4L. V4L2 es un conjunto de interfaces API generales para recopilar imágenes, videos y datos de audio en el sistema operativo Linux. Con el equipo de recopilación de videos adecuado y los controladores correspondientes, puede realizar la recopilación de imágenes, videos, audio, etc. V4L2 es como un excelente servicio de mensajería, que entrega datos de imágenes desde dispositivos de recopilación de videos de manera segura y eficiente a usuarios con diferentes necesidades.

En Linux, todo es un archivo y todos los periféricos se consideran un archivo especial llamado "archivo de dispositivo". Los dispositivos de vídeo no son una excepción: también pueden considerarse archivos de dispositivo, que pueden leerse y escribirse como archivos normales. El archivo de dispositivo de una cámara basada en V4L2 es generalmente /dev/videoX (X es cualquier número y debe corresponder a su propio dispositivo).

V4L2 admite tres formas de recopilar imágenes: mapeo de memoria (mmap), lectura directa (read) y puntero de usuario. El método de mapeo de memoria tiene una velocidad de adquisición más rápida y generalmente se usa para la recopilación de datos de video continuos, y tiene una mayor probabilidad de aplicación en el trabajo real; el método de lectura directa es relativamente más lento, por lo que a menudo se usa para la recopilación de datos estáticos. datos de imágenes, el uso de punteros de usuario es relativamente lento, pocos, si está interesado, puede investigarlo usted mismo. Dado que el método de mapeo de memoria se usa más ampliamente, este artículo se centra en la adquisición de video utilizando el método de mapeo de memoria.

2. Proceso de recopilación de videos V4L2

El uso de V4L2 para la recopilación de videos generalmente se divide en 5 pasos:

(1) Encienda el dispositivo, realice la configuración de los parámetros de inicialización y configure la ventana de recopilación de imágenes de video, el tamaño y el formato de la red recopilada a través de la interfaz V4L2;

(2) Solicitar buffers de cuadros de imágenes y realizar un mapeo de memoria para asignar estos buffers de cuadros desde el espacio del kernel al espacio del usuario para facilitar que las aplicaciones lean y procesen datos de imágenes;

(3) Poner en cola el búfer de fotogramas e iniciar la recopilación de vídeos;

(4) El controlador comienza a recopilar datos de video y la aplicación extrae el búfer de fotogramas de la cola de salida de la recopilación de videos. Después del procesamiento, vuelve a colocar el búfer de cuadros en la cola de entrada de la recopilación de videos y recopila datos de video continuos en un bucle;

(5) Liberar recursos y detener el trabajo de recolección.

3. Ejemplo de programa V4L2

3.1 Archivos de encabezado y definiciones de macros

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>

#define VIDEO_DEVICE "/dev/video0"  // 摄像头设备文件路径
#define OUTPUT_FOLDER "./captured_frames/"  // 图像保存文件夹路径
#define NUM_FRAMES 100  // 要捕获的帧数

3.2 Encienda el dispositivo de cámara

Para consultar los atributos del dispositivo, debe utilizar la estructura struct v4l2_capability, que describe la información del controlador del dispositivo de captura de video.

struct v4l2_capability
{
    
    
     __u8 driver[16];       // 驱动名字
     __u8 card[32];         // 设备名字
     __u8 bus_info[32];     // 设备在系统中的位置
     __u32 version;         // 驱动版本号
     __u32 capabilities;    // 设备支持的操作
     __u32 reserved[4];     // 保留字段
};
// 打开摄像头设备
fd = open(VIDEO_DEVICE, O_RDWR);
if (fd == -1) 
{
    
    
     perror("Unable to open device");
     return -1;
}

// 查询摄像头能力
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) 
{
    
    
     perror("Unable to query device");
     close(fd);
     return -1;
}

3.3 Establecer formato de marco de imagen

Configurar el formato de imagen requiere el uso de la estructura struct v4l2_format, que describe el formato específico de cada cuadro de imagen, incluido el tipo de cuadro y la longitud y el ancho de la imagen.

// 设置图像格式
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;  // 设置图像宽度
fmt.fmt.pix.height = 480; // 设置图像高度
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; // 设置图像格式为MJPEG
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) 
{
    
    
     perror("Unable to set format");
     close(fd);
     return -1;
}

Aquí primero podemos consultar los formatos admitidos por la cámara utilizando el siguiente método: después de ejecutar el código, se generarán todos los formatos admitidos por la cámara.

//1.打开设备
int fd = open("/dev/video0", O_RDWR);
if(fd < 0)
{
    
    
     perror("打开设备失败");
     return -1;
}
//2.获取摄像头支持的格式ioctl(文件描述符, 命令, 与命令对应的结构体)
struct v4l2_fmtdesc v4fmt;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int i=0;
while(1)
{
    
    
     v4fmt.index = i++;  
     int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
     if(ret < 0)
     {
    
    
          perror("获取失败");
          break;
     }
     printf("index=%d\n", v4fmt.index);
     printf("flags=%d\n", v4fmt.flags);
     printf("description=%s\n", v4fmt.description);
     unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
     printf("pixelformat=%c%c%c%c\n", p[0],p[1],p[2],p[3]);
     printf("reserved=%d\n", v4fmt.reserved[0]);
}
//9.关闭设备
close(fd);
return 0;

3.4 Solicitar caché de cuadros de imagen y asignarlo al espacio del usuario

// 请求缓冲区
req.count = 4; // 请求4个缓冲区(可以根据需要调整)
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) 
{
    
    
     perror("Unable to request buffers");
     close(fd);
     return -1;
}

// 映射缓冲区
for (int i = 0; i < req.count; ++i) 
{
    
    
     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     buf.memory = V4L2_MEMORY_MMAP;
     buf.index = i;
     if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) 
     {
    
    
          perror("Unable to query buffer");
          close(fd);
          return -1;
     }
     buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);

     // 启用缓冲区
     if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) 
     {
    
    
          perror("Unable to queue buffer");
          close(fd);
          return -1;
     }
}

3.5 Iniciar captura y detener captura

// 启动捕获
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) 
{
    
    
     perror("Unable to start capture");
     close(fd);
     return -1;
}

// 创建输出文件夹
if (mkdir(OUTPUT_FOLDER, 0755) == -1) 
{
    
    
     perror("Unable to create output folder");
     close(fd);
     return -1;
}

while (frame_count < NUM_FRAMES) 
{
    
    
     // 从缓冲区中获取图像数据
     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) 
     {
    
    
          perror("Unable to dequeue buffer");
          continue;
     }

     // 生成文件名
     snprintf(filename, sizeof(filename), "%sframe%d.jpg", OUTPUT_FOLDER, frame_count);

     // 创建并保存图像文件
     output = fopen(filename, "wb");
     if (!output) 
     {
    
    
          perror("Unable to open output file");
     } 
     else 
     {
    
    
          fwrite(buffer, buf.bytesused, 1, output);
          fclose(output);
          printf("Frame %d captured and saved to %s\n", frame_count, filename);
          frame_count++;
     }

     // 将缓冲区重新排队以继续捕获
     if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) 
     {
    
    
          perror("Unable to queue buffer");
          break;
     }
}

// 停止捕获
if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) 
{
    
    
     perror("Unable to stop capture");
}

// 关闭文件和设备
for (int i = 0; i < req.count; ++i) 
{
    
    
     munmap(buffer, buf.length);
}
close(fd);

El código completo es el siguiente: El código implementa el uso de V4L2 para recopilar imágenes y crear una carpeta para almacenar las imágenes en él.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>

#define VIDEO_DEVICE "/dev/video0"  // 摄像头设备文件路径
#define OUTPUT_FOLDER "./captured_frames/"  // 图像保存文件夹路径
#define NUM_FRAMES 100  // 要捕获的帧数

int main() 
{
    
    
    int fd;
    struct v4l2_capability cap;
    struct v4l2_format fmt;
    struct v4l2_requestbuffers req;
    struct v4l2_buffer buf;
    void *buffer;
    char filename[256];
    FILE *output;
    int frame_count = 0;

    // 打开摄像头设备
    fd = open(VIDEO_DEVICE, O_RDWR);
    if (fd == -1) 
    {
    
    
        perror("Unable to open device");
        return -1;
    }

    // 查询摄像头能力
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) 
    {
    
    
        perror("Unable to query device");
        close(fd);
        return -1;
    }

    // 设置图像格式
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;  // 设置图像宽度
    fmt.fmt.pix.height = 480; // 设置图像高度
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; // 设置图像格式为MJPEG
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) 
    {
    
    
        perror("Unable to set format");
        close(fd);
        return -1;
    }

    // 请求缓冲区
    req.count = 4; // 请求4个缓冲区(可以根据需要调整)
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) 
    {
    
    
        perror("Unable to request buffers");
        close(fd);
        return -1;
    }

    // 映射缓冲区
    for (int i = 0; i < req.count; ++i) 
    {
    
    
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) 
        {
    
    
            perror("Unable to query buffer");
            close(fd);
            return -1;
        }
        buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        
        // 启用缓冲区
        if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) 
        {
    
    
            perror("Unable to queue buffer");
            close(fd);
            return -1;
        }
    }

    // 启动捕获
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) 
    {
    
    
        perror("Unable to start capture");
        close(fd);
        return -1;
    }

    // 创建输出文件夹
    if (mkdir(OUTPUT_FOLDER, 0755) == -1) 
    {
    
    
        perror("Unable to create output folder");
        close(fd);
        return -1;
    }

    while (frame_count < NUM_FRAMES) 
    {
    
    
        // 从缓冲区中获取图像数据
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) 
        {
    
    
            perror("Unable to dequeue buffer");
            continue;
        }

        // 生成文件名
        snprintf(filename, sizeof(filename), "%sframe%d.jpg", OUTPUT_FOLDER, frame_count);

        // 创建并保存图像文件
        output = fopen(filename, "wb");
        if (!output) 
        {
    
    
            perror("Unable to open output file");
        } 
        else 
        {
    
    
            fwrite(buffer, buf.bytesused, 1, output);
            fclose(output);
            printf("Frame %d captured and saved to %s\n", frame_count, filename);
            frame_count++;
        }

        // 将缓冲区重新排队以继续捕获
        if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) 
        {
    
    
            perror("Unable to queue buffer");
            break;
        }
    }

    // 停止捕获
    if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) 
    {
    
    
        perror("Unable to stop capture");
    }

    // 关闭文件和设备
    for (int i = 0; i < req.count; ++i) 
    {
    
    
        munmap(buffer, buf.length);
    }
    close(fd);

    return 0;
}

referencia

  1. Desarrollo de aplicaciones Linux [Capítulo 7] Desarrollo de aplicaciones de programación Camera V4L2 - Zhihu (zhihu.com)
  2. Aprenda la colección de videos V4L2 con Xiaobai_Explicación detallada de la colección de videos v4l2_Blog-CSDN de Xiao Yuan OVO
  3. Notas de la nube de Youdao (youdao.com)

Guess you like

Origin blog.csdn.net/weixin_69035671/article/details/132699680