实验平台:Ubuntu20.04
摄像头:1080P 监控摄像头,采用V4L2驱动框架
Video4Linux2(V4L2)是Linux内核中用于视频设备的一个API,旨在提供对视频捕捉、输出和处理设备的支持。它是Video4Linux(V4L)的继任者,具有更强的功能和更好的设计。以下是V4L2的详细介绍:
/dev/video0, /dev/video1 等设备文件。ioctl 系列函数,用于配置和控制视频设备。/dev 目录下创建的设备文件,用于用户空间程序访问视频设备。ioctl 函数设置和获取设备参数,如分辨率、帧率、视频格式等。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同样支持多种分辨率格式。得知这些参数之后,方便编程实现录制输出工作。
命令示例:
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画面。
主要流程较为简单,如下所示:

完整代码实现:
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文件,为没有被编码的原始的数据,需要指定参数才能播放
效果如图:

AVFrame)是解码后的原始数据或编码前的原始数据。当你通过FFmpeg读取视频数据时,即使没有显式地进行编码,FFmpeg也将视频数据封装在 AVPacket 中。原因如下:
av_read_frame 函数读取数据流,无论数据是否已经编码,读取到的数据都封装在 AVPacket 结构中。这是因为 AVPacket 是用于传输解码器或编码器之间的数据单位。av_read_frame 从输入设备(如摄像头)读取数据时,FFmpeg将摄像头的原始数据作为 AVPacket 进行处理。这个 AVPacket 包含了从设备获取的原始数据块,即使这些数据是未压缩的。av_read_frame 提供了一个统一的接口,用于处理各种输入数据流(文件、网络流、设备捕获)。这种设计简化了处理过程,不需要为不同的数据源提供不同的读取机制。