本项目实现一个简单的视频播放器,支持打开本地视频文件并播放,以及提供进度条展示播放进度和更改视频进度,支持视频播放/暂停,声音/静音状态切换,以及设置按钮图标。
环境准备 #
下载并安装 LAVFilters 解码器
https://github.com/Nevcairiel/LAVFilters/releases
按照默认设置进行安装。

如果在后续播放视频中出现 doRender: Unknown error 0x80040266 类似错误. 则表示解码库安装失败。
设计方案 #
使用QMediaPlayer进行播放音视频,QMediaPlayer是Qt封装的一个与平台无关的统一音视频操作接口,底层的解码还需要依靠平台完成,所以在项目开始之前我们需要安装 LAVFilters 解码器。
QMediaPlayer提供常用的接口如下

这些接口能够完成播放,暂停,停止,倍速播放,设置音量等基本操作。
但QMediaplayer类只负责多媒体控制,没有在UI显示功能,所以我们需要一个widget来显示视频内容。
Qt提供一个QVideoWidget来支持QMediaplayer的图像输出,我们可以使用QMediaplayer::setVideoOutput函数进行指定用来输出图像的组件,setVideoOutput支持的UI组件有QVideoWidget,QGraphicsVideoItem,QAbstractVideoSurface这三种。
QVideoWidget类虽然使用比较方便,但不支持获取视频帧,所以需要使用QAbstractVideoSurface类来达到这种效果。QAbstractVideoSurface是抽象类,需要使用子类去继承才可以实例化。
QAbstractVideoSurface类成员函数如下。

在用户界面上使用一个QLabel显示视频图像,底下一个进度条进行管理视频进度,最底下是一排控制按钮,包括打开,播放/暂停,静音。
新建项目

取消选中,不成声*.ui文件。

视频播放需要用到Qt的多媒体模块,在pro文件中如下图所示位置加入multimedia multimediawidgets。

在项目上右键,选择Add New。

选择C++类。

Base Class选择Custom设置为QAbstractVideoSurface,类名自定义为VideoSuface。

完成后项目目录如下。

项目代码 #
整个视频播放器代码如下。
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMediaPlayer>
#include <QVideoWidget>
#include <QMediaPlaylist>
#include <QPushButton>
#include <QToolButton>
#include <QLabel>
#include <QSlider>
#include "videosurface.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QMediaPlayer m_player;
QMediaPlaylist m_player_list;
QVideoWidget *m_video_widget;
VideoSurface m_video_surface;
QLabel m_lb_video;
// 控制按钮
QPushButton *m_pb_open;
QToolButton *m_tb_play;
QToolButton *m_tb_volume;
QSlider *m_sd_schedule;
private:
void init_ui();
void video_play();
private slots:
void open_file();
void show_frame(QVideoFrame frame);
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QHBoxLayout>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QStatusBar>
#include <QStyle>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
init_ui();
}
MainWindow::~MainWindow()
{
}
void MainWindow::init_ui()
{
m_pb_open = new QPushButton("open");
m_tb_play = new QToolButton();
m_tb_volume = new QToolButton();
m_tb_play->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
m_tb_volume->setIcon(style()->standardIcon(QStyle::SP_MediaVolume));
connect(m_pb_open, SIGNAL(clicked(bool)), this, SLOT(open_file()));
connect(m_tb_play, &QToolButton::clicked, this, &MainWindow::video_play);
connect(m_tb_volume, &QToolButton::clicked, [=](){
m_player.setMuted(!m_player.isMuted());
if (m_player.isMuted()) {
m_player.setMuted(true);
m_tb_volume->setIcon(style()->standardIcon(QStyle::SP_MediaVolumeMuted));
} else {
m_player.setMuted(false);
m_tb_volume->setIcon(style()->standardIcon(QStyle::SP_MediaVolume));
}
});
QHBoxLayout *hl_control = new QHBoxLayout();
hl_control->addWidget(m_pb_open);
hl_control->addWidget(m_tb_play);
hl_control->addWidget(m_tb_volume);
m_video_widget = new QVideoWidget();
m_sd_schedule = new QSlider(Qt::Horizontal);
connect(m_sd_schedule, &QSlider::sliderMoved, [=] () {
m_player.setPosition(m_sd_schedule->value() * 1000);
});
connect(&m_player, &QMediaPlayer::durationChanged, [=](){
m_sd_schedule->setRange(0, m_player.duration() / 1000);
});
connect(&m_player, &QMediaPlayer::positionChanged, [=](){
m_sd_schedule->setValue(m_player.position() / 1000);
});
m_player.setPlaylist(&m_player_list);
m_player.setVideoOutput(&m_video_surface);
connect(&m_video_surface, &VideoSurface::frame_ready,
this, &MainWindow::show_frame);
QVBoxLayout *vl_main = new QVBoxLayout();
vl_main->addWidget(&m_lb_video);
vl_main->addWidget(m_sd_schedule);
vl_main->addLayout(hl_control);
QWidget *widget = new QWidget();
widget->setLayout(vl_main);
this->setCentralWidget(widget);
}
void MainWindow::open_file()
{
QString filename;
filename = QFileDialog::getOpenFileName(this, "Open File", "./" ,"Video(*.mp4 *.avi *.rmvb)");
if (filename.isEmpty()) {
this->statusBar()->showMessage("file is empty");
} else {
this->setWindowTitle(filename);
m_player_list.addMedia(QUrl(filename));
m_player.stop();
m_player_list.next();
video_play();
}
}
void MainWindow::show_frame(QVideoFrame frame)
{
QVideoFrame frametodraw(frame);
if(!frametodraw.map(QAbstractVideoBuffer::ReadOnly))
return;
QImage img = QImage(frame.bits(),
frame.width(),
frame.height(),
frame.bytesPerLine(),
QVideoFrame::imageFormatFromPixelFormat(frametodraw.pixelFormat())
//QImage::Format_RGB32
);
// img = img.mirrored(false, true);//图像垂直翻转(图像是倒置的,调用该函数可正置。同时该函数也解决了频繁的内存错误引起的崩溃, 原因不明)
QPixmap pix = QPixmap::fromImage(img);
m_lb_video.setPixmap(pix);
frametodraw.unmap();
}
void MainWindow::video_play()
{
switch (m_player.state()) {
case QMediaPlayer::StoppedState:
case QMediaPlayer::PausedState:
m_player.play();
m_tb_play->setIcon(style()->standardIcon(QStyle::SP_MediaPause));
break;
case QMediaPlayer::PlayingState:
m_player.pause();
m_tb_play->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
break;
}
}
videosurface.h
#ifndef VIDEOSURFACE_H
#define VIDEOSURFACE_H
#include <QAbstractVideoSurface>
#include <QObject>
#include <QVideoFrame>
#include <QVideoSurfaceFormat>
class VideoSurface : public QAbstractVideoSurface
{
Q_OBJECT
public:
VideoSurface();
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const;
bool present(const QVideoFrame &frame) ;
bool start(const QVideoSurfaceFormat &videoformat);
void stop();
signals:
void frame_ready(QVideoFrame frame);
};
#endif // VIDEOSURFACE_H
videosurface.cpp
#include "videosurface.h"
#include <QDebug>
VideoSurface::VideoSurface(): QAbstractVideoSurface()
{
}
QList<QVideoFrame::PixelFormat> VideoSurface::supportedPixelFormats(
QAbstractVideoBuffer::HandleType type) const
{
if(type == QAbstractVideoBuffer::NoHandle)
{
return QList<QVideoFrame::PixelFormat>()
<< QVideoFrame::Format_RGB32;
}
else
return QList<QVideoFrame::PixelFormat>();
}
bool VideoSurface::present(const QVideoFrame &frame)
{
if(frame.isValid())
{
emit frame_ready(frame);
// qDebug()<<__func__;
return true;
}
stop();
return false;
}
bool VideoSurface::start(const QVideoSurfaceFormat &videoformat)
{ //格式是RGB32
if(QVideoFrame::imageFormatFromPixelFormat(videoformat.pixelFormat()) != QImage::Format_Invalid && !videoformat.frameSize().isEmpty()){
QAbstractVideoSurface::start(videoformat);
return true;
}
return false;
}
void VideoSurface::stop()
{
QAbstractVideoSurface::stop();
}
成果展示 #
运行后打开视频播放,窗口标题显示路径。


播放/暂停按钮图标演示。


音量/静音按钮图标演示。

