音视频开发—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 <stdio.h>
#include <stdlib.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
#include <iostream>
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 提供了一个统一的接口,用于处理各种输入数据流(文件、网络流、设备捕获)。这种设计简化了处理过程,不需要为不同的数据源提供不同的读取机制。
  • 35
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Trump. yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值