👼

GStreamerの概要とRustでの呼び出し方

2024/10/02に公開

GStreamerは、オープンソースのマルチメディアフレームワークであり、音声や動画のストリーミング、編集などを柔軟に行うことができます。噛み砕いて言うと「プログラマブルなFFmpeg」みたいな感じです。
8月に弊社で開催された開発合宿というイベントにて、AI対話ボットを作成するために利用しました。近いうちにAI対話ボットについてもご紹介できればと思っています。

本稿では、GStreamerの概要、Rustでの呼び出し方を紹介します。
環境はmacOS@12.7.4、GStreamer@1.24.6、Rust@1.79.0です。

インストール

以下の手順に従って、必要なバイナリをダウンロードしてインストールします。

  1. GStreamer ダウンロードページにアクセス
  2. runtime installerdevelopment installerの両方をダウンロード
  3. ダウンロードしたインストーラーを実行し、指示に従ってインストール

インストールが完了すると、GStreamerの実行に必要なライブラリやツールが/Library/Frameworks/GStreamer.framework配下に配置されます。
次のようにコマンドラインツールにパスを通しておきましょう。

export PATH="/Library/Frameworks/GStreamer.framework/Commands:$PATH"
export PKG_CONFIG_PATH="/Library/Frameworks/GStreamer.framework/Versions/Current/lib/pkgconfig"

動作確認

下記のコマンドを実行して動作を確認しましょう。「ポー」という音が聞こえればOKです。

gst-launch-1.0 audiotestsrc ! autoaudiosink

gst-launch-1.0は、GStreamerのコマンドラインツールの1つで、GStreamerパイプラインを簡単に構築して実行するためのユーティリティです。プログラミングせずに直接コマンドラインから試すことができます。プロダクションで使うためのものではありません。

GStreamerの基本要素

GStreamerは複数の要素(element)を組み合わせたパイプラインを構成することで、様々な処理を実現できます。プラグインベースのアーキテクチャを採用しており、プラグインを追加することで容易に機能を拡張可能です。

pipeline

ここでは、最も基本的なsrc要素とsink要素に加えて、filtermux、およびdemux要素について説明します。

プラグインの分類

GStreamerではプラグインをインストールする際に、複数のプラグインをまとめてインストールできるプラグインセットが提供されています。

  • gstreamer1.0-plugins-base: 基本セット (重要で典型的なコンポーネント一式)
  • gstreamer1.0-plugins-good: LGPLライセンスで配布されている良質なプラグインセット
  • gstreamer1.0-plugins-bad: 品質(コードレビュー、ドキュメンテー ション、テストコード、アクティブなメンテナ、需要)が基準に達していないプラグインセット
  • gstreamer1.0-plugins-ugly: 良質ながらライセンスで配布時に問題の起 こる可能性があるプラグインセット

source

src要素は、メディアデータを生成または取得して、パイプラインにデータを流し込む役割を持ちます。例えば、audiotestsrcは、サイン波などのテスト音声を生成するソース要素です。その他にも、ファイルからメディアデータを読み込むfilesrcや、ネットワークからデータを受け取るudpsrcなど、さまざまなソース要素が用意されています。

sink

sink要素は、パイプライン内のデータを消費し、再生・保存などを行います。例えば、autoaudiosinkは、システムのデフォルト音声出力デバイスで音声を再生するシンク要素です。保存する場合はfilesink、RTPで配信する場合はrtpsinkなどが利用できます。

filter

filter要素は、パイプライン内でデータを変換したり、加工したりする役割を持ちます。例えば、音声データにエフェクトをかける場合や、ビデオの解像度を変更する場合などに使用されます。

以下は、filter要素を使用して音声にエコーをかける例です。

gst-launch-1.0 audiotestsrc wave=ticks ! audioconvert ! audioecho delay=50000000 intensity=0.6 ! autoaudiosink

このコマンドでは、audiotestsrcが生成した音声データがaudioconvertで処理され、その後audioechoフィルターでエコーが追加されます。最終的に、autoaudiosinkで音声が再生されます。

mux(マルチプレクサ)

mux要素は、複数のストリーム(例えば音声と映像)を1つのストリームにまとめる役割を持ちます。たとえば、音声と映像を組み合わせて1つの動画ファイルとして保存する際に使用されます。

gst-launch-1.0 videomixer name=mix sink_1::xpos=0 sink_1::ypos=0
 ! autovideosink videotestsrc ! videoscale ! video/x-raw,width=100,height=100 ! mix.sink_1

demux(デマルチプレクサ)

demux要素は、mux要素とは逆に、1つのストリームを複数のストリームに分離する役割を持ちます。たとえば、動画ファイルを読み込んで、音声と映像を個別に処理する際に使用されます。

以下は、GStreamerを使用して音声付きの動画ファイルを再生するサンプルコマンドです。このコマンドでは、demux要素を使用して音声と映像を分離し、それぞれを再生します。

gst-launch-1.0 filesrc location=sample.mp4 ! qtdemux name=demux \
  demux.video_0 ! queue ! decodebin ! autovideosink \
  demux.audio_0 ! queue ! decodebin ! audioconvert ! autoaudiosink
  • filesrc:動画ファイル(sample.mp4)を読み込む
  • qtdemux:動画ファイルを解析し、音声と映像のストリームを分離
  • queue:映像と音声の処理を並列に行うために使用
  • decodebin:音声や映像をデコードして再生可能な形式に変換
  • autovideosink:映像を画面に表示
  • audioconvert:音声データを適切な形式に変換
  • autoaudiosink:音声をスピーカーから再生

このパイプラインを使用することで、動画ファイルの音声と映像を同期して再生できます。

Pad(パッド)

各要素のデータの入口/出口をシンクパッド/ソースパッドと言います。データの入力元の要素に対して自身の要素はデータを消費するシンクなので入り口はシンクパッド、出力先の要素に対してはデータを生成するソースなのでソースパッドという命名なのでしょう。

pad

ソース要素とシンク要素は末端の要素なのでパッドが1つしかありません。

edge

一方、デマルチプレクサ(demux)やマルチプレクサ(mux)などの要素は、複数のストリームを扱うため、複数のパッドを持つことができます。
muxは複数のストリームを1つのストリームにまとめるため、複数のシンクパッドを持ちます。

mux

これに対し、demuxは1つのストリームから複数の映像や音声ストリームを分離するため、複数のソースパッドを持ちます。

demux

Capabilities

Capabilitiesは、GStreamerの各要素が処理できるメディアデータのフォーマットや属性を定義したものです。パイプライン内で異なる要素を接続する際、互いのPadがどのようなデータを受け渡しできるかを判断するために重要です。互換性がある場合にのみ、要素同士が正常に接続され、データが正しく処理されます。

gst-inspect-1.0はGStreamerの要素の仕様を調べることができるコマンドです。audioechoの仕様を調べてみましょう。

gst-inspect-1.0 audioecho

Pad Templatesセクションには、要素が持つパッドの情報が記載されています。

Pad Templates:
  SINK template: 'sink'
    Availability: Always
    Capabilities:
      audio/x-raw
                 format: { (string)F32LE, (string)F64LE }
                   rate: [ 1, 2147483647 ]
               channels: [ 1, 2147483647 ]
                 layout: interleaved

  SRC template: 'src'
    Availability: Always
    Capabilities:
      audio/x-raw
                 format: { (string)F32LE, (string)F64LE }
                   rate: [ 1, 2147483647 ]
               channels: [ 1, 2147483647 ]
                 layout: interleaved

Element has no clocking capabilities.
Element has no URI handling capabilities.
  • Availability: PadにはAlways Pads、Sometimes Pads、Request Padsという種類がある
  • Capabilities:
    • audio/x-raw: パッドが生データのオーディオを扱うことを示す
    • format: オーディオデータのフォーマットで、F32LE(32-bit Little Endian Float)とF64LE(64-bit Little Endian Float)を許容
    • rate: サンプルレートで、範囲 [ 1, 2147483647 ] まで対応可能
    • channels: チャンネル数で、範囲 [ 1, 2147483647 ] まで対応可能
    • layout: データの配置方法を示す

もしもCapabilitiesに一致しないデータが入力されると次のようにエラーになります。

$ gst-launch-1.0 fdsrc ! audioecho
Setting pipeline to PAUSED ...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
foo
ERROR: from element /GstPipeline:pipeline0/GstFdSrc:fdsrc0: Internal data stream error.
Additional debug info:
../libs/gst/base/gstbasesrc.c(3127): gst_base_src_loop (): /GstPipeline:pipeline0/GstFdSrc:fdsrc0:
streaming stopped, reason not-negotiated (-4)
Execution ended after 0:00:04.009247573
Setting pipeline to NULL ...
Freeing pipeline ...

RustでGStreamerを使用する

GStreamerのRustバインディングであるgstreamer-rsというcrateを利用してRustから呼び出すことができます。

プロジェクトの作成

まず、Rustの環境を設定する必要があります。RustのパッケージマネージャーであるCargoを使用して、GStreamer関連のクレートを追加します。以下のコマンドでプロジェクトを作成し、gstreamerクレートを追加します。

cargo new myapp
cd myapp
cargo add gstreamer

音声再生の実装

Rustで音声を再生するGStreamerパイプラインを実装してみましょう。src/main.rs を次の通りに編集してください。

use gstreamer::prelude::*;

pub fn main() {
    // Initialize GStreamer
    gstreamer::init().unwrap();

    // Create the elements
    let source = gstreamer::ElementFactory::make("audiotestsrc")
        .name("source")
        .build()
        .expect("Could not create parser element");
    let sink = gstreamer::ElementFactory::make("autoaudiosink")
        .name("sink")
        .build()
        .expect("Could not create sink element");

    // Create the empty pipeline
    let pipeline = gstreamer::Pipeline::with_name("test-pipeline");

    // Build the pipeline
    pipeline.add_many([&source, &sink]).unwrap();
    gstreamer::Element::link_many(&[&source, &sink]).unwrap();

    // Start playing
    pipeline
        .set_state(gstreamer::State::Playing)
        .expect("Unable to set the pipeline to the `Playing` state");

    // Wait until error or EOS
    let bus = pipeline.bus().unwrap();
    for msg in bus.iter_timed(gstreamer::ClockTime::NONE) {
        use gstreamer::MessageView;

        match msg.view() {
            MessageView::Eos(..) => break,
            MessageView::Error(err) => {
                println!(
                    "Error from {:?}: {} ({:?})",
                    err.src().map(|s| s.path_string()),
                    err.error(),
                    err.debug()
                );
                break;
            }
            _ => (),
        }
    }

    // Shutdown pipeline
    pipeline
        .set_state(gstreamer::State::Null)
        .expect("Unable to set the pipeline to the `Null` state");
}

実行

GStreamerのバイナリ版の場合、次のように環境変数を設定しないとライブラリが認識されません。

export RUSTFLAGS="-C link-args=-Wl,-rpath,/Library/Frameworks/GStreamer.framework/Versions/1.0/lib"

cargo run を実行して、ポーっと音が出たらオッケーです。

参考

公式チュートリアル。

https://gstreamer.freedesktop.org/documentation/tutorials/index.html

公式チュートリアルをRustで書き直したものが公開されています。

https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/tree/main/tutorials/src/bin

Rust製プラグイン作成のチュートリアル。

https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/tree/main/tutorial

GStreamerの基本概念について。

https://qiita.com/alivelime/items/50d796c09baabb765625

プラグイン作成に関する日本語記事。サンプルコードが参考になりました。

https://qiita.com/uzuna/items/6c183253736e26598c45

多重化の説明が分かりやすかったです。

https://www.pixela.co.jp/products/pickup/dev/gstreamer/gst_1_overview_of_gstreamer.html

GitHubで編集を提案
株式会社MICIN

Discussion