Qt QMediaPlayer + QAbstractVideoSurface 实现播放本地视频并截取帧图像

34 篇文章 1 订阅

第一个天坑: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>

  • 10
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值