QTAV踩过的坑和MDK-SDK的一些小东西

本文讲述了作者在使用qtav在内网环境中遇到的编译问题,包括mingw与msvc的区别、lnk2019错误的解决,以及视频播放的黑屏问题。最后推荐了MKD-SDK作为qtav的替代库,提供更稳定的视频播放体验。
摘要由CSDN通过智能技术生成

最近需要在qt界面内放一个widget用以播放视频,被推荐了qtav,踩了很多坑

  • qtav
  1. 首先说明,我是需要内网编译,内网开发,然后打包程序,所以直接注入到qt根目录的方式我是不考虑的,以下问题大多建立在这个基础之上
  2. 网上给的编译教程基本翻烂了,感觉都是一样的,这边推荐是可以试一下官网给的现成的lib库,mscv2015的,如果可用那么恭喜你,逃过一劫,地址我放到下面了
    QtAV1.12.0-VS2015x86.7z/download
    如果你很不幸和我一样需要自己动手编译,windows平台上我可以提供给诸位的教训如下:
    1. 下请务必确定自己可以使用mscv去按照教程编译而不是mingw,mingw编译出的.a非常难用,我甚至一度想过把他强转成lib
    2. 你可能会遇到mingw编译通过(可能会提示缺一个Q什么的,自己去补个头文件就好)但是mscv提示lnk2019这种无法理解的问题,请你全局搜索qlistlink,这个库因为时间的问题在一个地方使用了几次这个类型的变量,而如果你的qt版本稍稍新一些,他会导致你出现lnk2019的问题,自己去全部替换成qlist就行,不会影响你的正常使用的
    3. 这里不是编译的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();
}

好的,现在你有了信号,每次他触发都代表视频播放到了新的位置,之后的事情就交给大家了

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值