Qt QMediaPlayer + QAbstractVideoSurface 实现低CPU占用率播放本地视频并截取帧图像
第一个天坑:QVideoProbe
网上大部分文献分享如何用QMediaPlayer播放本地视频,并用QVideoProbe实现抓取视频帧。发现这些文献完全是照抄Qt的帮助文档中的内容(https://doc.qt.io/qt-5/qvideoprobe.html)
QMediaPlayer *player = new QMediaPlayer();
QVideoProbe *probe = new QVideoProbe;
connect(probe, SIGNAL(videoFrameProbed(QVideoFrame)), this, SLOT(processFrame(QVideoFrame)));
probe->setSource(player); // Returns true, hopefully.
player->setVideoOutput(myVideoSurface);
player->setMedia(QUrl::fromLocalFile("observation.mp4"));
player->play(); // Start receiving frames as they get presented to myVideoSurface
但本人在Win10 + Qt5.15.2和Qt5.14.2上实验结果发现QVideoProbe的videoFrameProbed信号压根不触发,具体原因不明,不知是否是操作系统定制化功能。实乃坑中之坑
第二个坑:QScreen截取QVideoWidget图像
这个问题在于截取的图像分辨率是QWidget的分辨率,不是视频帧原始分辨率,毫无卵用
靠谱一点的坑:继承QAbstractVideoSurface捕捉视频帧
https://blog.csdn.net/weixin_39956356/article/details/96484789
这个方法能获得每一帧,但是代价就是获得每一帧后进行绘制,都相当于每帧都是关键帧,CPU彪的好高,不能利用GPU加速,还是不能用
取长补短:只在需要截图的时刻用继承QAbstractVideoSurface捕捉视频帧,之后换回QVideoWidget显示QMediaPlayer的输出
首先播放时用QMediaPlayer的setVideoOutput方法将QVideoWidget设置为输出。点击截图按钮时,将继承的QAbstractVideoSurface的类实例通过QMediaPlayer的setVideoOutput方法输出,接收到QVideoFrame后转成QImage存图,最后再通过QMediaPlayer的*setVideoOutput方法将QVideoWidget**设置为输出。实测CPU占用率非常之小。
界面如下
代码如下:
QAbstractVideoSurface的继承类
videosurface.h
//提取视频帧
#ifndef VIDEOSURFACE_H
#define VIDEOSURFACE_H
#include <QObject>
#include <QAbstractVideoSurface>
class VideoSurface : public QAbstractVideoSurface
{
Q_OBJECT
public:
explicit VideoSurface(QObject *parent = 0);
~VideoSurface();
signals:
void showImage(QVideoFrame frame);
protected:
bool present(const QVideoFrame &frame);
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
QAbstractVideoBuffer::HandleType handleType =
QAbstractVideoBuffer::NoHandle) const;
};
#endif // VIDEOSURFACE_H
videosurface.cpp
#include "videosurface.h"
VideoSurface::VideoSurface(QObject *parent) : QAbstractVideoSurface(parent)
{
}
VideoSurface::~VideoSurface()
{
this->stop();
}
QList<QVideoFrame::PixelFormat> VideoSurface::supportedPixelFormats(
QAbstractVideoBuffer::HandleType handleType) const
{
Q_UNUSED(handleType);
QList<QVideoFrame::PixelFormat> listPixelFormats;
listPixelFormats << QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB32
<< QVideoFrame::Format_RGB24
<< QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555
<< QVideoFrame::Format_ARGB8565_Premultiplied
<< QVideoFrame::Format_BGRA32
<< QVideoFrame::Format_BGRA32_Premultiplied
<< QVideoFrame::Format_BGR32
<< QVideoFrame::Format_BGR24
<< QVideoFrame::Format_BGR565
<< QVideoFrame::Format_BGR555
<< QVideoFrame::Format_BGRA5658_Premultiplied
<< QVideoFrame::Format_AYUV444
<< QVideoFrame::Format_AYUV444_Premultiplied
<< QVideoFrame::Format_YUV444
<< QVideoFrame::Format_YUV420P
<< QVideoFrame::Format_YV12
<< QVideoFrame::Format_UYVY
<< QVideoFrame::Format_YUYV
<< QVideoFrame::Format_NV12
<< QVideoFrame::Format_NV21
<< QVideoFrame::Format_IMC1
<< QVideoFrame::Format_IMC2
<< QVideoFrame::Format_IMC3
<< QVideoFrame::Format_IMC4
<< QVideoFrame::Format_Y8
<< QVideoFrame::Format_Y16
<< QVideoFrame::Format_Jpeg
<< QVideoFrame::Format_CameraRaw
<< QVideoFrame::Format_AdobeDng;
//qDebug() << listPixelFormats;
// Return the formats you will support
return listPixelFormats;
}
bool VideoSurface::present(const QVideoFrame &frame)
{
if (frame.isValid())
{
emit showImage(frame);
return true;
}
return false;
}
主界面
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMediaPlayer>
#include <QVideoWidget>
#include <QVideoFrame>
#include "videosurface.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void onShowImage(QVideoFrame frame);
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QMediaPlayer m_player;
QVideoWidget m_widget;
VideoSurface m_videoSurface;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->layout_Main->addWidget(&m_widget);
m_player.setVideoOutput(&m_widget);
m_player.setMedia(QUrl::fromLocalFile("D:/Projects/TestQMediaPlay/Output/Test.avi"));
m_player.play();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onShowImage(QVideoFrame frame)
{
disconnect(&m_videoSurface,SIGNAL(showImage(QVideoFrame)),
this, SLOT(onShowImage(QVideoFrame)));
frame.image().save("frame.jpg");
m_player.setVideoOutput(&m_widget);
}
void MainWindow::on_pushButton_clicked()
{
connect(&m_videoSurface,SIGNAL(showImage(QVideoFrame)),
this, SLOT(onShowImage(QVideoFrame)), Qt::QueuedConnection);
m_player.setVideoOutput(&m_videoSurface);
}
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centerMain">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="layout_Main"/>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>