本项目实现一个简单的视频播放器,支持打开本地视频文件并播放,以及提供进度条展示播放进度和更改视频进度,支持视频播放/暂停,声音/静音状态切换,以及设置按钮图标。
环境准备 #
下载并安装 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(); }
成果展示 #
运行后打开视频播放,窗口标题显示路径。
播放/暂停按钮图标演示。
音量/静音按钮图标演示。