最近需要在qt界面内放一个widget用以播放视频,被推荐了qtav,踩了很多坑
- qtav
- 首先说明,我是需要内网编译,内网开发,然后打包程序,所以直接注入到qt根目录的方式我是不考虑的,以下问题大多建立在这个基础之上
- 网上给的编译教程基本翻烂了,感觉都是一样的,这边推荐是可以试一下官网给的现成的lib库,mscv2015的,如果可用那么恭喜你,逃过一劫,地址我放到下面了
QtAV1.12.0-VS2015x86.7z/download
如果你很不幸和我一样需要自己动手编译,windows平台上我可以提供给诸位的教训如下:- 下请务必确定自己可以使用mscv去按照教程编译而不是mingw,mingw编译出的.a非常难用,我甚至一度想过把他强转成lib
- 你可能会遇到mingw编译通过(可能会提示缺一个Q什么的,自己去补个头文件就好)但是mscv提示lnk2019这种无法理解的问题,请你全局搜索qlistlink,这个库因为时间的问题在一个地方使用了几次这个类型的变量,而如果你的qt版本稍稍新一些,他会导致你出现lnk2019的问题,自己去全部替换成qlist就行,不会影响你的正常使用的
- 这里不是编译的bug而是qtav的bug,我不知道为什么网上讨论的这么少,但是我这边播放视频有近乎一半的概率会出现前几秒视频在黑屏,然后这几秒的视频就丢失了,这个问题如果也困扰到了你,我这边的建议是放弃这个问题,我在github的issue中找到了有同样困惑的大家,但是这个库已经不维护了,如果你看到了github里面的rendme,你就会发现这个新的库,mkd-sdk,他几乎完美的避免了qtav的上述问题
- MDK-SDK
作者直接提供了lib文件和dll文件,以及在github中的使用例子
https://github.com/wang-bin/mdk-examples/blob/master/Qt/README.md
不过要注意,现在使用它去在qt中播放视频必须需要qopenglwidget的支持,请确保你的项目可以这么做 - mdk-sdk的一点小代码
因为我需要绑定一个qslider,我在搞进去example中的QMDKWidget之后无法很好的实现这个功能,结合理解取了下巧分享给大家
我这边是对QMDKWidget
类加了一个信号,使得视频在渲染新的一帧的时候触发,这样我就能得到现在播放到了什么位置,请注意void positionChange(int p);
,除和他相关之外所有代码和作者的例子中的内容相同 QMDKWidget.h
/*
* Copyright (c) 2020-2021 WangBin <wbsecg1 at gmail.com>
* MDK SDK with QOpenGLWindow example
*/
#pragma once
#include <QOpenGLWidget>
#include <memory>
namespace mdk {
class Player;
}
class QMDKWidget : public QOpenGLWidget
{
Q_OBJECT
public:
QMDKWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~QMDKWidget();
void setDecoders(const QStringList& dec);
void setMedia(const QString& url);
void play();
void pause();
void stop();
bool isPaused() const;
void seek(qint64 ms, bool accurate = false);
qint64 position() const;
void snapshot();
qint64 duration() const;
void prepreForPreview(); // load the media, and set parameters
signals:
void mouseMoved(int x, int y);
void doubleClicked();
void positionChange(int p);
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
void keyPressEvent(QKeyEvent *) override;
void mouseMoveEvent(QMouseEvent* ev) override;
void mouseDoubleClickEvent(QMouseEvent*) override;
private:
std::shared_ptr<mdk::Player> player_;
};
QMDKWidget.cpp
/*
* Copyright (c) 2020-2023 WangBin <wbsecg1 at gmail.com>
* MDK SDK with QOpenGLWidget example
*/
#include "QMDKWidget.h"
#include "mdk/Player.h"
#include <QDebug>
#include <QDir>
#include <QKeyEvent>
#include <QOpenGLContext>
#include <QStringList>
#include <QScreen>
#include <QGuiApplication>
#if __has_include(<QX11Info>)
#include <QX11Info>
#endif
#if defined(Q_OS_ANDROID)
# if __has_include(<QAndroidJniEnvironment>)
# include <QAndroidJniEnvironment>
# endif
# if __has_include(<QtCore/QJniEnvironment>)
# include <QtCore/QJniEnvironment>
# endif
#endif
#include <mutex>
using namespace MDK_NS;
static void InitEnv()
{
#ifdef QX11INFO_X11_H
SetGlobalOption("X11Display", QX11Info::display());
qDebug("X11 display: %p", QX11Info::display());
#elif (QT_FEATURE_xcb + 0 == 1) && (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
const auto x = qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
if (x) {
const auto xdisp = x->display();
SetGlobalOption("X11Display", xdisp);
qDebug("X11 display: %p", xdisp);
}
#endif
#ifdef QJNI_ENVIRONMENT_H
SetGlobalOption("JavaVM", QJniEnvironment::javaVM());
#endif
#ifdef QANDROIDJNIENVIRONMENT_H
SetGlobalOption("JavaVM", QAndroidJniEnvironment::javaVM());
#endif
}
QMDKWidget::QMDKWidget(QWidget *parent, Qt::WindowFlags f)
: QOpenGLWidget(parent, f)
, player_(std::make_shared<Player>())
{
static std::once_flag initFlag;
std::call_once(initFlag, InitEnv);
player_->setDecoders(MediaType::Video, {
#if (__APPLE__+0)
"VT",
"hap",
#elif (__ANDROID__+0)
"AMediaCodec:java=0:copy=0:surface=1:async=0",
#elif (_WIN32+0)
"MFT:d3d=11",
"CUDA",
"hap", // before any ffmpeg based decoders
"D3D11",
"DXVA",
#elif (__linux__+0)
"hap",
"VAAPI",
"VDPAU",
"CUDA",
#endif
"FFmpeg"});
player_->setRenderCallback([this](void*){
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
emit positionChange(player_.position());
});
}
QMDKWidget::~QMDKWidget()
{
makeCurrent();
player_->setVideoSurfaceSize(-1, -1); // cleanup gl renderer resources
}
void QMDKWidget::setDecoders(const QStringList &dec)
{
std::vector<std::string> v;
for (const auto& d : dec) {
v.push_back(d.toStdString());
}
player_->setDecoders(MediaType::Video, v);
}
void QMDKWidget::setMedia(const QString &url)
{
player_->setMedia(url.toUtf8().constData());
}
void QMDKWidget::play()
{
player_->set(State::Playing);
}
void QMDKWidget::pause()
{
player_->set(State::Paused);
}
void QMDKWidget::stop()
{
player_->set(State::Stopped);
}
bool QMDKWidget::isPaused() const
{
return player_->state() == State::Paused;
}
void QMDKWidget::seek(qint64 ms, bool accurate)
{
auto flags = SeekFlag::FromStart;
if (!accurate)
flags |= SeekFlag::KeyFrame;
player_->seek(ms, flags);
}
qint64 QMDKWidget::position() const
{
return player_->position();
}
void QMDKWidget::snapshot() {
Player::SnapshotRequest sr{};
player_->snapshot(&sr, [](Player::SnapshotRequest * _sr, double frameTime) {
const QString path = QDir::toNativeSeparators(
QString("%1/%2.jpg")
.arg(QCoreApplication::applicationDirPath())
.arg(frameTime));
return path.toStdString();
// Here's how to convert SnapshotRequest to QImage and save it to disk.
/*if (_sr) {
const QImage img = QImage(_sr->data, _sr->width, _sr->height,
QImage::Format_RGBA8888);
if (img.save(path)) {
qDebug() << "Snapshot saved:" << path;
} else {
qDebug() << "Failed to save:" << path;
}
} else {
qDebug() << "Snapshot failed.";
}
return std::string();*/
});
}
qint64 QMDKWidget::duration() const
{
return player_->mediaInfo().duration;
}
void QMDKWidget::prepreForPreview()
{
player_->setActiveTracks(MediaType::Audio, {});
player_->setActiveTracks(MediaType::Subtitle, {});
player_->setProperty("continue_at_end", "1"); // not required by the latest sdk
player_->setBufferRange(0);
player_->prepare();
}
void QMDKWidget::initializeGL()
{
GLRenderAPI ra;
ra.getProcAddress = +[](const char* name, void* opaque) {
Q_UNUSED(opaque); // ((QOpenGLContext*)opaque)->getProcAddress(name));
return (void*)QOpenGLContext::currentContext()->getProcAddress(name);
};
ra.opaque = context();
player_->setRenderAPI(&ra/*, this*/);
// context() may change(destroy old and create new) via setParent()
std::weak_ptr<mdk::Player> wp = player_;
connect(context(), &QOpenGLContext::aboutToBeDestroyed, [=]{
makeCurrent();
auto sp = wp.lock();
if (sp) // release and remove old gl resources with the same vo_opaque(nullptr), then new resource will be created in resizeGL/paintGL
sp->setVideoSurfaceSize(-1, -1/*, context()*/); // it's better to cleanup gl renderer resources as early as possible
else
Player::foreignGLContextDestroyed();
doneCurrent();
});
}
void QMDKWidget::resizeGL(int w, int h)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
auto s = screen();
qDebug("resizeGL>>>>>dpr: %f, logical dpi: (%f,%f), phy dpi: (%f,%f)", s->devicePixelRatio(), s->logicalDotsPerInchX(), s->logicalDotsPerInchY(), s->physicalDotsPerInchX(), s->physicalDotsPerInchY());
player_->setVideoSurfaceSize(w*devicePixelRatio(), h*devicePixelRatio()/*, this*/);
#else
player_->setVideoSurfaceSize(w, h/*, this*/);
#endif
}
void QMDKWidget::paintGL()
{
player_->renderVideo(/*this*/);
}
void QMDKWidget::keyPressEvent(QKeyEvent *e)
{
switch (e->key()) {
case Qt::Key_Space: {
if (player_->state() != State::Playing)
play();
else
pause();
}
break;
case Qt::Key_Right:
seek(position() + 10000);
break;
case Qt::Key_Left:
seek(position() - 10000);
break;
case Qt::Key_Q:
qApp->quit();
break;
case Qt::Key_C:
if (QKeySequence(e->modifiers() | e->key()) == QKeySequence::Copy) {
snapshot();
}
break;
case Qt::Key_F: {
if (isFullScreen())
showNormal();
else
showFullScreen();
}
break;
default:
break;
}
}
void QMDKWidget::mouseMoveEvent(QMouseEvent *ev)
{
Q_EMIT mouseMoved(ev->pos().x(), ev->pos().y());
ev->accept();
}
void QMDKWidget::mouseDoubleClickEvent(QMouseEvent *ev)
{
Q_EMIT doubleClicked();
ev->accept();
}
好的,现在你有了信号,每次他触发都代表视频播放到了新的位置,之后的事情就交给大家了