Qtで動画を書き出したい


この記事は Qt Advent Calender 2015 の13日の記事です。

WindowsでQtで動画を書き出す奮闘気です。



動画事情

昨今Web上で動画が見れるのが当たり前になってきました。
そこでプログラムから動画を書き出したいと思います。
使用人口が増えるとライブラリや関連ツールも増えていくものです。
今、この時代なら、動画書き出しできるライブラリが・・・・・・。
あれっ、ほとんど無い!です。

世の中に出回っている動画ツールおよび使用しているライブラリを、結構見て回ったのですが、
ほとんどがFFMPEGかlibav(これはFFMPEGの派生)でした。
libVLC?いやそれもFFMPEGですね。
動画のライブラリの選択肢は、ほぼFFMPEGしかなくなってしまったようです。

さて、MPEG(.mp4とか)ではなくて、AVI(.avi)ならあるかもしれません。
動画出力といえば、非圧縮avi出力は絶対に行いたいところです。
.aviフォーマットは、バージョンが大きく2つあり、新しいほうはAVI2.0と呼ばれているようです。
古いほうでは2GBまでしか書き出せませんが、新しいほうはいくらでも書き出せます。
2GBとか非圧縮動画なら一瞬で使い切ってしまうので、AVI2.0を書き出したいところです。
こちらも調査しました。
結果、ほとんど無い!です。

AVI2.0はWindowsであればdirectshowで出力できるようです。
また、以下のようなライブラリを発見し、使えそうなことを確認しましたが、こちらは現時点でライセンスが付いていないので使用できません。

・TechSmith - AVI20

結局FFMPEGに行き着きます。


でもQtならできる?


Qtでは動画出力できないのでしょうか?こちらも調査しました。
Qtは様々なコンポーネントにリンクしまくっていて、内部にChromium先生(chromium先生はffmpegユーザーですね)を抱えています。
さらにQtMulitimediaなるライブラリを備えており、内部に
qvideoencodersettingscontrol.h
などといったヘッダが見えます。なんか出来そうな気がします。

そこで、ソースをずっと眺めていたんですが、どうやらQtの動画/音声部分は
QtMultimediaのプラグインとして実装されており、
プラグインといっても動的に増やしたりはできなさそう(?)です。
そしてそのプラグインの定義らしきファイルを発見しました
qt/qtmultimedia/src/plugins/plugins.pro


######################################################################
#
# Qt Multimedia
#
######################################################################

TEMPLATE = subdirs

SUBDIRS += m3u

android {
   SUBDIRS += android
}

blackberry {
    SUBDIRS += blackberry
}

qnx {
    SUBDIRS += qnx
}

win32 {
    SUBDIRS += audiocapture
}

win32 {
    config_directshow: SUBDIRS += directshow
    config_wmf: SUBDIRS += wmf
}

unix:!mac {
    config_gstreamer {
       SUBDIRS += gstreamer
    } else {
        SUBDIRS += audiocapture
    }

    # v4l is turned off because it is not supported in Qt 5
    # !maemo*:SUBDIRS += v4l

    config_pulseaudio {
        SUBDIRS += pulseaudio
    }
}

mac:!simulator {
    SUBDIRS += audiocapture

    !ios {
        SUBDIRS += qt7
        config_avfoundation: SUBDIRS += avfoundation
    }
}


どうやらQtはWindowsではdirectshowかWMFを使っているようです。
で、plugins以下のソースを見てみましたが、動画をエンコードしているような箇所は見当たりませんでした。
というわけでQt(Windows)では動画は書き出せません。

では他のプラットフォームはどうでしょうか。H.264やmp4など引っかかりそうな単語で全文検索してみました。
結果、
qt/qtmultimedia/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.cppより一部抜粋

QGstreamerVideoEncode::QGstreamerVideoEncode(QGstreamerCaptureSession *session)
    :QVideoEncoderSettingsControl(session), m_session(session)
{
    QList<QByteArray> codecCandidates;
    codecCandidates << "video/h264" << "video/xvid" << "video/mpeg4" << "video/mpeg1" << "video/mpeg2" << "video/theora";

    m_elementNames["video/h264"] = "x264enc";
    m_elementNames["video/xvid"] = "xvidenc";
    m_elementNames["video/mpeg4"] = "ffenc_mpeg4";
    m_elementNames["video/mpeg1"] = "ffenc_mpeg1video";
    m_elementNames["video/mpeg2"] = "ffenc_mpeg2video";
    m_elementNames["video/theora"] = "theoraenc";


なにやら発見しました。linux版ではgstreamerというLGPLのライブラリで動画がエンコードできるようです。
非圧縮フォーマットやAVI出力が見当たりませんが、世の中には、可逆圧縮mp4 という特殊フォーマットが存在します。
これならいけるかもしれません。下のほうを見てみます
qt/qtmultimedia/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.cppの
GstElement *QGstreamerVideoEncode::createEncoder()より一部抜粋

if (codec == QLatin1String("video/h264")) {
    //constant quantizer mode
    g_object_set(G_OBJECT(encoderElement), "pass", 4, NULL);
    int qualityTable[] = {
        50, //VeryLow
        35, //Low
        21, //Normal
        15, //High
        8 //VeryHigh
    };
    g_object_set(G_OBJECT(encoderElement), "quantizer", qualityTable[qualityValue], NULL);
} else if (codec == QLatin1String("video/xvid")) {

ここまで決めうちされていては出来そうにありません。
一応g_object_setというのでセットしてるようだからg_object_getで取り出して書き込み直前に上書きすれば
もしかしたら外から設定できるかもしれませんが・・・。


Chromium先生

QtWebEngineにはChromium先生が搭載されています。もしかして話題のHTML5を使えば
QtWebEngineを使って表示したページ上で動画をエンコードできたりしないでしょうか?調べました

・Creating .webm video from getUserMedia()

ありました。webmならいけるかもしれないようです。でもwebpはともかくwebmで可逆っていけるんでしょうか?
ちょっと調べたけど良く分かりませんでした。



qtffmpegwrapper


大体Qtで動画出力できないのか?みたいなスレッドの書き込みを見るとこれが載っています。
このライブラリ、まぁどう考えてもFFMPEG使って動画出すだけのライブラリですが、こちらも調査しました。

非常に古いライブラリで2012年あたりで開発者のメンテが止まってるようです。
最初に見つけたものはQt5では全く動きそうに無い感じでしたが、
こちらにQt5でコンパイルできるようにした、と書かれたフォークを発見しました。

・bergstr11/qtffmpegwrapper

しかし、これ、Windows(VC++)でビルド通らないし、ヘッダで
#include "ffmpeg.h"

とかしてて、おいおい勘弁してくれ、という感じだったので、さらにフォークしてWinで動くようにしてヘッダも整理しました

・uimac/qtffmpegwrapper

こちらです。これでQImage(RGB32のみ有効)を投げ込んだら、拡張子に応じてFFMPEGを使って適当な動画を出力することができます!
使い方としては、次のような感じです。

// 抜粋して整形したものです。擬似コードと思ってください

#include <QVideoEncoder.h>

void exportAVI() {
    // エンコーダーのインスタンスを作ります
    QVideoEncoder encoder;

    // ファイルを作ります。
    // gopはフレーム間の最大の間隔とのことです。非圧縮なら1ですかね。とりあえず20で試しました。
    // ビットレートは大きな値を入れとけば良いのでは。この辺詳しくないので調べてください。
    encoder.createFile("out.avi", width, height, bitrate, gop, fps);

    // maxframeは書き出しフレーム数です.
    const unsigned maxframe = 500;
    for (unsigned i = 0; i < maxframe; ++i)
    {
        // どっかから書き出しイメージを持ってきます.
        // 書き出しイメージは左下から右上にBMPのようにピクセルが配置されている、QImage::Format_RGB32のQImageです
        QImage &frame = getFrameImage(i * 1000.0 / fps)
        // 1フレーム出力します
        encoder.encodeImage(frame);
    }
    // 出力終了.
    encoder.close();
}



非圧縮はまだ試していませんがFFMPEGだからちょっといじれば出来ることでしょう。
今週中別のカレンダー用に動画出さないといけないので近日検証&実装予定です。

さて、これはQtと親和性があるライブラリというだけで、結局FFMPEGを使っているので、Qt使う意味あるのか?
と言われたら返す言葉もありませんが、以上調査結果になります。




明日の Qt Advent Calender 2015 は ・・・ あれっ。誰も・・いない!




Comments