目录
[(2)QCamera 用于访问和控制摄像头设备](#(2)QCamera 用于访问和控制摄像头设备)
[(3)QCameraViewfinder 用于在界面上显示摄像头的预览图像](#(3)QCameraViewfinder 用于在界面上显示摄像头的预览图像)
[(5)设置摄像头拍照 --- QCameraImageCapture](#(5)设置摄像头拍照 --- QCameraImageCapture)
[2、Linux v4l2框架](#2、Linux v4l2框架)
[① V4L2的技术背景](#① V4L2的技术背景)
[② v4l2为应用程序提供了统一的接口有哪些](#② v4l2为应用程序提供了统一的接口有哪些)
[③ V4L2提供标准化API](#③ V4L2提供标准化API)
[① 代码思路](#① 代码思路)
[② 如何使用 v4l2_format设置摄像头参数](#② 如何使用 v4l2_format设置摄像头参数)
[③ 示例代码类的设计](#③ 示例代码类的设计)
[④ 示例代码成员函数](#④ 示例代码成员函数)
[(3)V4L2 使用的缓存队列](#(3)V4L2 使用的缓存队列)
[① 概念](#① 概念)
[② 申请3个缓冲队列](#② 申请3个缓冲队列)
[③ 获取三个缓冲队列的首地址](#③ 获取三个缓冲队列的首地址)
[(1)Linux mplayer 介绍](#(1)Linux mplayer 介绍)
(4)获取当前ListWidget选择的条目项,使用进程运行播放器播放
[① 对ffmpeg的认知](#① 对ffmpeg的认知)
[② ffmpeg的下载](#② ffmpeg的下载)
[③ 安装ffmpeg之前,需要先安装SDL](#③ 安装ffmpeg之前,需要先安装SDL)
[① 运行configure进行编译安装之前的配置](#① 运行configure进行编译安装之前的配置)
[② 上面配置成功之后,进行编译](#② 上面配置成功之后,进行编译)
[③ 编译成功之后,进行安装ffmpeg](#③ 编译成功之后,进行安装ffmpeg)
[① 查看 FFmpeg 版本](#① 查看 FFmpeg 版本)
[② 测试 ffplay 播放 MP4 或者查看音视频文件信息](#② 测试 ffplay 播放 MP4 或者查看音视频文件信息)
一、摄像头编程
1、Qt内置摄像头类
(1)QT摄像头使用的前置要求
cpp
摄像头的分类
面试的时候问摄像头的硬件接口:
USB
HDMI
① QT摄像头使用需要用两个类:
QCamera :用于访问和控制摄像头设备
QCameraViewfinder:用于在界面上显示摄像头的监控画面
② 在pro文件中添加对应的插件QCamera 和 QCameraViewfinder 的功能插件:
QT += multimedia ---- QCamera
QT += multimediawidgets ---- QCameraViewfinder
(2)QCamera 用于访问和控制摄像头设备
cpp
QCamera 是 Qt 中的一个类,用于访问和控制摄像头设备。它提供了一组功能,允许你在 Qt 应用程序中使用摄像头进行视频捕捉、拍照和其他摄像头相关操作。
通过 QCamera 类,你可以执行以下操作:
打开和关闭摄像头设备。
设置摄像头的参数,如分辨率、帧速率、曝光等。
捕捉摄像头的实时视频流。
拍照并保存图像。
控制摄像头的各种功能,如自动对焦、闪光灯等。
以下是他的常用接口:
构造函数和析构函数:
QCamera(const QString &deviceName = QString(), QObject *parent = nullptr):构造函数,创建一个QCamera对象。
~QCamera():构函数,销毁QCamera对象。
初始化和反初始化:
void load():加载相机设备的插件并初始化相机。
void unload():卸载相机设备的插件并释放相机。
基本操作:
void start():启动相机捕获,开始捕获视频流。
void stop():停止相机捕获,停止捕获视频流。
bool isAvailable() const:检查相机设备是否可用。
QCamera::State state() const:获取相机的状态。
图像捕获:
QCameraImageCapture* imageCapture() const:获取图像捕获对象的指针。
QCameraViewfinderSettings viewfinderSettings() const:获取当前相机的取景器设置。
void setViewfinderSettings(const QCameraViewfinderSettings &settings):设置相机的取景器设置。
QList<QSize> supportedViewfinderSizes(const QCameraViewfinderSettings &settings = QCameraViewfinderSettings()) const:获取相机支持的取景器分辨率列表。
摄像头特性:
QCameraExposure* exposure() const:获取相机的曝光对象指针。
QCameraFocus* focus() const:获取相机的对焦对象指针。
QCameraImageProcessing* imageProcessing() const:获取相机的图像处理对象指针。
QCameraViewfinderSettings::ViewfinderType viewfinderType() const:获取当前相机的取景器类型。
其他函数:
QString deviceName() const:获取相机设备的名称。
void setCaptureMode(QCamera::CaptureModes mode):设置相机的捕获模式。
QCamera::CaptureModes captureMode() const:获取相机的捕获模式。
(3)QCameraViewfinder 用于在界面上显示摄像头的预览图像
cpp
QCameraViewfinder 类提供了与摄像头预览相关的接口,用于在界面上显示摄像头的实时图像。
下面是一些常用的 QCameraViewfinder 接口:
setCamera(QCamera *camera) :将指定的摄像头 (QCamera) 与视图绑定,以显示摄像头的实时预览。
start():开始接收和显示摄像头的实时视频流。 注意如果摄像头和窗口绑定了,那么窗口不需要自行启动。
stop():停止接收和显示摄像头的视频流。
sizeHint():获取视图的大小建议,用于布局管理。
paintEvent(QPaintEvent *event):绘制视图的重载函数,用于在界面上绘制摄像头的预览图像。
sizePolicy():获取视图的大小策略,用于布局管理。
minimumSizeHint():获取视图的最小大小建议,用于布局管理。
这些接口可以帮助你在界面上创建和控制摄像头的预览视图。你可以使用 setCamera() 方法将摄像头对象与 QCameraViewfinder 绑定,然后使用 start() 方法开始接收和显示摄像头的视频流。paintEvent() 方法用于在界面上绘制摄像头的预览图像。
请注意,QCameraViewfinder 是一个基类,它可以通过继承和重载来自定义视图的行为。你可以根据需要添加其他功能和接口。
(4)示例代码
cpp
QWidget window;
QVBoxLayout layout(&window);
QCamera camera; // 创建摄像头对象
QCameraViewfinder viewfinder; // 创建视图查看器对象
// 将视图查看器设置为摄像头的视图,并将其添加到布局中
camera.setViewfinder(&viewfinder);
layout.addWidget(&viewfinder);
// 启动摄像头
camera.start();
window.show();
结果演示:

(5)设置摄像头拍照 --- QCameraImageCapture
cpp
使用QCameraImageCapture 操作 QCamera 进行拍照
示例代码:
-----------widget.h-----------
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QCamera>
#include <QCameraViewfinder>
#include <QWidget>
#include <QPushButton>
#include <QObject>
#include <QCameraImageCapture>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void Init();
public slots:
void Snap_Photo();
private:
Ui::Widget *ui;
QCamera * camera;
QCameraViewfinder * viewfinder;
QCameraImageCapture *imageCapture;
QPushButton * snap_btn;
};
#endif // WIDGET_H
-----------widget.cpp-----------
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->Init();
}
Widget::~Widget()
{
delete ui;
}
void Widget::Init()
{
this->resize(800,480);
this->camera = new QCamera;
this->viewfinder = new QCameraViewfinder(this);
this->viewfinder->resize(700,480);
this->camera->setViewfinder(this->viewfinder);
this->camera->setCaptureMode(QCamera::CaptureStillImage);
// 创建图像捕获对象
this->imageCapture = new QCameraImageCapture(camera);
this->snap_btn = new QPushButton("拍照",this);
this->snap_btn->move(710,100);
QObject::connect(this->snap_btn,&QPushButton::clicked,this,&Widget::Snap_Photo);
// 启动相机
camera->start();
}
void Widget::Snap_Photo()
{
QString imagePath = "xxxxxx.jpg";
// 捕获当前一帧图像
imageCapture->capture(imagePath);
}
-----------main.cpp-----------
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget window;
window.show();
return a.exec();
}
结果演示:

2、Linux v4l2框架
(1)V4L2概念与编程开发理念引入
① V4L2的技术背景
cpp
V4L2(Video for Linux 2)是Linux操作系统中的一个视频""采集""和""输出""的框架,主要用于处理视频设备,如摄像头、电视接收卡等。它为应用程序提供了统一的接口,以便于开发者在不同硬件上实现视频捕捉和播放功能。
起源与发展:
V4L2是V4L(Video for Linux)的继任者,最初设计于1990年代后期。
随着硬件的发展和需求的增加,V4L2成为Linux下视频设备的标准接口,广泛应用于各种多媒体应用 中。
功能:
支持视频捕获、回放和流媒介处理。
提供对多种视频格式的支持,包括MJPEG、YUV等。
支持设备控制、图像调节和帧率调整等功能。
架构:
V4L2采用了模块化的设计,允许开发者通过驱动程序与硬件进行交互。
它包括用户空间API和内核空间驱动,使得应用程序可以方便地访问和控制视频设备。
应用场景:
广泛应用于视频监控、视频会议、直播、以及嵌入式系统等领域。
总之,V4L2为Linux平台上的视频应用提供了强大且灵活的支持,使得开发者能够更容易地实现复杂的视频处理功能。
② v4l2为应用程序提供了统一的接口有哪些
cpp
V4L2(Video for Linux 2)为应用程序提供了一系列统一的接口,主要包括以下几个方面:
设备发现和管理:
通过 /dev/video* 设备文件访问视频设备。
支持查询可用的视频设备、设备信息及其能力。
视频格式控制:
应用程序可以使用 V4L2 API 查询和设置视频捕获和输出的格式,包括像素格式、分辨率等。
缓冲区管理:
支持多种缓冲区类型,如内存映射缓冲区(MMAP)、用户空间缓冲区(USERPTR)等。
提供接口用于请求、释放和管理这些缓冲区。
流控制:
启动和停止视频流的捕获或播放。
控制帧的捕获频率和流的传输。
设备控制:
包括控制视频设备的特性,如亮度、对比度、饱和度、增益等。
支持自定义控制,允许开发者扩展设备功能。
事件处理:
支持事件通知机制,可以处理与视频捕获相关的异步事件,如缓冲区准备就绪、错误状态等。
标准化API:
提供了一致的调用方式,使得不同设备的操作可以使用相同的接口进行。
这些接口使得开发者能够在不同类型的视频设备上轻松实现视频捕获和播放功能,从而提高了跨平台的视频应用开发效率。
③ V4L2提供标准化API
cpp
1. 设备管理
open():打开视频设备。
ioctl():通过设备控制命令与驱动程序进行交互。
close():关闭视频设备。
2. 视频格式设置
VIDIOC_QUERYCAP:查询设备的能力。
VIDIOC_ENUM_FMT:枚举支持的视频格式。
VIDIOC_S_FMT:设置视频格式。
VIDIOC_G_FMT:获取当前视频格式。
3. 缓冲区管理
VIDIOC_REQBUFS:请求缓冲区。
VIDIOC_QUERYBUF:查询缓冲区的详细信息。
VIDIOC_QBUF:将缓冲区排入v4l2的缓存队列。
VIDIOC_DQBUF:从队列中取出缓冲区。
4. 流控制
VIDIOC_STREAMON:开始视频流。
VIDIOC_STREAMOFF:停止视频流。
5. 设备控制
VIDIOC_G_CTRL:获取控制项的当前值。
VIDIOC_S_CTRL:设置控制项的值。
6. 事件处理
VIDIOC_SUBDEV_G_FMT 和 VIDIOC_SUBDEV_S_FMT:用于子设备的格式设置(如摄像头的传感器)。
VIDIOC_WAITEVENT:等待事件发生。
7. 错误处理
使用返回值和errno来处理错误,确保对失败操作的适当响应。
(2)打开摄像头,设置并且获取摄像头硬件参数
① 代码思路
cpp
1)使用open函数打开摄像头设备文件:/dev/video0
v4l2-ctl --list-devices 查看设备文件
2)使用ioctl函数设置摄像头参数
设置摄像头硬件参数宏:VIDIOC_S_FMT
使用struct v4l2_format 结构体存放设置到摄像头的硬件参数。
结构体对应的头文件:/usr/include/linux/videodev2.h
3)使用ioctl函数获取摄像头参数,验证你刚刚设置是否成功
② 如何使用 v4l2_format设置摄像头参数
cpp
v4l2_format 结构体是 V4L2 API 中用于描述视频格式的核心数据结构。它包含了一些关键成员,用于指定捕获或输出设备的视频格式。以下是 v4l2_format 结构体的主要成员及其分析:
结构体定义
struct v4l2_format {
__u32 type;// 媒体类型,通常是 V4L2_BUF_TYPE_VIDEO_CAPTURE 或 V4L2_BUF_TYPE_VIDEO_OUTPUT
union {
struct v4l2_pix_format pix; // 像素格式信息
struct v4l2_pix_format_mplane pix_mp; // 多平面像素格式信息
struct v4l2_vbi_format vbi; // VBI 格式信息
struct v4l2_sliced_vbi_format sliced_vbi; // 切片 VBI 格式信息
// 还可以包含其他格式的定义,如音频等
} fmt; // 媒体格式
};
主要成员分析
__u32 type:
指定媒体的类型,常见的值包括:
V4L2_BUF_TYPE_VIDEO_CAPTURE:表示视频捕获格式。
V4L2_BUF_TYPE_VIDEO_OUTPUT:表示视频输出格式。
1. V4L2_BUF_TYPE_VIDEO_CAPTURE
用途:用于视频采集设备,如摄像头、视频采集卡等。
操作方向:从设备到应用程序。即,设备捕获的视频数据会被传输到用户空间进行处理或显示。
典型场景:实时视频流、视频录制等。
2. V4L2_BUF_TYPE_VIDEO_OUTPUT
用途:用于视频输出设备,如显示器、视频播放器等。
操作方向:从应用程序到设备。即,应用程序将处理后的视频数据提供给设备进行播放或显示。
典型场景:视频播放、视频输出到显示器等。
union fmt:
这是一个联合体,包含多种格式的具体信息,适应不同的视频处理需求。根据type 字段的值,这里可以选择合适的格式结构。
struct v4l2_pix_format:
用于描述单平面像素格式。主要成员包括:
width:图像宽度(像素)。
height:图像高度(像素)。
pixelformat:像素格式,如 V4L2_PIX_FMT_YUYV、V4L2_PIX_FMT_RGB24、V4L2_PIX_FMT_MJPEG 等。
field:图像的场模式,表示是逐行还是隔行扫描。
bytesperline:每行字节数。
sizeimage:图像的总大小(字节)。
colorspace:色彩空间。
struct v4l2_pix_format_mplane:
用于描述多平面像素格式,如 YUV 格式的分离平面。其成员与 v4l2_pix_format 类似,但支持多个平面。
struct v4l2_vbi_format和 struct v4l2_sliced_vbi_format:
分别用于处理 VBI(垂直空闲信号)和切片 VBI 数据,通常用于特定类型的视频信号。
重点:

③ 示例代码类的设计
cpp
class camera_test
{
public:
camera_test();
bool Camera_Device_Init();
private:
int camera_fd;
int camera_type;//存放摄像头格式(像素点格式)
int camera_pix_width;//存放摄像头分辨率宽度
int camera_pix_height;//存放摄像头分辨率高度
};
④ 示例代码成员函数
cpp
#include "camera_test.h"
camera_test::camera_test() //构造函数
{
if(Camera_Device_Init() == false)
{
cout<<"启动摄像头例程失败!"<<endl;
}
else
{
cout<<"启动摄像头例程成功!"<<endl;
}
}
bool camera_test::Camera_Device_Init() //初始化函数
{
this->camera_fd = open(CAMERA_DEV_PATH,O_RDWR);
if(this->camera_fd == -1)
{
cerr<<"打开摄像头失败";
return false;
}
struct v4l2_format format;//定义存放获取到的摄像头属性
memset(&format,0,sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //指定媒体设备的类型:V4L2_BUF_TYPE_VIDEO_CAPTURE
if(ioctl(this->camera_fd,VIDIOC_G_FMT,&format) == -1)
{
cerr<<"获取摄像头信息失败!";
return false;
}
else
{
cout<<"宽:"<<format.fmt.pix.width<<endl;
cout<<"高::"<<format.fmt.pix.height<<endl;
cout<<"像素格式:"<<format.fmt.pix.pixelformat<<endl;
if(format.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
{
cout<<"摄像头格式为yuyv"<<endl;
}
}
struct v4l2_capability cap;
if (ioctl(this->camera_fd, VIDIOC_QUERYCAP, &cap) == -1) {
cout<<"Querying Capabilities"<<endl;
close(this->camera_fd);
return -1;
}
cout<<"Driver:"<<cap.driver<<endl;
cout<<"Card:"<< cap.card<<endl;
cout<<"Bus info: "<<cap.bus_info<<endl;
return true;
}
(3)V4L2 使用的缓存队列
① 概念
cpp
缓存队列一块连续的内存空间,用来存放实时帧图片数据,一般一次性能存放过3张帧图片数据。
一般来说,V4L2 使用的缓存队列通常有以下几种模式:
内存映射缓冲(MMAP):
在使用 MMAP 缓冲时,通常会创建一组缓存(队列)。具体数量可以由驱动和硬件支持的最大队列数决定,通常这个数量为 1、2 或 3 个缓存队列。
对于大多数应用程序,3 个队列是比较常见的,特别是在视频采集或显示应用中,这样能够实现适当的帧缓存和缓冲区切换,减少延迟并提高效率。
用户指针缓冲(Userptr):
用户指针模式下,用户提供自己的缓冲区,V4L2 将其填充数据或从中读取数据。这种模式下缓存队列的数量同样取决于设备和驱动的具体实现。
直接缓冲(DMA-BUF):
在某些高性能的视频设备或硬件中,可能使用 DMA-BUF 来管理缓冲区,这种情况下,缓存队列的数量也可能会有所不同,且通常支持更高的并发和性能。
② 申请3个缓冲队列
cpp
思路:设置缓存队列为3个让vfl2使用3个缓存区,然后自己映射3个内存作为缓存队列
struct v4l2_requestbuffers {
__u32 count; // 请求的缓冲区个数
__u32 type; // 缓冲区类型 (输入/输出类型)
__u32 memory; // 缓冲区存储方式(如内存映射、用户指针等)
__u32 reserved[2]; // 保留,通常不使用
};
struct v4l2_requestbuffers reqbuf;
memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.count = 3; // 申请缓冲区个数为3
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置缓冲区类型为视频捕获
reqbuf.memory = V4L2_MEMORY_MMAP; // 设置缓冲区存储方式(内存映射)
if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
perror("VIDIOC_REQBUFS");
return -1;
}
printf("Supported buffer count: %u\n", reqbuf.count);
③ 获取三个缓冲队列的首地址
cpp
struct v4l2_buffer buffer[reqbuf.count];
char * start[reqbuf.count];
//进行缓存区映射,获取三个缓存区的首地址
for(int index = 0; index < reqbuf.count; index++)
{
//查询第 index个缓存区的信息
memset(&buffer[index],0,sizeof(struct v4l2_buffer));
buffer[index].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer[index].memory = V4L2_MEMORY_MMAP;
buffer[index].index = index;
if(ioctl(video_fd,VIDIOC_QUERYBUF,&buffer[index]) == -1)
{
perror("ioctl ...");
return -1;
}
//获取缓存区的大小和偏移量
start[index] = (char *)mmap(NULL,buffer[index].length,PROT_READ | PROT_WRITE,MAP_SHARED,video_fd,buffer[index].m.offset);
if(start[index] == (char *)MAP_FAILED)
{
perror("mmap ...");
return -1;
} if(ioctl(video_fd,VIDIOC_QBUF,&buffer[index]) == -1)
{
perror("ioctl ...");
return -1;
}
}
(4)V4l2框架操作摄像头画面数据
cpp
1)输入一个命令获取摄像头设备文件:v4l2-ctl --list-devices
2)编写一个Qt程序
1)窗口类 --- 实现摄像头初始化 + 界面设计
1.实现摄像头初始化
第一步:打开摄像头---open
第二步: 设置V4L2的硬件参数
引入V4L2 的一个结构体 struct v4l2_format,用来设置摄像头参数 或者 获取摄像头参数
1.设置视频捕获类型:type = V4L2_BUF_TYPE_VIDEO_CAPTURE
2.设置画面像素点格式:JPEG YUYV... fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
3.根据产品终端的分辨率去设置 摄像头画面的分辨率 fmt.pix.width = 640; fmt.pix.height = 480;
4.调用ioctl函数结合VIDIOC_S_FMT 进行设置
第三步:设置缓存区的个数,以及缓存的形式为:内存映射
引入V4L2 的一个结构体 v4l2_requestbuffers 用来设置缓存区的个数,以及缓存的形式为:内存映射。
1. 设置 视频捕获类型:type = V4L2_BUF_TYPE_VIDEO_CAPTURE
2. 设置缓存区数量:3 reques_buffers.count = 3;
3. 设置缓存区缓存方式为内存映射:reques_buffers.memory = V4L2_MEMORY_MMAP;
4. 调用ioctl函数结合VIDIOC_REQBUFS 进行设置
第四步:申请 缓存区 把缓存区加到 缓存队列中 获取获取缓存区画面 ,重新把缓存区加到 缓存队列
引入V4L2 的一个结构体 v4l2_buffer 用来存放3个缓存区的信息,配合 指针数组长度为3保存保存缓存区首地址
1. 循环3次查询缓存区信息,把获取到的缓存区信息中的 缓存区映射偏移量,才自己去映射申请缓存区
2. 设置 视频捕获类型 v4l2_buffer[0~2].type = V4L2_BUF_TYPE_VIDEO_CAPTURE
3. 设置缓存区缓存方式为内存映射:v4l2_buffer[0~2].memory = V4L2_MEMORY_MMAP;
4. 设置要查询第几个缓存区:buffer[index].index = index;
5. 调用ioctl函数结合VIDIOC_QUERYBUF 进行获取每一个缓存区的信息
6. 得到缓存区的大小已经缓存区的偏移量进行mmap映射,mmap的返回值 存放 指针数组中
[index].length
[index].m.offset
7. 把三个缓存区添加到缓存队列,能让摄像头把画面存进缓存区中
2)线程任务类 viod task():线程任务函数
第一步:启动摄像头工作
引入V4L2 的一个enum v4l2_buf_type 启动色摄像头
1. 设置 视频捕获类型:type = V4L2_BUF_TYPE_VIDEO_CAPTURE
2. 设置开启视频流 :调用ioctl函数结合 VIDIOC_STREAMON 进行设置
第二步骤:从缓存队列中 取出缓存区,并且显示缓存图片数据,重新把缓存区加到 缓存队列
引入V4L2 的一个结构体 v4l2_buffer 用来存放3个缓存区的信息
1. 设置 视频捕获类型:.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
2. 设置缓存区缓存方式为内存映射:.memory = V4L2_MEMORY_MMAP;
while(1)
{
3. 调用ioctl函数结合VIDIOC_DQBUF 进行从队列中取出缓冲区
4.通过指针数据指向的缓存区得到画面像素点 要转成 rgb格式
5. 存放rgb格式转成qt RGB::888格式设置给 标签显示
6.重新把缓存区加到 缓存队列 ioctl结合 VIDIOC_QBUF
}
第三步:munmap + close
(5)实现版本
C语言版本:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <string.h>
void YUYVtoRGB(const unsigned char* yuyvData, unsigned char* rgbData, int width, int height) {
int imageSize = width * height * 2; // 图像数据大小,每个像素占用2个字节
int rgbIndex = 0; // RGB数据索引
for (int i = 0; i < imageSize; i += 4) {
unsigned char y0 = yuyvData[i]; // 当前像素的亮度分量
unsigned char u = yuyvData[i + 1]; // 当前像素的色度分量(U)
unsigned char y1 = yuyvData[i + 2]; // 下一个像素的亮度分量
unsigned char v = yuyvData[i + 3]; // 下一个像素的色度分量(V)
int c = (int)y0 - 16; // 调整亮度分量的范围
int d = (int)u - 128; // 调整色度分量的范围
int e = (int)v - 128; // 调整色度分量的范围
int r = (298 * c + 409 * e + 128) >> 8; // 计算红色分量
int g = (298 * c - 100 * d - 208 * e + 128) >> 8; // 计算绿色分量
int b = (298 * c + 516 * d + 128) >> 8; // 计算蓝色分量
// 将RGB分量的值限制在合法范围内(0-255)
r = (r < 0) ? 0 : ((r > 255) ? 255 : r);
g = (g < 0) ? 0 : ((g > 255) ? 255 : g);
b = (b < 0) ? 0 : ((b > 255) ? 255 : b);
// 存储转换后的RGB分量值到输出缓冲区
rgbData[rgbIndex++] = (unsigned char)r;
rgbData[rgbIndex++] = (unsigned char)g;
rgbData[rgbIndex++] = (unsigned char)b;
c = (int)y1 - 16; // 调整亮度分量的范围
r = (298 * c + 409 * e + 128) >> 8; // 计算红色分量
g = (298 * c - 100 * d - 208 * e + 128) >> 8; // 计算绿色分量
b = (298 * c + 516 * d + 128) >> 8; // 计算蓝色分量
r = (r < 0) ? 0 : ((r > 255) ? 255 : r);
g = (g < 0) ? 0 : ((g > 255) ? 255 : g);
b = (b < 0) ? 0 : ((b > 255) ? 255 : b);
rgbData[rgbIndex++] = (unsigned char)r;
rgbData[rgbIndex++] = (unsigned char)g;
rgbData[rgbIndex++] = (unsigned char)b;
}
}
void Show(int * mmap_lcd,char * rgbdata)
{
for(int y=0,n=0;y<480; y++)
{
for(int x=0; x<640; x++,n+=3)
{
*(mmap_lcd + 800*y+x) = rgbdata[n] << 16 | rgbdata[n+1] << 8 | rgbdata[n+2]<<0;
}
}
return ;
}
int main()
{
int lcd_fd = open("/dev/fb0",O_RDWR);
if(lcd_fd == -1)
{
perror("open ...");
return -1;
}
int * mmap_lcd = mmap(NULL,1024*600*4,PROT_READ | PROT_WRITE,MAP_SHARED,lcd_fd,0);
if(mmap_lcd == (int *)MAP_FAILED)
{
perror("mmap ...");
return -1;
}
//打开摄像头
int video_fd = open("/dev/video7",O_RDWR);
if(video_fd == -1)
{
perror("open ...");
return -1;
}
//定义存取摄像头属性的专属结合体
struct v4l2_format format;
memset(&format,0,sizeof(format));
//设置媒体类型
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//设置摄像头捕获的每一帧图片像素点的格式
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
//进行设置
if(ioctl(video_fd,VIDIOC_S_FMT,&format) == -1)
{
perror("iotcl ...");
return -1;
}
else
{
printf("设置摄像头格式成功!\n");
}
//验证是否设置成功
if(ioctl(video_fd,VIDIOC_G_FMT,&format) == -1)
{
perror("iotcl ...");
return -1;
}
else
{
printf("宽:%d\n",format.fmt.pix.width);
printf("高:%d\n",format.fmt.pix.height);
printf("像素格式:%d\n",format.fmt.pix.pixelformat);
if(format.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
{
printf("摄像头格式为YUYV!---%d\n",V4L2_PIX_FMT_YUYV);
}
else if(format.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG)
{
printf("摄像头格式为JPEG!---%d\n",V4L2_PIX_FMT_MJPEG);
}
}
//设置缓存队列个数
struct v4l2_requestbuffers reqbuf;
memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.count = 3; // 申请缓冲区个数为 3
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置缓冲区类型为视频捕获
reqbuf.memory = V4L2_MEMORY_MMAP; // 设置缓冲区存储方式(内存映射)
if (ioctl(video_fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
perror("VIDIOC_REQBUFS");
return -1;
}
printf("Supported buffer count: %u\n", reqbuf.count);
struct v4l2_buffer buffer[reqbuf.count];
char * start[reqbuf.count];
//进行缓存区映射,获取三个缓存区的首地址
for(int index = 0; index < reqbuf.count; index++)
{
//查询第 index个缓存区的信息
memset(&buffer[index],0,sizeof(struct v4l2_buffer));
buffer[index].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer[index].memory = V4L2_MEMORY_MMAP;
buffer[index].index = index;
if(ioctl(video_fd,VIDIOC_QUERYBUF,&buffer[index]) == -1)
{
perror("ioctl ...");
return -1;
}
//获取缓存区的大小和偏移量
start[index] = (char *)mmap(NULL,buffer[index].length,PROT_READ | PROT_WRITE,MAP_SHARED,video_fd,buffer[index].m.offset);
if(start[index] == (char *)MAP_FAILED)
{
perror("mmap ...");
return -1;
}
if(ioctl(video_fd,VIDIOC_QBUF,&buffer[index]) == -1)
{
perror("ioctl ...");
return -1;
}
}
//启动摄像头进行拍摄
enum v4l2_buf_type vtype = V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置视频捕获格式
if(ioctl(video_fd,VIDIOC_STREAMON,&vtype) == -1)
{
perror("ioctl ...");
return -1;
}
struct v4l2_buffer v4lbuf;
memset(&v4lbuf,0,sizeof(v4lbuf));
v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置视频捕获格式
v4lbuf.memory = V4L2_MEMORY_MMAP; //设置缓存方式是映射
int i=0;
char rgb[640*480*3];
while(1)
{
v4lbuf.index = i % 3;
//获取摄像头缓存区
if(ioctl(video_fd,VIDIOC_DQBUF,&v4lbuf) == -1)
{
perror("ioctl ...");
return -1;
}
//把缓存区中的图片一帧数据 从yuyv转成rgb
YUYVtoRGB(start[i % 3],rgb,640,480);
Show(mmap_lcd,rgb);
i++;
}
return 0;
}
二、音视频
1、QT内置视频类
(1)Qt播放视频文件的前置要求
cpp
1、windows安装 视频解码器,可从百度网盘下载:
链接:https://pan.baidu.com/s/1sCaMw0V_wGQ1d-0K_8Jx0w?pwd=3br0
提取码:3br0
2、Qt播放视频的类(Qt自带的多媒体框架)
(1)QMediaPlayer、QMediaPlaylist:控制视频的播放、管理音频和字幕等相关的功能
(2)QVideoWidget:将视频显示在窗口屏幕上
3、在pro文件中添加对应的插件QMediaPlayer 和 QVideoWidget 的功能:
QT += multimedia
QT += multimediawidgets
(2)QMediaPlayer的常用接口
QMediaPlayer:Qt中用于控制视频播放的核心类,它提供了一系列的接口来管理视频的加载、播放、暂停、停止等操作。
cpp
QMediaPlayer的常用接口:
setMedia(const QMediaContent &media):设置要播放的媒体内容。可以传递一个QMediaContent对象,指定要播放的文件、URL或流。
play():开始播放视频。
pause():暂停视频播放。
stop():停止视频播放。
duration():返回视频的总时长。
position():返回当前视频播放的位置。
setPosition(qint64 position):设置视频播放的位置。
state():返回当前视频播放的状态,如QMediaPlayer::PlayingState表示正在播放,QMediaPlayer::PausedState表示已暂停,QMediaPlayer::StoppedState表示已停止。
error():返回当前的错误状态,如QMediaPlayer::NoError表示没有错误,QMediaPlayer::ResourceError表示资源错误,QMediaPlayer::FormatError表示格式错误等。
mediaStatus():返回当前媒体的状态,如QMediaPlayer::LoadingMedia表示正在加载媒体,QMediaPlayer::LoadedMedia表示媒体已加载,QMediaPlayer::EndOfMedia表示媒体播放结束等。
volume():返回当前音量的值。
setVolume(int volume):设置音量的值。
muted():返回当前是否静音。
setMuted(bool muted):设置是否静音。
mediaChanged(const QMediaContent &media):当媒体内容发生变化时,发送此信号。
stateChanged(QMediaPlayer::State state):当播放状态发生变化时,发送此信号。
(3)QVideoWidget的常用接口
QVideoWidget:用于显示视频的Qt控件,它提供了一系列的接口来管理视频的显示、尺寸、比例等操作。
cpp
QVideoWidget的常用接口:
setAspectRatioMode(Qt::AspectRatioMode mode):设置视频的显示模式,可以选择Qt::IgnoreAspectRatio、Qt::KeepAspectRatio、Qt::KeepAspectRatioByExpanding三种。
Qt::IgnoreAspectRatio:表示不保持图片宽高比,直接拉伸填充整个控件。
Qt::KeepAspectRatio:表示保持图片宽高比,缩放图片大小使其能够完全展示在控件内。
Qt::KeepAspectRatioByExpanding:表示保持图片宽高比,将图片缩放至最大,并尽可能地填充整个控件,如果图片宽高比与控件宽高比不同,则会有一部分控件区域没有被填满。
setFullScreen(bool fullScreen):设置是否全屏显示视频。
setMedia(const QMediaContent &media, QIODevice *stream = nullptr):设置要显示的媒体内容,可以传递一个QMediaContent对象和一个QIODevice对象。
setMediaPlayer(QMediaPlayer *player):设置QVideoWidget关联的QMediaPlayer对象。
sizeHint():返回QVideoWidget的最佳大小。
paintEvent(QPaintEvent *event):重写QVideoWidget的paintEvent事件,实现视频的显示。
(4)视频的播放
mainwindow.h:

mainwindow.cpp:


结果:

(5)显示视频进度条
①QSlider 适合做可交互的进度条:如果你需要让用户通过拖动滑块来调整视频播放位置(快进/快退),使用 QSlider 最合适。它支持鼠标拖拽交互,通过 valueChanged 或 sliderMoved 信号响应操作。
②QProgressBar 适合只读显示进度:如果你只需要直观展示播放进度,不需要用户交互,使用 QProgressBar 更简洁。它只负责显示,不接受鼠标操作。
③两者共同的用法:都通过 setMinimum(0)、setMaximum(总时长)、setValue(当前播放位置) 来控制进度显示,单位通常用毫秒(ms)。
④QSlider 交互优化建议:建议连接 sliderReleased 信号而非 valueChanged,这样仅在用户松开滑块时才跳转视频位置,避免拖拽过程中频繁 seek 导致界面卡顿。配合 setTracking(false) 可进一步提升性能。
⑤选择依据:根据需求选择------需要用户拖动就用 QSlider,仅展示进度就用 QProgressBar。
mainwindow.h:

mainwindow.cpp:





结果:

mainwindow.cpp中滑块添加样式:

结果:

2、Linux的mplayer音视频播放器
mplayer安装:
sudo apt-get update
sudo apt-get install mplayer
(1)Linux mplayer 介绍
cpp
MPlayer 是一个开源的媒体播放器,支持几乎所有视频和音频格式。它主要运行在 Linux 操作系统上,但也有 Windows 和其他平台的版本。MPlayer 可以通过命令行界面运行,并且支持几乎所有常见的视频格式,比如 AVI、MP4、MKV、FLV、MOV 等,还能处理不同的音频格式如 MP3、AAC、OGG 等。
MPlayer 的特点:
1)多平台支持:不仅支持 Linux,MPlayer 还可以运行在 Windows、MacOS、FreeBSD 和其他类 Unix 系统上。
2)支持广泛的格式:几乎支持所有主流的视频和音频格式,能够播放压缩文件(如 Zip)中的多媒体文件。
3)硬件加速:MPlayer 支持硬件加速,利用图形卡进行视频解码,提升播放性能,尤其在播放高分辨率视频时尤为重要。
4)命令行控制:MPlayer 主要是一个基于命令行的播放器,但也支持通过图形用户界面(GUI)进行控制,提供了丰富的命令行参数来控制播放、字幕、音量等。
5)字幕支持:MPlayer 支持字幕文件(如 SRT、SUB 等),并能够调整字幕的显示效果,包括字体、大小、颜色等。
6)实时流媒体:它还支持网络流媒体播放,可以从互联网上直接流式播放音频和视频。
(2)控制mplayer的管道命令
cpp
具体命令如下
播放进度:
get_time_length:获取时间总长度 发送get_time_length会返回ANS_LENGTH
get_time_pos:获取当前时间进度 发送get_time_pos会返回ANS_TIME_POSITION
设置音量:
-af volume=值
volume=1.0:表示原始音量(100%)。
volume=0.5:表示 50% 的音量。
volume=0.0:静音。
volume=1.0:原始音量。可以设置大于1.0 的值(例如 volume=2.0),但可能会导致音频失真。
设置播放进度:
seek n 0:表示设置从n s开始播放
设置暂停和继续:
pause
(3)通过文件对话框选择音乐添加到listwidget中
cpp
注意事项:
1)如果播放器已经在播放了,则需要关闭,不让会影响文件的选择
2)可通过静态函数创建文本对话框,注意设置文本过滤位MP3和AVI
3)设置listwidget中的列表项的高度,不让列表项会很细

(4)获取当前ListWidget选择的条目项,使用进程运行播放器播放
cpp
注意事项:
1)需要判断播放器进程是否在运行,如果没在运行才能播放新音频文件
2)使用listwidget的currentItem获取点击的item,当获取的item不为空时才能播放指定文件
3)使用QString中arg带参组合字符串组成启动播放器播放命令,启动进程运行mplayer播放指定文件

(5)获取视频总时间和当前时间
cpp
注意事项:
1)使用QProcess的write发送 "获取播放文件的时间总长度"的命令,使用信号与槽获机制取读取返回的数据
2)使用QTimer每隔一秒调用write发送"获取播放文件的当前时间",使用信号与槽获机制取读取返回的数据
3)根据获取的文件时间总长时间设置滑块的最大值
4) 根据每隔一秒获取的文件当前时间设置滑块的当前值
定时器初始化:

定时器槽函数:

发送获取命令:

获取时间总长度和当前参数的槽函数Get_Media_File_inf,并且设置滑块最大值和当前值:

(6)使用QSlider滑块控件控制播放进度
cpp
注意事项:
1)判断播放器进程是否在运行,如果在运行才能设置播放进度
2)使用滑块控件的sliderReleased槽函数和value获取最后滑块停止时的进度值来设置播放进度
3)调用QProcess的write来发送设置播放进度

(7)暂停和继续
cpp
注意事项:
1)判断播放器进程是否在运行,如果在运行才能设置暂停和继续
2)用整形标志位判断状态
播放:则暂停
暂停:则继续
3)调用QProcess的write来发送设置暂停和继续

(8)快进和快退
cpp
注意事项:
1)判断播放器进程是否在运行,如果在运行才能设置暂停和播放


(9)使用slider滑条设置音量以及静音
cpp
注意事项:
1)使用sliderMoved获取滑块移动过程中的进度值取设置标签文本
2)使用sliderMoved获取滑块移动过程中的进度值取设置当前音量
3)把获取到的滑块进度值(音量) 除 100 ,用浮点型存放
4)调用QProcess的write来发送设置音量的命令

3、Linux的FFmpeg
(1)前置条件
① 对ffmpeg的认知
cpp
ffmepg在三大平台上都有:windos、linux、 ios
FFmpeg是一个功能强大的开源多媒体框架,不仅仅是一个音视频编码库。它提供了一套完整的工具和库,用于处理、转码、录制、流媒体和播放音视频文件。
以下是FFmpeg的主要功能:
音视频编码:FFmpeg支持多种音频和视频编码格式,包括常见的如AAC、MP3、H.264、VP9等,以及一些专有格式。
格式转换:FFmpeg可以将音视频文件从一种格式转换为另一种格式,如将MP4文件转换为FLV文件。
录制和捕捉:FFmpeg可以从摄像头、麦克风或其他输入设备录制音视频。
流媒体传输:FFmpeg可以将音视频流实时传输到网络上,支持常见的流媒体协议如RTMP、HLS等。
视频处理:FFmpeg提供了一些视频处理功能,如裁剪、缩放、旋转、滤镜等。
音频处理:FFmpeg可以进行音频混音、音量调节、降噪等音频处理操作。
字幕处理:FFmpeg支持添加、提取和处理视频字幕。
② ffmpeg的下载
访问FFmpeg官方网站:https://ffmpeg.org/
下载Linux The Bionic Beaver 仿生海狸版本的ffmpeg 的具体操作:
cpp
"The Bionic Beaver"(仿生海狸)是Ubuntu(一个广泛使用的Linux发行版)的一个版本代号。Ubuntu每个版本都会有一个代号,通常以一个动物名称和一个形容词的组合命名,以表示该版本的特点或目标。"Bionic"意味着仿生的,而"Beaver"则指的是海狸。
在Ubuntu 18.04版本中,代号为"The Bionic Beaver",它是一个长期支持(LTS)版本,于2018年4月发布。这个版本的目标是提供稳定、可靠且高效的操作系统,具备良好的硬件兼容性和安全性。




③ 安装ffmpeg之前,需要先安装SDL
安装SDL:
cpp
不一定需要安装SDL,但如果你想使用FFmpeg的一些图形化工具,如ffplay,则需要安装SDL库。
SDL是一个跨平台的多媒体库,它提供了对音频、视频、鼠标、键盘、游戏控制器等硬件设备的支持。
在Linux上安装SDL库通常很简单,你可以通过包管理器直接安装。
例如,在Ubuntu上,你可以使用以下命令安装SDL库:
sudo apt-get install libsdl2-dev
补充:如果安装异常,则需要安装一些依赖库
sudo apt-get install libx11-dev
sudo apt-get install xorg-dev
sudo apt-get install libsdl2-image-dev
sudo apt-get install libsdl2-mixer-dev
sudo apt-get install libsdl2-ttf-dev
sudo apt-get install libsdl2-gfx-dev
以下是每个依赖库的简要说明:
libx11-dev:X11是Linux和Unix等操作系统上常见的图形用户界面(GUI)系统,这个库提供了对X11窗口系统的支持。SDL库使用X11窗口系统来创建和管理窗口。
xorg-dev:这是一个基于X11的图形系统,并提供了一些额外的功能和库,包括键盘、鼠标、显示器和输入设备的驱动程序。SDL库需要这些功能才能实现全面的窗口管理和事件处理。
libsdl2-image-dev:这个库提供了对图像文件的支持,包括BMP、PNG、JPEG、GIF等格式。SDL库使用这个库来加载和显示图像文件。
libsdl2-mixer-dev:这个库提供了对音频文件的支持,包括WAV、MP3、OGG等格式。SDL库使用这个库来播放声音和音乐。
libsdl2-ttf-dev:这个库提供了对TrueType字体的支持。SDL库使用这个库来渲染文本和字体。
libsdl2-gfx-dev:这个库提供了一些额外的图形功能,如线条、矩形、圆形、椭圆形等。SDL库使用这个库来绘制图形和几何图形。
检查是否已将成功安装SDL:
dpkg -l | grep libsdl
sdl2-config --version
cpp
dpkg -l | grep libsdl这个命令会列出所有包含"libsdl"字符串的软件包,如果SDL库已经安装,你应该能够看到它们的列表。如果没有安装,则不会出现任何内容。
另外,你还可以通过在终端中输入sdl2-config --version命令来检查SDL库的版本。如果已经安装,则会输出当前安装的SDL库的版本号。如果没有安装,则会提示未找到命令或软件包。
验证现象:列出所有包含"libsdl"字符串的软件包

libsdl2-2.0-0:amd64 : 是SDL库的运行时库,用于在运行时加载和使用SDL功能。
libsdl2-dev:amd64 :是SDL库的开发文件,用于编译和开发基于SDL的应用程序。 这两个软件包的状态都标记为"ii",表示已经成功安装在你的系统上。同时,你还可以看到相应的版本号。
验证现象:检查SDL库的版本

(2)安装编译ffmpeg
① 运行configure进行编译安装之前的配置
cpp
解压ffmpeg源码包到家目录,进入家目录输入配置命令:./configure --prefix=/home/dante/ffmpeg/bulid
指定ffpmeg的安装路径:--prefix=/home/dante/ffmpeg/bulid
出错现象:

解决方法如下:
cpp
方法一:安装yasm ,命令:sudo apt-get install yasm
方法二:在配置命令后面加上 --disable-yasm
yasm 是一个汇编器,用于将汇编语言代码转换为机器码。它是一个通用的、可重定位的汇编器,适用于多种处理器架构。
解决问题之后继续:

上图可以看出 shared 的选项是 no ,说明当前配置不能编译出动态库,而我们经常需要用到动态库,所以需要在配置命令后面加上 --enable-shared。
还有就是当前并没有加载到 SDL 的库,那么我们需要做如下操作:
cpp
在命令行终端设置 PKG_CONFIG_PATH变量,指定 SDL 编译生成的 pkgconfig 文件路径
export PKG_CONFIG_PATH=/home/dante/ffmpeg/build/lib/pkgconfig:$PKG_CONFIG_PATH
我们为了开启生成动态库功能,以及生成库的路径,最终配置命令如下:
cpp
./configure --prefix=/home/dante/ffmpeg/build/ --enable-shared --extra-cflags=-I/home/dante/ffmpeg/build/include --extra-ldflags=-L/home/dante/ffmpeg/build/lib --enable-gpl
执行现象:

② 上面配置成功之后,进行编译
输入:make
编译部分现象截图:

③ 编译成功之后,进行安装ffmpeg
输入:make install
安装部分现象截图:

(3)测试ffmpeg是否安装成功
① 查看 FFmpeg 版本
cpp
进入ffmpeg安装路径中的bin目录输入命令:./ffmpeg -version

可能出现的错误,缺少libavdevice动态库,输入sudo apt-get install libavdevice-dev。
cpp
如果安装库文件并不能解决问题,那么你可以尝试手动添加库文件路径。假设你的 libavdevice.so.57 库文件在 /usr/local/lib 目录下,你可以运行以下命令将这个目录添加到系统的库文件路径中:
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
libavdevice-dev 库是 FFmpeg 提供的用于处理音视频设备的库。它包含了与音频采集、视频采集和音视频设备输入输出相关的功能和接口。通过使用
libavdevice-dev,你可以在应用程序中实现音视频的采集和播放功能,包括从摄像头、麦克风或其他音视频设备获取数据,以及将数据输出到音视频设备进行播放。
解决问题之后:

② 测试 ffplay 播放 MP4 或者查看音视频文件信息
cpp
export LD_LIBRARY_PATH=/home/dante/ffmpeg/build/lib/
./ffplay /mnt/d/code/ffmpeg_test/QT\ --\ 摄像头抓拍.mp4
./ffprobe /mnt/d/code/ffmpeg_test/QT\ --\ 摄像头抓拍.mp4

查看现象:

拓展:如果是使用Ubuntu子系统播放视频,子系统是不带窗口桌面的操作系统,不支持ffmpeg使用sdl播放视频的,当然你可以安装Virtual Framebuffer(虚拟帧缓冲)给SDL提供图形化界面。
cpp
1、安装Xvfb软件包
打开终端,并使用以下命令安装Xvfb软件包:
sudo apt-get install xvfb
2、启动Virtual Framebuffer
使用以下命令启动Virtual Framebuffer:
Xvfb :1 -screen 0 <width>x<height>x16
例如,要创建一个分辨率为1024x768的Virtual Framebuffer,可以运行以下命令:
Xvfb :1 -screen 0 1024x768x16
3、设置DISPLAY环境变量
在终端中运行以下命令,将DISPLAY环境变量设置为刚刚创建的Virtual Framebuffer,这将告诉SDL程序使用Virtual Framebuffer作为图形输出设备:
export DISPLAY=:1
4、运行播放视频
xvfb-run -a ./ffplay /mnt/d/code/ffmpeg_test/QT\ --\ 摄像头抓拍.mp4
