#topicpath
* sndio - OpenBSDの音声フレームワーク [#t571d65f]
RIGHT:EBUG 第66回会合 ~
2018年 8月25日、長岡市 ながおか市民センター ~
川俣吉広、kaw@on.rim.or.jp

**概要 [#xbc2884e]
[[sndio(7)>https://man.openbsd.org/sndio.7]]は音声を統一的に扱うための仕組みで、Alexandre Ratchovらによって2008年リリースのOpenBSD 4.5に始めて導入された。
現在はFreeBSD/NetBSD/Linuxにも移植されている。

ALSA, JACK, OSS, PulseAudioなどの音声フレームワークと同様、sndioは音声を扱うハードウェアとアプリケーションとの橋渡しをする。
具体的には、sndioは以下のような機能を持っている。

:音声デバイスを共有する|音声アプリケーションがデバイスに直接アクセスした場合、アクセスできるのは単一のアプリケーションだけ。~
sndioを介在させることで複数のアプリケーションが同時に音声を再生・収録できることを可能にする。

:音声のフォーマット変換を行う|音声データの形式、サンプルレート、量子化ビット深度、チャンネル数などのパラメータが音声デバイスとアプリケーションとで異っていても、sndioで変換を行うことで支障なく使用できる。

:音源の制御を可能にする|sndioは音声データそのもの以外に、起動・停止、音量制御、タイムコードなどをMIDIプロトコルを使用して伝送することができる。これにより、音源の制御を行うことができる。

:信号のルーティングを行う|sndioサウンドサーバでは複数の音源をミックスしたり、あるいは複数の音声アプリケーションへ送出するなどのルーティングを行う。~
これらの伝送はネットワークを経由して、他ホストで稼動している音声デバイスやアプリケーションを使用することもできる。

**構成 [#z972347e]
以下に、OpenBSDでsndioフレームワークが動作している様子を示す。
#ref(sndio.png);

:ハードウェア|様々な音声機器は、PC内の音声コーデックに接続される。コーデックはアナログ機器とのA/D, D/A変換や複数入出力のミキシングやファンアウト、そして各信号のレベル制御などをおこなう。

:デバイスドライバ|カーネル内には、コーデックに対応したデバイスドライバがあり、コーデックの機種毎の機能に対応した制御を行う。
例えばIntelのICH8 I/Oコントローラ・ハブは[[Intel(R) HD Audio規格のコーデック>http://www.vitalsparks.com/hdaudio.html]]を搭載しており、これに対応するドライバは、[[azalia(4)>https://man.openbsd.org/azalia.4]]である。~
コーデックに対応したドライバの上位にはデバイス非依存の[[audio(4)>https://man.openbsd.org/audio.4]]があり、ユーザプロセスに対して一貫したAPIを提供する。~
このレイヤーを参照・操作するツールとして[[audioctl(1)>https://man.openbsd.org/audioctl.1]]や[[mixerctl(1)>https://man.openbsd.org/mixerctl.1]]が提供されている。
 $ audioctl
 name=azalia0
 mode=play,record
 pause=0
 active=1
 nblks=8
 blksz=960
 rate=48000
 encoding=s16le
 play.channels=2
 play.bytes=3796930560
 play.errors=883200
 record.channels=2
 record.bytes=3796930560
 record.errors=552960
~
 $ mixerctl -v | sort
 inputs.dac-0:1=234,234 
 inputs.beep=119 
 inputs.beep_mute=off  [ off on ]
 inputs.dac-2:3=234,234 
 inputs.hp_source=sel6,mix6  { sel6 mix6 }
 inputs.mic2=0,0 
 inputs.mic3=0,0 
 inputs.mic3_source=sel7,mix6  { sel7 mix6 }
 inputs.mic=0,0 
 inputs.mix4_source=sel3,mix6  { sel3 mix6 }
 inputs.mix6_mic2=0,0 
 inputs.mix6_mic=0,0 
 inputs.mix6_source=mic,mic2  { mic mic2 }
 inputs.sel3_source=dac-0:1  [ dac-0:1 dac-2:3 ]
 inputs.sel4_source=dac-0:1  [ dac-0:1 dac-2:3 ]
 inputs.sel6_source=dac-0:1  [ dac-0:1 dac-2:3 ]
 inputs.sel7_source=dac-0:1  [ dac-0:1 dac-2:3 ]
 inputs.spkr_source=dac-2:3,mix6  { dac-2:3 mix6 }
 outputs.hp_boost=off  [ off on ]
 outputs.hp_mute=off  [ off on ]
 outputs.hp_sense=plugged  [ unplugged plugged ]
 outputs.master.mute=off  [ off on ]
 outputs.master.slaves=dac-0:1,dac-2:3,hp,spkr  { dac-0:1 dac-2:3 beep hp spkr mic3 mix6 mic3 }
 outputs.master=255,255 
 outputs.mic2_dir=input-vr80  [ none input input-vr0 input-vr50 input-vr80 input-vr100 ]
 outputs.mic3_dir=input-vr80  [ none output input input-vr0 input-vr50 input-vr80 input-vr100 ]
 outputs.mic3_mute=off  [ off on ]
 outputs.mic3_sense=unplugged  [ unplugged plugged ]
 outputs.mic_dir=input-vr80  [ none input input-vr0 input-vr50 input-vr80 input-vr100 ]
 outputs.mic_sense=plugged  [ unplugged plugged ]
 outputs.mix6=0,0 
 outputs.mix6_mute=off  [ off on ]
 outputs.spkr_boost=off  [ off on ]
 outputs.spkr_eapd=on  [ off on ]
 outputs.spkr_mute=on  [ off on ]
 outputs.spkr_muters=hp,mic3  { hp mic3 }
 record.adc-0:1=200,200 
 record.adc-0:1_mute=off  [ off on ]
 record.adc-0:1_source=mic  [ mic mic2 ]
 record.adc-2:3=200,200 
 record.adc-2:3_mute=off  [ off on ]
 record.adc-2:3_source=mic2  [ mic mic2 ]
 record.volume.mute=off  [ off on ]
 record.volume.slaves=adc-2:3,adc-0:1  { adc-2:3 adc-0:1 mic mic2 }
 record.volume=200,200 

:音声サーバ - [[sndiod(8)>https://man.openbsd.org/sndiod.8]]|sndioフレームワークの中核で、ブート時に起動されデフォルトの音声デバイス/dev/audioへのアクセスを提供する。前節の「概要」で述べた機能の殆どは、このsndiodによってサポートされる。~
sndiodと音声アプリケーションはソケットインターフェースを使ってデータのやり取りをする。よって、ネットワーク経由で他ホストのsndiodや音声アプリケーションとデータのやり取りをすることも可能。

:ユーザコマンド - [[aucat(1)>https://man.openbsd.org/aucat.1]]|コマンドレベルでsndioにアクセスするためのツール。aucatもsndiod同様、sndioの機能の殆どを提供する。~
sndiodがデーモンとしてバックグラウンドで機能を提供するのに対し、aucatはユーザが直接オンライン、あるいはオフラインでsndioの機能を利用することを意図して作成されている。~
例えば、aucatには処理を行う音声データをファイルから入力したり、ファイルへ出力したりする機能がある。~
~
aucatコマンド自体はOpenBSD 2.0から存在し、sndioフレームワークが登場する以前のOpenBSD 4.3までは単に複数の音声ファイルを連結して再生する(concatenate and play audio files)コマンドだった。~
OpenBSD 4.5でsndioフレームワークが登場した時点ではsndiodはなく、aucatがデーモンの役目も負っていた。OpenBSD 5.1以降はsndiodとaucatとに役割が分割された。

:音声アプリケーション|音声を扱うアプリケーションでsndioを使用するには、以下のパターンが考えられる。
--音声アプリケーション自体が、最初からsndioに対応している。~
...特に手を加えずにビルドして使う。~
~
--音声アプリケーション自体は対応していないが、アプリケーションがリンクするライブラリが対応している。~
...例えば音声編集ソフトのAudacityなど。Audacity自体はsndioに対応していないが、クロスプラットフォームの音声ライブラリであるPortAudioをリンクしてビルドするとPortAudioはsndioをサポートしているため、Audacityもsndio対応となる。
 $ cd /usr/ports
 $ find * -type d -name patches | xargs grep -rils 'portaudio' | cut -d/ -f1,2 \
 | sort | uniq | wc -l
 12
~
--音声アプリケーションにsndio対応のパッチを当てる。~
...次項で説明するsndio APIを用いるように、アプリケーションのソースコードを変更する。~
ports/packagesでは、このケースが最も多いようだ。
 $ cd /usr/ports
 $ find * -type d -name patches | xargs fgrep -rls 'sndio.h' | cut -d/ -f1,2 \
 | sort | uniq | wc -l
 41
~
--sndioに対応していない音声アプリケーションとsndioツールを組合せて使う。~
...音声アプリケーションが再生あるいは収録のみで、リアルタイム性を要求されないのであれば、パイプやファイルを経由してaucatなどとデータのやりとりをする。

**API [#ba4463c8]
sndio APIを使用するには、libsndioより提供される各種関数を使う。

-[[sio_open, sio_close, sio_setpar, sio_getpar, sio_getcap, sio_start, sio_stop, sio_read, sio_write, sio_onmove, sio_nfds, sio_pollfd, sio_revents, sio_eof, sio_setvol, sio_onvol, sio_initpar>https://man.openbsd.org/sio_open.3]]

-[[mio_open, mio_close, mio_read, mio_write, mio_nfds, mio_pollfd, mio_revents, mio_eof>https://man.openbsd.org/mio_open.3]]

sio_*が音声ストリーム関連、mio_*がMIDIストリーム関連の関数となる。

処理の流れ:
 sio_open()                   /* 音声デバイスや音声サーバに接続 */
    ↓
 sio_getpar() / sio_setpar()  /* 音声フォーマットの取得と設定 */
    ↓
 sio_start()                  /* 処理の開始 */
    ↓
 sio_read() / sio_write()     /* 入出力 */
    ↓
 sio_stop()                   /* 処理の終了 */
    ↓
 sio_close()                  /* 接続を閉じる */
MIDI接続の場合も、これに準ずる。

sio_open()では、一番目の引数で、以下の記法によって接続先を指定する:
>'''type'''[''@'''''hostname''']['','''''unit''']''/'''''devnum'''[''.'''''option''']

:'''type'''|音声デバイスの種類 ... rsnd, rmidi, snd, midithru, midi, default
:'''hostname'''|リモートに接続するばあいのホスト名
:'''unit'''|接続するサーバの番号
:'''devnum'''|デバイスの番号
:'''option'''|サブデバイス文字列

この記法はsndiodやaucatなどで音源のデバイスやサーバに接続する場合の接続先指定としても使用される。

例:
>rsnd/0 ... /dev/audio0 ~
snd/0.rear ... デフォルトで起動しているsndiodのリアスピーカ出力

**sndioの使用例 [#x7ebf0e4]
-ffmpegを使用したscreen castの例 ~
sndiodにサブデバイスとしてmonを追加(''-m play,mon -s mon'')。~
これにより、音声アプリケーションの出力を他のアプリケーションへ入力できるようになる。
 # rcctl set sndiod flags '-s default -m play,mon -s mon'
 # rcctl restart sndiod
この設定を行っておくとffmpeg実行時、''-i snd/0.mon'' を指定することで、スクリーンキャプチャ時にアプリケーションの再生音を収録する。
 $ ffmpeg -f x11grab -s 800x600 -i :0.0+100,100 -f sndio -i snd/0.mon screencapt.mpg

-sndio非対応のアプリケーションでsndioを使用する例 ~
pkg_addで導入したlameは、sndio非対応であるが、aucatと組合せてsndioデバイスの音声出力をMP3に変換してファイルに保存できる。~
以下の例ではaucatとlameをパイプで繋いで音声データを受け渡している。
データはheaderless raw形式なので、lameの入力フォーマットとして-rを指定し、サンプリングレートも明示的に指定している。
 $ aucat -o - | lame -r -s 48 - recinput.mp3

**情報源 [#fcd638e8]
-OpenBSD FAQ - Multimedia ~
https://www.openbsd.org/faq/faq13.html

-sndio home ~
http://www.sndio.org/

-sndio - OpenBSD audio & MIDI framework for music and desktop applications ~
http://www.openbsd.org/papers/asiabsdcon2010_sndio_slides.pdf

----
#topicpath
Top Index Search Recent Backups  Help  RSS