MPV进阶使用:LIBMPV

发布时间 2023-11-28 09:45:04作者: eiSouthBoy

一、介绍

mpv不仅提供了IPC的使用方式,还提供了函数库,方便将mpv嵌入其他程序。

EMBEDDING INTO OTHER PROGRAMS (LIBMPV)

mpv can be embedded into other programs as video/audio playback backend. The recommended way to do so is using libmpv. See libmpv/client.h in the mpv source code repository. This provides a C API. Bindings for other languages might be available (see wiki).

释义:mpv能被嵌入到其他程序,作为视频/音频播放后台。推荐方法是使用 libmpv。在mpv源码仓库查看libmpv/client.h。在此提供了C API。其他语言的绑定可能可用(查看 wiki)

二、使用

对于libmpv提供了qt使用案例

以下内容为验证qt案列:

qtexample.h

#ifndef QTEXAMPLE_H
#define QTEXAMPLE_H

/*
 * 注1:程序正常运行需要将动态链接库mpv-1.dll拷贝至输出目录的dubug和release
 * 注2:程序界面UI全部由代码实现
*/

#include <QMainWindow>
#include <client.h>

class QTextEdit;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    /* 自定义槽函数 */
    void on_file_open();  // 打开文件
    void on_new_window(); // 新建窗口
    void on_mpv_events(); // 这个槽函数由 wakeup()调用(通过mpv_events信号)

signals:
    void mpv_events();

private:
    QWidget *mpv_container;
    mpv_handle *mpv;
    QTextEdit *log;

    void create_mvpPlayer(); // 创建mvpPlayer
    void handle_mpv_event(mpv_event *event); // 处理mpv事件
    void append_log(const QString &text); // 附加log到plainTextEdit
};

#endif // QTEXAMPLE_H

#include <clocale>
#include <sstream>
#include <stdexcept>

#include <QtGlobal>
#include <QFileDialog>
#include <QStatusBar>
#include <QMenuBar>
#include <QMenu>
#include <QGridLayout>
#include <QApplication>
#include <QTextEdit>
#include <QJsonDocument>

#include <qthelper.hpp>
#include "qtexample.h"

/* 唤醒函数 */
static void wakeup(void *ctx)
{
    // 此回调可从任何mpv线程调用(但也可以从调用mpv API的线程递归地返回)
    // 只是需要通知要唤醒的Qt GUI线程(以便它可以使用mpv_wait_event()),并尽快返回
    MainWindow *mainwindow = (MainWindow *)ctx;
    emit mainwindow->mpv_events();
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    // 设置主窗口标题名称和最小尺寸
    setWindowTitle("Qt embedding demo");
    setMinimumSize(640, 480);

    // 添加菜单栏
    QMenu *menu = menuBar()->addMenu(tr("&File"));

    // 在菜单栏添加open file Action
    QAction *on_open = new QAction(tr("&Open"), this);
    on_open->setShortcuts(QKeySequence::Open);
    on_open->setStatusTip(tr("Open a file"));
    connect(on_open, &QAction::triggered, this, &MainWindow::on_file_open);
    menu->addAction(on_open);
    // 在菜单栏添加new window Action
    QAction *on_new = new QAction(tr("&New window"), this);
    connect(on_new, &QAction::triggered, this, &MainWindow::on_new_window);
    menu->addAction(on_new);

    // 返回主窗口的状态栏(不存在则创建一个空的状态栏)
    statusBar();

    // 在主窗口下创建mpv log窗口
    QMainWindow *log_window = new QMainWindow(this);
    log = new QTextEdit(log_window);
    log->setReadOnly(true);
    log_window->setCentralWidget(log);
    log_window->setWindowTitle("mpv log window");
    log_window->setMinimumSize(500, 50);
    log_window->show();

    // 创建mvpPlayer
    create_mvpPlayer();
}

// 处理mpv事件
void MainWindow::handle_mpv_event(mpv_event *event)
{
    switch (event->event_id)
    {
        case MPV_EVENT_PROPERTY_CHANGE:
        {
            mpv_event_property *prop = (mpv_event_property *)event->data;
            if (strcmp(prop->name, "time-pos") == 0) // 状态栏显示当前文件的播放时间(单位:秒)
            {

                if (prop->format == MPV_FORMAT_DOUBLE) // 属性数据的值格式是:double
                {
                    double time = *(double *)prop->data;
                    std::stringstream ss;
                    ss << "At: " << time;
                    statusBar()->showMessage(QString::fromStdString(ss.str()));
                }
                else if (prop->format == MPV_FORMAT_NONE) // 属性数据的值格式未知 (可能意味着播放已停止)
                {
                    statusBar()->showMessage("");
                }
            }
            else if (strcmp(prop->name, "chapter-list") == 0 ||
                       strcmp(prop->name, "track-list") == 0)
            {
                // 出于演示目的,将属性作为JSON转储
                if (prop->format == MPV_FORMAT_NODE)
                {
                    QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data);
                    // 使用JSON支持,可轻松打印mpv_node内容。
                    QJsonDocument d = QJsonDocument::fromVariant(v);
                    append_log("Change property " + QString(prop->name) + ":\n");
                    append_log(d.toJson().data());
                }
            }
            break;
        }
        case MPV_EVENT_VIDEO_RECONFIG:
        {
            // 检索新的视频大小
            int64_t w, h;
            if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
                mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 &&
                w > 0 && h > 0)
            {
                // 请注意,MPV_EVENT_VIDEO_RECONFIG事件不一定表示要调整大小,如果尺寸确实发生了变化,
                // 您应该检查一下视频。如果视频不合适,mpv本身会将视频缩放,放大到container大小
                std::stringstream ss;
                ss << "Reconfig: " << w << " " << h;
                statusBar()->showMessage(QString::fromStdString(ss.str()));
            }
            break;
        }
        case MPV_EVENT_LOG_MESSAGE:
        {
            struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
            std::stringstream ss;
            ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
            append_log(QString::fromStdString(ss.str()));
            break;
        }
        case MPV_EVENT_SHUTDOWN:
        {
            mpv_terminate_destroy(mpv);
            mpv = NULL;
            break;
        }
        default:
            // 忽视不感兴趣的或未知的事件
            break;
    }
}

// 这个槽函数由 wakeup()调用(通过mpv_events信号)
void MainWindow::on_mpv_events()
{
    // 处理所有事件,直到事件队列为空
    while (mpv)
    {
        mpv_event *event = mpv_wait_event(mpv, 0);
        if (event->event_id == MPV_EVENT_NONE)
            break;
        handle_mpv_event(event);
    }
}

// 打开文件
void MainWindow::on_file_open()
{
    QString filename = QFileDialog::getOpenFileName(this, "Open file");
    if (mpv)
    {
        const QByteArray c_filename = filename.toUtf8();
        const char *args[] = {"loadfile", c_filename.data(), NULL};
        // 与mpv_command相同,但异步运行命令(调用mpv渲染API线程)
        mpv_command_async(mpv, 0, args);
    }
}

// 新建窗口
void MainWindow::on_new_window()
{
    // 新建个MainWindow实例
    (new MainWindow())->show();
}

// 附加log
void MainWindow::append_log(const QString &text)
{
    QTextCursor cursor = log->textCursor();
    cursor.movePosition(QTextCursor::End);
    cursor.insertText(text);
    log->setTextCursor(cursor);
}

// --stream-record   https://mpv.io/manual/master/
// 创建mvpPlayer
void MainWindow::create_mvpPlayer()
{
    // 创建mpv实例
    mpv = mpv_create();
    if (!mpv)
        throw std::runtime_error("can't create mpv instance");

    // 创建一个视频子窗口(mpv_container)来播放视频
    mpv_container = new QWidget(this);
    setCentralWidget(mpv_container);
    mpv_container->setAttribute(Qt::WA_DontCreateNativeAncestors);
    mpv_container->setAttribute(Qt::WA_NativeWindow);
    // 然后将窗口ID传递给mpv wid选项
    int64_t wid = mpv_container->winId();
    mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);

    // 启用默认绑定,因为我们很懒。 通常,播放者使用mpv作为后端将实现其自己的键绑定
    mpv_set_option_string(mpv, "input-default-bindings", "yes");

    // 启用键盘输入在X11 Window上
    mpv_set_option_string(mpv, "input-vo-keyboard", "yes");

    // 让我们通过 MPV_EVENT_PROPERTY_CHANGE 接收属性更改事件,如果这属性更改了
    mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
    mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
    mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);

    // 请求级别为"info"或更高级别的日志消息。它们以 MPV_EVENT_LOG_MESSAGE 的形式接收
    mpv_request_log_messages(mpv, "info");

    // 从这里开始,将调用唤醒功能。 回调可以来自任何线程,因此我们使用 QueuedConnection 机制以线程安全的方式中继唤醒
    connect(this, &MainWindow::mpv_events, this, &MainWindow::on_mpv_events,
            Qt::QueuedConnection);
    mpv_set_wakeup_callback(mpv, wakeup, this);

    // 判断mpv实例是否成功初始化
    if (mpv_initialize(mpv) < 0)
        throw std::runtime_error("mpv failed to initialize");
}

MainWindow::~MainWindow()
{
    if (mpv)
        mpv_terminate_destroy(mpv);
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // Qt在QApplication构造函数中设置了locale,但是libmpv需要将LC_NUMERIC类别设置为“ C”,因此将其改回。
    std::setlocale(LC_NUMERIC, "C");

    MainWindow w;
    w.show();

    return a.exec();
}

输出结果:

三、注意事项

  • libmpv提供了库文件:libmpv.dll.a 和 mpv-1.dll,链接时加入lib.dll.a文件,运行时mpv-1.dll放入exe同文件夹