• 音视频开发—V4L2介绍,FFmpeg 打开摄像头输出yuv文件


    实验平台:Ubuntu20.04

    摄像头:1080P 监控摄像头,采用V4L2驱动框架

    1.V4L2相关介绍

    Video4Linux2(V4L2)是Linux内核中用于视频设备的一个API,旨在提供对视频捕捉、输出和处理设备的支持。它是Video4Linux(V4L)的继任者,具有更强的功能和更好的设计。以下是V4L2的详细介绍:

    1.1. 基本概念

    • API: V4L2提供了一个标准的应用程序编程接口(API),使开发者能够在用户空间与视频设备进行交互。
    • 设备文件: 在Linux系统中,视频设备通常表示为 /dev/video0/dev/video1 等设备文件。

    1.2. 主要功能

    • 视频捕捉: 从摄像头或其他视频输入设备捕获视频帧。
    • 视频输出: 将视频数据输出到显示设备或其他输出目标。
    • 视频流: 支持视频流的处理和传输,包括实时视频流的捕获和播放。
    • 图像处理: 提供基本的图像处理功能,如缩放、色彩转换等。

    1.3. V4L2驱动框架

    • 驱动层: V4L2驱动程序位于内核空间,负责与硬件进行交互,控制视频设备的操作。
    • 用户空间API: 提供给应用程序使用的系统调用接口,如 ioctl 系列函数,用于配置和控制视频设备。

    1.4. 主要组件

    • 设备节点: 在 /dev 目录下创建的设备文件,用于用户空间程序访问视频设备。
    • 控制接口: 通过 ioctl 函数设置和获取设备参数,如分辨率、帧率、视频格式等。
    • 缓冲区管理: 支持多种缓冲区管理模式,包括内存映射(mmap)、用户指针(user pointer)和DMA缓冲区(DMABUF)。
    • 格式转换: 支持多种视频格式,如YUV、RGB、MJPEG、H.264等,并提供格式转换功能。

    1.5. 使用V4L2的应用

    • 视频采集应用: 使用V4L2 API开发的视频捕捉应用程序,如网络摄像头软件、视频录制软件等。
    • 多媒体框架: GStreamer、FFmpeg等多媒体框架支持V4L2,可以使用这些框架方便地进行视频处理和传输。
    • 嵌入式系统: 在嵌入式Linux系统中,V4L2广泛用于摄像头、视频采集卡等设备的驱动开发。

    1.6. 常用V4L2工具

    • v4l2-ctl: 一个命令行工具,用于控制和调试V4L2设备。可以查询设备信息、设置参数、捕获视频帧等。
      v4l2-ctl --list-formats-ext  # 列出设备支持的所有格式
      v4l2-ctl --set-fmt-video=width=1920,height=1080,pixelformat=H264  # 设置视频格式
      v4l2-ctl --stream-mmap --stream-count=100 --stream-to=output.raw  # 捕获视频流
      

    比如可以查看本次实验的摄像头的相关参数

    marxist@ubuntu:~/Desktop/audio_test/build$ v4l2-ctl --list-formats-ext
    ioctl: VIDIOC_ENUM_FMT
    	Type: Video Capture
    
    	[0]: 'MJPG' (Motion-JPEG, compressed)
    		Size: Discrete 1920x1080
    			Interval: Discrete 0.033s (30.000 fps)
    		Size: Discrete 640x480
    			Interval: Discrete 0.008s (120.101 fps)
    			Interval: Discrete 0.011s (90.000 fps)
    			Interval: Discrete 0.017s (60.500 fps)
    			Interval: Discrete 0.033s (30.200 fps)
    		Size: Discrete 1280x720
    			Interval: Discrete 0.017s (60.000 fps)
    			Interval: Discrete 0.033s (30.500 fps)
    		Size: Discrete 1024x768
    			Interval: Discrete 0.033s (30.000 fps)
    		Size: Discrete 800x600
    			Interval: Discrete 0.017s (60.000 fps)
    		Size: Discrete 1280x1024
    			Interval: Discrete 0.033s (30.000 fps)
    		Size: Discrete 320x240
    			Interval: Discrete 0.008s (120.101 fps)
    	[1]: 'YUYV' (YUYV 4:2:2)
    		Size: Discrete 1920x1080
    			Interval: Discrete 0.167s (6.000 fps)
    		Size: Discrete 640x480
    			Interval: Discrete 0.033s (30.000 fps)
    		Size: Discrete 1280x720
    			Interval: Discrete 0.111s (9.000 fps)
    		Size: Discrete 1024x768
    			Interval: Discrete 0.167s (6.000 fps)
    		Size: Discrete 800x600
    			Interval: Discrete 0.050s (20.000 fps)
    		Size: Discrete 1280x1024
    			Interval: Discrete 0.167s (6.000 fps)
    		Size: Discrete 320x240
    			Interval: Discrete 0.033s (30.000 fps)
    
    

    由上述可知,摄像头一共支持两种格式,一是MJPG格式,已经由硬件压缩好的一种格式,一种就是常见的YUV422格式,YUV同样支持多种分辨率格式。得知这些参数之后,方便编程实现录制输出工作。

    2.ffmpeg命令实现打开摄像头输出yuv文件

    命令示例:

    ffmpeg -f v4l2 -framerate 9 -video_size 1280x720 -pixel_format yuyv422 -i /dev/video0 -c:v rawvideo -pix_fmt yuv420p output.yuv
    

    参数解释:

    -f v4l2: 指定输入格式为V4L2(Video4Linux2)。

    -framerate 9: 设置帧率为9fps。

    -video_size 1280x720: 设置视频分辨率为1280x720(720p)。

    -pixel_format yuyv422: 指定像素格式为YUYV422。

    -i /dev/video0: 指定输入设备为 /dev/video0

    -c:v rawvideo: 指定视频编码器为原始视频(不压缩)。

    output.yuv: 指定输出文件名为 output.yuv

    播放示例:

    注意:需要指定格式和分辨率,原始的数据文件并不包含这些信息,需要手动指定才能播放。

    ffplay -pix_fmt yuyv422 -s 1280*720 output.yuv 
    

    效果图:

    在这里插入图片描述

    此时正常解析出来yuv画面。

    3.使用C语言编程实现

    主要流程较为简单,如下所示:

    在这里插入图片描述

    完整代码实现:

    extern "C"
    {
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    }
    #include 
    using namespace std;
    #define OUTPUT_FILE "output.yuv"
    #define CAMERA_DEVICE "/dev/video0"
    AVFormatContext *format_ctx = NULL;
    AVCodecContext *codec_ctx = NULL;
    AVCodec *codec = NULL;
    AVPacket packet ;
    AVFrame *frame = NULL;
    FILE *output_file = NULL;
    AVInputFormat *input_format = NULL;
    AVDictionary *options; //摄像头相关参数
    int video_stream_index = -1;
    
    int open_v4l2_cam()
    {
        input_format = av_find_input_format("v4l2");
    
        if (!input_format)
        {
            fprintf(stderr, "Could not find input format 'v4l2'\n");
            return -1;
        }
    
        // 摄像头支持多种参数,因此使用option 指定参数 最大支持到9帧
        av_dict_set(&options, "video_size", "1280*720", 0);
        av_dict_set(&options, "framerate", "9", 0);
        av_dict_set(&options, "input_format", "yuyv422", 0);
        int ret = avformat_open_input(&format_ctx, CAMERA_DEVICE, input_format, &options);
        if (ret != 0)
        {
            cerr << "open input device fail" << endl;
            return -1;
        }
        ret = avformat_find_stream_info(format_ctx, NULL);
        if (ret < 0)
        {
            cerr << "findding stream info" << endl;
            return -1;
        }
        video_stream_index = -1;
        // 查找视频流
        for (size_t i = 0; i < format_ctx->nb_streams; i++)
        {
            /* code */
            if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                video_stream_index = i;
                break;
            }
        }
        if (video_stream_index == -1)
        {
            cerr << "no found video_steam" << endl;
            return -1;
        }
        return 0;
    }
    int init_codec_env()
    {
        // 分配帧和数据包
        frame = av_frame_alloc();
        av_init_packet(&packet);
        // 准备写入文件
        output_file = fopen(OUTPUT_FILE, "wb");
        if (!output_file)
        {
            fprintf(stderr, "Error opening output file\n");
            return -1;
        }
    }
    
    void start_ouput_data()
    {
        // 读取视频帧并保存为YUV 文件,
        cout << "start record" << endl;
        while (av_read_frame(format_ctx, &packet) >= 0)
        {
            if (packet.stream_index == video_stream_index)
            {
                fwrite(packet.data, 1, packet.size, output_file);
            }
            av_packet_unref(&packet);
        }
    }
    int main()
    {
    
        avdevice_register_all(); // Ensure that device library is registered
    
        int ret = open_v4l2_cam();
        if (ret < 0)
        {
            cerr << "open cam fail !" << endl;
            return -1;
        }
        ret = init_codec_env();
        start_ouput_data();
        // 释放资源
        fclose(output_file);
        av_dict_free(&options);
        avformat_close_input(&format_ctx);
        return 0;
    }
    

    输出的yuv文件,为没有被编码的原始的数据,需要指定参数才能播放

    效果如图:

    在这里插入图片描述

    4.注意事项

    4.1Packet 和 Frame 的区别

    1. Packet(数据包):
      • 一般是指编码后的数据包(如H.264、H.265等编码格式的压缩数据)。
      • 包含元数据和编码数据。
      • 是数据流中的最小单位,可以包含一个完整帧或部分帧的数据。
    2. Frame(帧):
      • 是原始数据的表现形式,未经过编码压缩的原始视频或音频数据。
      • 在视频处理中,一个帧通常是一个完整的视频画面。
      • 在FFmpeg中,帧(AVFrame)是解码后的原始数据或编码前的原始数据。

    4.2为什么读取摄像头时使用 packet而不是 frame?

    当你通过FFmpeg读取视频数据时,即使没有显式地进行编码,FFmpeg也将视频数据封装在 AVPacket 中。原因如下:

    1. 数据流处理:
      • FFmpeg通过 av_read_frame 函数读取数据流,无论数据是否已经编码,读取到的数据都封装在 AVPacket 结构中。这是因为 AVPacket 是用于传输解码器或编码器之间的数据单位。
    2. 输入格式的处理:
      • 当使用 av_read_frame 从输入设备(如摄像头)读取数据时,FFmpeg将摄像头的原始数据作为 AVPacket 进行处理。这个 AVPacket 包含了从设备获取的原始数据块,即使这些数据是未压缩的。
    3. 统一接口:
      • av_read_frame 提供了一个统一的接口,用于处理各种输入数据流(文件、网络流、设备捕获)。这种设计简化了处理过程,不需要为不同的数据源提供不同的读取机制。
  • 相关阅读:
    在数据中查找信号
    电池管理系统(BMS)的进化与分类
    浏览器的选择建议,按照这些建议选,总能找到合适的
    开源的代名词「GitHub 热点速览」
    HTTP面试题总结
    ElasticSearch学习
    过了那么多1024节才知道……
    python socket 传输opencv读取的图像
    高通骁龙处理器天梯排行榜2022 骁龙处理器发布时间排行
    word制作多个单位联合发文的文件头
  • 原文地址:https://blog.csdn.net/weixin_46999174/article/details/139340186