PCオーディオをRTPで転送する

ゲームエンジンのストリーム入出力のためにRTPを検討している。RTPはUDPベースのプロトコルで、RTSPのようなリアルタイムメディアプロトコルの基盤として使用されている。要するにタイムスタンプとシーケンス番号の付いたUDPパケット標準と言える。teslawireは企画としてマルチプレイ(一人が複数デバイスで同時にゲームをプレイする)を盛り込んでいるため、ネットワーク経由での操作感を早期に検討する必要がある。ネットワークとオーディオの共有が実際の環境でどういうプレイ感になるのかを実際の環境で評価するため、RTPを通した施策を行う。
操作データの転送方法として、従来はOpenSoundControl(OSC)を検討していたがOSCは開発の進展が最近無いのと、多くの環境で採用されてはいるがMIDIほどの普及を見なかったためMIDIに乗り換えることにした。RTPはMIDIを転送するためのRFCも既に発行されており、AppleiOSMacOSが標準でサポートしている。ただし、Appleの実装は独自のBonjourベースのセッションプロトコルを持つため、通常のMIDIバイスとしてプログラムするためにはそれも実装する必要がある。
(操作データだけでなくオーディオも同時に検討する。これは効果音をタブレット等の手元デバイスから再生するため。)

PCオーディオの転送

まず、RTPを使用したオーディオ転送の品質感を確認するために、ありもののソフトでPCオーディオをリモート転送してみることにした。
前回( http://d.hatena.ne.jp/mjt/20131223/p2 )同様、一旦jack( http://jackaudio.org/ )にバッファし、書き出すという方法を採る。UDPパケットへの変換にはgstreamerを使用する。
gstreamerにはjacksrc/jacksinkプラグインが有り、ストリームをjackに中継することができる。

Gstreamerのバイナリ配布にはjackのためのプラグインが含まれていない。CMakeで適当にビルドする:

project(winjackgst)
cmake_minimum_required(VERSION 2.8)

set(jackroot "c:/libs/jack")
set(jackinc ${jackroot}/includes)
set(jacklibs ${jackroot}/lib)

set(gstroot "c:/gstreamer/1.0/x86_64")
set(gstinc ${gstroot}/include)
set(gstlibs ${gstroot}/lib)
set(srcroot
    "c:/repos/gst-plugins-good/ext/jack")
set(srcbase
    "c:/repos/gst-plugins-good")

link_directories(${gstlibs} ${jacklibs})

include_directories(
    ${jackinc}
    ${gstinc}
    ${gstinc}/glib-2.0
    ${gstinc}/gstreamer-1.0
    ${gstlibs}/glib-2.0/include
    ${srcbase}/gst-libs
    )

set(srcs
    ${srcroot}/gstjack.c
    ${srcroot}/gstjackaudioclient.c
    ${srcroot}/gstjackaudiosink.c
    ${srcroot}/gstjackaudiosrc.c
    ${srcroot}/gstjackutil.c)

add_definitions(
    -DPACKAGE=\"xxlocal\"
    -DGETTEXT_PACKAGE=\"xxlocal\"
    -DVERSION=\"1.0.0\" -DGST_LICENSE=\"LGPL\" -DGST_PACKAGE_NAME=\"xxlocal\"
    -DGST_PACKAGE_ORIGIN=\"xxlocal\")

add_library(libgstjackaudio SHARED ${srcs})

target_link_libraries(libgstjackaudio
    libjack64
    glib-2.0
    gobject-2.0
    gstaudio-1.0
    gstreamer-1.0)

install(TARGETS libgstjackaudio COMPONENT runtime DESTINATION
    ${gstlibs}/gstreamer-1.0)

このプラグインは、jack_free()関数のかわりにlibcのfree()を使用しているバグが有り、Windowsで使うにはそれを修正する必要がある(jackはMSVCRT.dllにリンクしているが、プラグインがVCの提供するCRTにリンクしてしまうことによりlibcの混在が起こる)。

gst-launch-1.0.exe -v udpsrc port=5001 caps="application/x-rtp,media=(string)audio,clock-rate=(int)44100,encoding-name=(string)L16,channels=(int)2" ! rtpL16depay ! audioconvert ! jackaudiosink connect=0

jackaudiosinkは自動的にグラフを構築してしまうので、事故を防ぐためにconnect=0を必ず指定する。また、jackは全てのオーディオサンプルを32bit floatで扱うため、audioconvertプラグインを使用してサンプル形式を変換する必要もある。
実際のRTPの受信は、udpsrcからrtpL16depayを使用する。udpsrcは標準ではcapsを提供しないため、後段のプラグインにRTPパケットであることを認識させるためにcapsを手書きする必要がある。
Windowsではjackのバッファサイズを1024未満にできなかった。このため、レイテンシも1024サンプル x2 = 2048サンプル + ネットワーク遅延となる。gstreamerが原因に見えるので、自前のRTP実装を準備したい。

手元のUbuntuにはGStreamer 1.x系がインストールされていなかったので、今回はGStreamer-0.10を使った。
諸々はデフォルトで存在する。

gst-launch-0.10 pulsesrc ! audio/x-raw-int,channels=2,rate=44100 ! audioconvert ! rtpL16pay ! udpsink host=spring.local port=5001

送信は非常にシンプルで、srcからrtpL16payに載せ、udpsinkにホストを指定して渡すだけとなる。ここには出てきていないが、jackの内部でPulseAudioの入出力を直接接続してリレーしている。