实验平台:Ubuntu20.04
摄像头:1080P 监控摄像头,采用V4L2驱动框架
文章目录
- 1.V4L2相关介绍
- 1.1. 基本概念
- 1.2. 主要功能
- 1.3. V4L2驱动框架
- 1.4. 主要组件
- 1.5. 使用V4L2的应用
- 1.6. 常用V4L2工具
- 2.ffmpeg命令实现打开摄像头输出yuv文件
- 3.使用C语言编程实现
- 4.注意事项
- 4.1Packet 和 Frame 的区别
- 4.2为什么读取摄像头时使用 packet而不是 frame?
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 的区别
- Packet(数据包):
- 一般是指编码后的数据包(如H.264、H.265等编码格式的压缩数据)。
- 包含元数据和编码数据。
- 是数据流中的最小单位,可以包含一个完整帧或部分帧的数据。
- Frame(帧):
- 是原始数据的表现形式,未经过编码压缩的原始视频或音频数据。
- 在视频处理中,一个帧通常是一个完整的视频画面。
- 在FFmpeg中,帧(
AVFrame
)是解码后的原始数据或编码前的原始数据。
4.2为什么读取摄像头时使用 packet而不是 frame?
当你通过FFmpeg读取视频数据时,即使没有显式地进行编码,FFmpeg也将视频数据封装在 AVPacket
中。原因如下:
- 数据流处理:
- FFmpeg通过
av_read_frame
函数读取数据流,无论数据是否已经编码,读取到的数据都封装在AVPacket
结构中。这是因为AVPacket
是用于传输解码器或编码器之间的数据单位。
- FFmpeg通过
- 输入格式的处理:
- 当使用
av_read_frame
从输入设备(如摄像头)读取数据时,FFmpeg将摄像头的原始数据作为AVPacket
进行处理。这个AVPacket
包含了从设备获取的原始数据块,即使这些数据是未压缩的。
- 当使用
- 统一接口:
av_read_frame
提供了一个统一的接口,用于处理各种输入数据流(文件、网络流、设备捕获)。这种设计简化了处理过程,不需要为不同的数据源提供不同的读取机制。