Qtでlibmpvでの動画再生
mpvはWindows/MacOS/Linux対応のメディアプレイヤーである
SMPlayerなどのバックエンドとして使われるが単体でも再生が可能
そのmpvにはlibmpvというAPIが付属しておりアプリケーション内で再生や制御を行える
今回はこれを使用し動画再生アプリを作成する
まずは.proに以下を追加 既にKeyがある場合はそれに追加で
次にmain.cppのQApplicationの宣言の下辺りに以下を追加
あとはクラスに書いていくのだが
mpvで動画を再生するのには再生先(QWidget)が必要となる
なのでQWidgetを継承したカスタムクラスの"Player"を作成し、まとめて書いてしまうことにする
QtCreator(QtDesigner)で配置した場合はPlayerへの格上げを忘れないように
ヘッダには以下のincludeを追加
ソースのincludeの下に以下を
さあ準備は整った
実際にファイルを再生しよう
以降if (mpvHandle){ }は省略
一時停止をしたい時は
再生時間を取得したい時は
これで大体わかるだろうが
設定時はmpv_set_propertyで、取得時はmpv_get_propertyで行うことが出来る
ただこれだとリクエストを送らないと情報を受け取れないので、シークバーなどを実装する際に困ってしまう
そこで予め設定しておくことで値に変更があった場合に通知されるようになる
ソースの監視部分に
他にどのようなプロパティやオプションが設定できるのかはmpvのリファレンスを見てください
一部の情報はこのフォーマットが使用されていることがある
説明を書くのが面倒なので、リファレンスに内容が記述してある「demuxer-cache-state」を対象に
ネットワーク上の動画を開いた際にキャッシュしている範囲を取得する方法を実際のコードにて(取得位置はここのhandle_mpv_event()内)
・mpv_command_stringとの違い
ページ内ではコマンドの実行にmpv::qt::command()を使用している
これはqthelperによる高レベルの関数で、自動的にQStringListを解釈してくれるので便利なのだが1つ問題がある
${}を解釈してくれないのだ
これは例えば本来コマンドで「show-text ${width}」とすると画面上に動画の横サイズを表示してくれる
一々ソースを書き換えてテストせずともコマンド用のQLineEditを作っておくだけで数値などが取得できるありがたい機能なのだが、これが使用できない
おそらくエスケープだかの問題だと思うのだが仕方がないので本来の方で実行する必要がある
実行は以下
あわせてエラーテキストの出力方法も記述
・コマンドラインでの実行
mpvはコマンドラインからでも指定したQWidgetに表示させることができる
ただ、情報取得を標準出力から取り出す必要があるために面倒になる
ちなみにwidに他のアプリケーションのWindowIDを割り当てても、ちゃんとそのウィンドウに描画される
勿論想定していない動作なので問題が発生するかもしれず注意
SMPlayerなどのバックエンドとして使われるが単体でも再生が可能
そのmpvにはlibmpvというAPIが付属しておりアプリケーション内で再生や制御を行える
今回はこれを使用し動画再生アプリを作成する
まずは.proに以下を追加 既にKeyがある場合はそれに追加で
QT_CONFIG -= no-pkg-config
CONFIG += link_pkgconfig
PKGCONFIG += mpv x11
CONFIG += link_pkgconfig
PKGCONFIG += mpv x11
次にmain.cppのQApplicationの宣言の下辺りに以下を追加
setlocale(LC_NUMERIC, "C");
あとはクラスに書いていくのだが
mpvで動画を再生するのには再生先(QWidget)が必要となる
なのでQWidgetを継承したカスタムクラスの"Player"を作成し、まとめて書いてしまうことにする
QtCreator(QtDesigner)で配置した場合はPlayerへの格上げを忘れないように
ヘッダには以下のincludeを追加
#include <mpv/client.h>
#include <mpv/render.h>
#include <mpv/qthelper.hpp>
あわせてprivateに以下を追加#include <mpv/render.h>
#include <mpv/qthelper.hpp>
mpv_handle *mpvHandle = nullptr;
ソースのincludeの下に以下を
static void wakeup(void *ctx)
{
Player *player = (Player *)ctx;
emit player->mpv_events();
}
そして以下を追加しrun_mpv()はPlayerの初期化の際に読み込む(コメントアウト部分は後述){
Player *player = (Player *)ctx;
emit player->mpv_events();
}
void Player::run_mpv()
{
mpvHandle = mpv_create();
if (!mpvHandle)
throw "Failed Create mpv";
mpv_set_option_string(mpvHandle, "wid", QString::number(winId()).toUtf8().data());
//Option部分
//監視部分
mpv_request_log_messages(mpvHandle,"info");
connect(this, &Player::mpv_events, this, &Player::on_mpv_events,Qt::QueuedConnection);
mpv_set_wakeup_callback(mpvHandle, wakeup, this);
if (mpv_initialize(mpvHandle) < 0)
throw std::runtime_error("mpv failed to initialize");
}
監視したイベントの受け取り(後述){
mpvHandle = mpv_create();
if (!mpvHandle)
throw "Failed Create mpv";
mpv_set_option_string(mpvHandle, "wid", QString::number(winId()).toUtf8().data());
//Option部分
//監視部分
mpv_request_log_messages(mpvHandle,"info");
connect(this, &Player::mpv_events, this, &Player::on_mpv_events,Qt::QueuedConnection);
mpv_set_wakeup_callback(mpvHandle, wakeup, this);
if (mpv_initialize(mpvHandle) < 0)
throw std::runtime_error("mpv failed to initialize");
}
void Player::on_mpv_events()
{
while (mpvHandle)
{
mpv_event *event = mpv_wait_event(mpvHandle, 0);
if (event->event_id == MPV_EVENT_NONE)
break;
handle_mpv_event(event);
}
}
void Player::handle_mpv_event(mpv_event *event)
{
if (event->event_id == MPV_EVENT_PROPERTY_CHANGE)
{
//プロパティ変更時に値を取得
mpv_event_property *prop = (mpv_event_property *)event->data;
if (strcmp(prop->name, "duration") == 0)
double duration = (*(double *)prop->data);
else if (strcmp(prop->name, "time-pos") == 0)
double timepos = (*(double *)prop->data);
}
else if (event->event_id == MPV_EVENT_SHUTDOWN)
{
mpv_terminate_destroy(mpvHandle);
mpvHandle = NULL;
}
}
{
while (mpvHandle)
{
mpv_event *event = mpv_wait_event(mpvHandle, 0);
if (event->event_id == MPV_EVENT_NONE)
break;
handle_mpv_event(event);
}
}
void Player::handle_mpv_event(mpv_event *event)
{
if (event->event_id == MPV_EVENT_PROPERTY_CHANGE)
{
//プロパティ変更時に値を取得
mpv_event_property *prop = (mpv_event_property *)event->data;
if (strcmp(prop->name, "duration") == 0)
double duration = (*(double *)prop->data);
else if (strcmp(prop->name, "time-pos") == 0)
double timepos = (*(double *)prop->data);
}
else if (event->event_id == MPV_EVENT_SHUTDOWN)
{
mpv_terminate_destroy(mpvHandle);
mpvHandle = NULL;
}
}
さあ準備は整った
実際にファイルを再生しよう
if (mpvHandle)
{
QString path = "/home/user/TestVideo.mp4";
QStringList cmdList {"loadfile",path};
mpv::qt::command(mpvHandle,args);
int f = 0;
mpv_set_property(mpvHandle, "pause", MPV_FORMAT_FLAG, &f);
}
これでPlayerに動画が再生されるはずだ{
QString path = "/home/user/TestVideo.mp4";
QStringList cmdList {"loadfile",path};
mpv::qt::command(mpvHandle,args);
int f = 0;
mpv_set_property(mpvHandle, "pause", MPV_FORMAT_FLAG, &f);
}
以降if (mpvHandle){ }は省略
一時停止をしたい時は
int f = 1; //Playは0でPauseは1
mpv_set_property(mpvHandle, "pause", MPV_FORMAT_FLAG, &f);
mpv_set_property(mpvHandle, "pause", MPV_FORMAT_FLAG, &f);
再生時間を取得したい時は
double timepos;
mpv_get_property(mpvHandle,"time-pos",MPV_FORMAT_DOUBLE, &timepos);
mpv_get_property(mpvHandle,"time-pos",MPV_FORMAT_DOUBLE, &timepos);
これで大体わかるだろうが
設定時はmpv_set_propertyで、取得時はmpv_get_propertyで行うことが出来る
ただこれだとリクエストを送らないと情報を受け取れないので、シークバーなどを実装する際に困ってしまう
そこで予め設定しておくことで値に変更があった場合に通知されるようになる
ソースの監視部分に
mpv_observe_property(mpvHandle, 0, "time-pos", MPV_FORMAT_DOUBLE);
これを追加することで通知が来る他にどのようなプロパティやオプションが設定できるのかはmpvのリファレンスを見てください
追加メモ
・MPV_FORMAT_NODE一部の情報はこのフォーマットが使用されていることがある
説明を書くのが面倒なので、リファレンスに内容が記述してある「demuxer-cache-state」を対象に
ネットワーク上の動画を開いた際にキャッシュしている範囲を取得する方法を実際のコードにて(取得位置はここのhandle_mpv_event()内)
mpv_node node = (*(mpv_node *)prop->data);
for(int i = 0; i < node.u.list->num; i++)
{
if (strcmp(node.u.list->keys[i], "seekable-ranges") == 0) {
mpv_node rangeNode = node.u.list->values[i];
for(int j = 0; j < rangeNode.u.list->num; j++)
{
mpv_node cacheNode = rangeNode.u.list->values[j];
int cacheNum = j;
double cacheSecA = cacheNode.u.list->values[0].u.double_;
double cacheSecB = cacheNode.u.list->values[1].u.double_; }
}
}
for(int i = 0; i < node.u.list->num; i++)
{
if (strcmp(node.u.list->keys[i], "seekable-ranges") == 0) {
mpv_node rangeNode = node.u.list->values[i];
for(int j = 0; j < rangeNode.u.list->num; j++)
{
mpv_node cacheNode = rangeNode.u.list->values[j];
int cacheNum = j;
double cacheSecA = cacheNode.u.list->values[0].u.double_;
double cacheSecB = cacheNode.u.list->values[1].u.double_; }
}
}
・mpv_command_stringとの違い
ページ内ではコマンドの実行にmpv::qt::command()を使用している
これはqthelperによる高レベルの関数で、自動的にQStringListを解釈してくれるので便利なのだが1つ問題がある
${}を解釈してくれないのだ
これは例えば本来コマンドで「show-text ${width}」とすると画面上に動画の横サイズを表示してくれる
一々ソースを書き換えてテストせずともコマンド用のQLineEditを作っておくだけで数値などが取得できるありがたい機能なのだが、これが使用できない
おそらくエスケープだかの問題だと思うのだが仕方がないので本来の方で実行する必要がある
実行は以下
あわせてエラーテキストの出力方法も記述
QByteArray ba = text.toUtf8();
int res = mpv_command_string(mpvHandle,ba);
if (res < 0)
qDebug() << "CommandError" << mpv_error_string(res);
int res = mpv_command_string(mpvHandle,ba);
if (res < 0)
qDebug() << "CommandError" << mpv_error_string(res);
・コマンドラインでの実行
mpvはコマンドラインからでも指定したQWidgetに表示させることができる
ただ、情報取得を標準出力から取り出す必要があるために面倒になる
$ mpv -wid=0x5400007 '/home/user/TestVideo.mp4'
ちなみにwidに他のアプリケーションのWindowIDを割り当てても、ちゃんとそのウィンドウに描画される
勿論想定していない動作なので問題が発生するかもしれず注意
コメント
コメントを投稿