オーディオとビデオを支える技術(7) -FFMPEG Qtビデオプレイヤーに映像を表示する。

前回、FFMPEGを使用してビデオをデコードする方法を学びました。
今回は、ビデオプレイヤーに映像を載せようと思います。


今回の最終目標。



ビデオを再生する必要があるので、Qt GUIプロジェクトを使用します。
プロジェクトを開くときは、Qtwidgetアプリを選びましょう。

f:id:nakadasanda1:20200702212131p:plain

FFMPEGについては、ここで紹介しないので、前回の記事を参照してください。



メインスレッドが重たい処理を行うと、インターフェースが動作しなくなるため、メインスレッドで、時間のかかる操作を実行できません。
ビデオの読み取りと、ビデオコードは、非常に時間のかかる操作であるため、これを行うには、別のスレッドを開くひつようがあります。


Qtでのスレッドはの使用法は、QThread を継承するクラスを作成し、run関数をオーバーロードして、時間のかかる操作を全部run関数に入れることです。

class VideoPlayer : public QThread
{
    Q_OBJECT
public:
    explicit VideoPlayer();
    ~VideoPlayer();

protected:
    void run();
};

Qtcreaterは、Q_OBJECTを作りだせないというバグがあるので、個別にQmakeしてあげる必要があります。


ここでrun関数は、ビデオを読み取ってビデオをデコードするという処理を行います。
読み取りと、デコードの部分は、前回作ったので、前回のmain関数をコピーして貼り付けます。


変更点は、次の通りです。
Qtで、コントロールする必要があるためデコードされたYUVデータをRGB32形式に変換します。

    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
                                     pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
                                     AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);

    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32,pCodecCtx->width, pCodecCtx->height);

    out_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *)pFrameRGB,out_buffer,AV_PIX_FMT_RGB32,pCodecCtx->width, pCodecCtx->height);

......
.....
....
    sws_scale(img_convert_ctx,
                          (uint8_t const *const *)pFrame->data,
                          pFrame->linesize,0,pCodecCtx->height,pFrameRGB->data,
                          pFrameRGB->linesize);


変換されたRGB32データをQimageオブジェクトに保存します。

    QImage tempImg = QImage((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);


次に作成した、Qimageをメインスレッドに渡してあげる必要があります。
Qtでは、一般的に使われるコールバック関数の代わりに、シグナルと、スロットというものを使用します。

シグナルとは、

シグナルとは、何かが発生したことをほかのオブジェクトに伝えるためのc++関数です。

例:
- ボタンがクリックされた際に発生するシグナル。
- ボタンのON/OFFが切り替わった際に発生するシグナル。
- 内容が変わったときに発生するシグナル。
C言語のsingnal(2)とは、まったく関係ないものです。

スロットとは 

スロットは、シグナルに反応して実行されるc++の関数です。

例:
- Widgetを閉じるスロット
- 画像の切り替えをするスロット

videoplayer.hでシグナルを宣言します。

signals:
    void sig_GetOneFrame(QImage img );


シグナルを送信します

    QImage tempImg = QImage((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
    QImage image = tempImg.copy();
    emit sig_GetOneFrame(image);


メインスレッドでシグナルとスレッドを接続します。

    mPlayer = new VideoPlayer();
    connect(mPlayer,SIGNAL(sig_GetOneFrame(QImage)),this,SLOT(slotGetOneFrame(QImage)));


信号処理関数は、こんな感じです。

void MainWindow::slotGetOneFrame(QImage img)
{
    mImage = img;
    update();
}

画像を受けとったらGUIの更新を行います。

メインスレッドでQpainterを使うことで、画像を描写します。

void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setBrush(Qt::black);
    painter.drawRect(0,0,this->width(),this->height());

    if(mImage.size().width() <= 0) return;

    QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio);

    painter.drawImage(0,0,img);
}

最後に実行します。

"Qtcreaterは、Q_OBJECTを作りだせないというバグがあるので、個別にQmakeしてあげる必要があります。"とのことなので、Qmakeの実行について書いておきます。
プロジェクトを右クリックするとQmakeの実行があるので実行します。
f:id:nakadasanda1:20200703104218p:plain

その後は、普通に実行ボタンを押すと実行できます。

最終的なソースコードです。

github.com

最後に

まだまだビデオプレイヤーに遠いですが段階的に改善します。

シリーズ一覧 nakadasanda.hatenablog.jp