最近需要在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_HSetGlobalOption("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_HSetGlobalOption("JavaVM", QJniEnvironment::javaVM());
#endif
#ifdef QANDROIDJNIENVIRONMENT_HSetGlobalOption("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 sdkplayer_->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/paintGLsp->setVideoSurfaceSize(-1, -1/*, context()*/); // it's better to cleanup gl renderer resources as early as possibleelsePlayer::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*/);
#elseplayer_->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();elsepause();}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();elseshowFullScreen();}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();
}
好的,现在你有了信号,每次他触发都代表视频播放到了新的位置,之后的事情就交给大家了