試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

GStreamer 0.10で音声を複数の出力先へ分岐させる(音を鳴らすと同時にファイルへ書き出すなど)

GStreamerの仕組みの中で、ある要素(プラグイン)から出力された音声データを

  • サウンドカードへ出力する(音を鳴らす)
  • ファイルに書き出す

といった複数の出力先へ渡したいということがある。
こうしたときに役に立つのがteeというプラグインで、コマンドのteeのように、受け取ったデータをそのまま2つに分岐させる機能を持つ。これを用いると上のような要求は簡単に満たせる。
ただし、teeと同時にqueueというプラグインの要素を2つ用意し、teeからそれぞれのqueueへリンク(接続)する形にしないと
http://gstreamer.freedesktop.org/wiki/FAQ#MypipelinewithmultiplesinksneverreachesthePAUSEDstate.2CwhatamIdoingwrong.3F
に書かれているようにPAUSEDの状態にできないとのことなので、PAUSEDの状態にするプログラムを書く場合には注意する。
処理の流れを図にすると

 src > [途中の処理を行う要素...] > tee > [途中の処理を行う要素...] > sink1 (出力先1)
(入力)                           +--> [途中の処理を行う要素...] > sink2 (出力先2)

のようにすることになる。teeからの接続はteeプラグインの要素のメンバ関数link()をそれぞれのqueueプラグインの要素に対して呼び出す形で行う。

tee = gst.element_factory_make ('tee', 'tee')
queue1 = gst.element_factory_make ('queue', 'queue1')
queue2 = gst.element_factory_make ('queue', 'queue2')
...
[teeの1つ前の要素].link (tee)
tee.link (queue1)
tee.link (queue2)
queue1.link ( ... )
queue2.link ( ... )

下は例で、FLACファイルの場所と出力WAVEファイルの場所を引数に指定して実行すると、それを再生しながらデコード結果をWAVEファイルに保存する。「GStreamer 0.10でwavparseプラグインをプログラム中で用いる上でのメモ(前半)」「GStreamer 0.10でwavparseプラグインをプログラム中で用いる上でのメモ(後半)」と同様、バージョン2.6系以上のPythonを対象とする。
[任意]ファイル名: gstteetest.py

#! /usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function  # 2.6系以上でprint()関数を用いる
import sys


class GstTeeTest:
  """
  GStreamerのteeプラグインを用いたテスト
  FLACファイルの再生とWAVEファイル書き出しを同時に行うテスト
  """
  __retval = 0
  def __on_bus_message_error (self, bus, message):
    """
    GStreamerのバスにエラーメッセージが流れた
    """
    # エラーの解析処理
    (gerror, debug) = message.parse_error ()
    print (debug, file=sys.stderr)
    self.__retval = 1
    # メインループを抜ける
    self.__loop.quit ()
  def main (self):
    """
    メイン処理
    """
    # コマンド行引数を入力ファイルとする
    if len (sys.argv) < 3:
      print ('usage: {0} [FLAC file] [WAVE file]'.format (sys.argv[0]))
      return 0

    # モジュールの読み込み
    try:
      import gst
    except:
      print ('Error: GStreamer Python binding is not installed.', file=sys.stderr)
      return 1
    try:
      import glib
    except:
      print ('Error: GLib Python binding is not installed.', file=sys.stderr)
      return 1

    # メインループ
    self.__loop = glib.MainLoop ()
    # ファイルの場所をGObjectプロパティlocationとして
    # その内容を開いて送り出すプラグイン
    filesrc = gst.element_factory_make ('filesrc', 'src')
    filesrc.props.location = sys.argv[1]
    # FLACヘッダを解析(中身はFLACエンコードされている状態)
    flacparse = gst.element_factory_make ('flacparse', 'parse')
    # FLACのデコード(ここで生の音声データになる)
    flacdec = gst.element_factory_make ('flacdec', 'decode')
    # 分岐
    # 注意:必ず分岐した次の要素はqueueプラグインになるようにする
    #      そうしないと不具合が起こることがある
    #      http://gstreamer.freedesktop.org/wiki/FAQ#MypipelinewithmultiplesinksneverreachesthePAUSEDstate.2CwhatamIdoingwrong.3F
    tee = gst.element_factory_make ('tee', 'tee')
    queue_audio = gst.element_factory_make ('queue', 'queue_audio')
    queue_file = gst.element_factory_make ('queue', 'queue_file')
    # 変換(これをしないと次のsinkにつながらない場合がある)
    audioconvert = gst.element_factory_make ('audioconvert', 'convert')
    audioresample = gst.element_factory_make ('audioresample', 'resample')
    # 自動でオーディオ出力先を探して渡してくれる
    autoaudiosink = gst.element_factory_make ('autoaudiosink', 'audiosink')
    # WAVEファイルにエンコード
    wavenc = gst.element_factory_make ('wavenc', 'encode')
    # ファイルへの書き出し
    filesink = gst.element_factory_make ('filesink', 'filesink')
    filesink.props.location = sys.argv[2]
    # 処理の流れを通すパイプライン
    pl = gst.Pipeline ()
    # 内部メッセージを処理するバス
    bus = pl.get_bus ()
    # 下の2つのGObjectシグナルを接続するために必要
    bus.add_signal_watch ()
    # ストリーム終端になったらメインループを抜けるようにする
    bus.connect ('message::eos', lambda bus, message: self.__loop.quit ())
    # エラーメッセージが出たら表示/終了のためのハンドラが呼ばれるようにする
    bus.connect ('message::error', self.__on_bus_message_error)
    # パイプラインに要素を追加(順番は任意)
    pl.add (filesrc, flacparse, flacdec,
            tee,
            queue_audio, audioconvert, audioresample, autoaudiosink,
            queue_file, wavenc, filesink)

    # 要素を接続していく
    # Pythonではgst.LinkErrorが出るがValaでは戻り値で処理する
    #
    # filesrc > flacparse > flacdec
    #  > tee > queue_audio > audioconvert > audioresample > autoaudiosink (サウンドカード出力)
    #     +--> queue_file > wavenc > filesink (ファイル書き出し)
    try:
      filesrc.link (flacparse)
      flacparse.link (flacdec)
      # 分岐したいところでteeに入れる
      flacdec.link (tee)
      # teeからそれぞれのqueueプラグインにリンクする
      tee.link (queue_audio)
      tee.link (queue_file)
      # 音を鳴らすためのルート
      queue_audio.link (audioconvert)
      audioconvert.link (audioresample)
      audioresample.link (autoaudiosink)  # 終点
      # WAVEファイルに書き出すためのルート
      queue_file.link (wavenc)
      wavenc.link (filesink)              # 終点
    except gst.LinkError as msg:
      print ('Error: {0}'.format (msg), file=sys.stderr)
      return 1

    # 準備完了
    # 再生状態にしてメインループ開始
    pl.set_state (gst.STATE_PLAYING)
    try:
      self.__loop.run ()
    except KeyboardInterrupt:
      # Ctrl+Cが押されたときはそこでループを抜けて停止・終了
      # これをしないとGStreamerのCRITICALメッセージが出る
      self.__loop.quit ()
    # 停止状態にして終了
    pl.set_state (gst.STATE_NULL)
    return self.__retval


if __name__ == '__main__':
  app = GstTeeTest ()
  sys.exit (app.main ())

下は実行例。

$ [gstteetest.pyの場所] [FLACファイルの場所] [WAVEファイルの場所]

関連記事:

参考URL:

使用したバージョン:

  • Python 2.6.5
  • PyGObject 2.21.1
  • PyGST 0.10.18
  • GStreamerライブラリ 0.10.29