#topicpath * OpenBSD発の音声フレームワーク - sndio [#t571d65f] RIGHT:EBUG 第66回会合 ~ 2018年 8月25日、長岡市 ながおか市民センター ~ 川俣吉広、kaw@on.rim.or.jp **sndioとは [#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); ***構成要素 [#d9d2cae2] :ハードウェア|様々な音声機器は、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]]である。~ 上図の例で挙げられているIntelのICH8 I/Oコントローラ・ハブでは[[Intel(R) HD Audio規格のコーデック>https://web.archive.org/web/20190627115932/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の実行例 $ 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の実行例 $ 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に対応している。~ ...OpenBSDネイティブのアプリケーション([[cdio(1)>https://man.openbsd.org/cdio.1]]など)。~ あるいは外部プロジェクトのアプリケーションであっても、開発元でsndioに対応している場合。~ 勿論パッチ等の対応なしでそのまま使用できる。~ ~ --音声アプリケーション自体は対応していないが、アプリケーションがリンクするライブラリが対応している。~ ...例えば音声編集ソフトのAudacityなど。Audacity自体はsndioに対応していないが、クロスプラットフォームの音声ライブラリであるPortAudioをリンクしてビルドするとPortAudioはsndioをサポートしているため、Audacityもsndio対応となる。 portaudioを使っているportsを数えてみる $ 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では、このケースが最も多いようだ。 sndio対応のパッチを当てているportsを数えてみる $ 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などとデータのやりとり行うことで対応できる。 ***音声デバイスへのアクセス制御 [#e3837e6f] 音声デバイスが無制限に共有されないように、sndioではセッションクッキーを使った認証を行う。~ sndiodに接続時、音声アプリケーションは$HOME/.aucat_cookieに可能された128ビットのランダムなデータをクッキーとしてsndioに提示する。 -他にsndioに接続している音声アプリケーションがない場合クッキーは無条件で登録され、接続は許可される。 -2番目以降のアプリケーションがクッキーを提出した時、sndiodに登録済みのものと同じ場合に接続が許可される。 -接続しているアプリケーションがなくなった時点で登録されていたクッキーは破棄される。 クッキーとして$HOME/.aucat_cookieを使うため、通常は同一のユーザからの接続のみが同時に受け入れられるが、$HOME/.aucat_cookieを同じ内容にすれば異ったユーザ間でもsndiodの共有が可能となる。 sndiodはデフォルトではUNIXドメインソケットでの接続のみ受け付ける。INETドメインでの接続を受け付ける場合は''-L''オプションで明示的に指定する必要がある。 **sndioの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_initpar() /* パラメータの初期化 */ ↓ sio_open() /* 音声デバイスや音声サーバに接続 */ ↓ sio_setpar() /* パラメータの設定 */ ←←←← sio_getpar() /* 設定結果の確認 */ ↑ ↓ ↑ 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 に直接アクセス (sndiodを介さない) ~ ''snd/0.rear'' ... デフォルトで起動しているsndiodのリアスピーカ出力 参考: -sndio - hints on writing & porting audio code ~ http://www.sndio.org/tips.html -sndioのサンプルコード ~ https://github.com/t6/sndio/tree/master/examples **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を使用する例 ~ lameはpkg_addで導入してもsndio非対応であるが、aucatと組合せてsndioデバイスの音声出力をMP3に変換してファイルに保存できる。~ 以下の例ではリモートマシンの音声入力をaucatで受信し、パイプ経由でlameに渡すことで音声データをon the flyでMP3形式に変換している。~ 受け渡しするデータはheaderless raw形式なので、lameの入力フォーマットとして-rを指定し、サンプリングレートも明示的に指定している。~ ~ リモートホストでの事前設定 remote_host # rcctl set sndiod flags '-L -' remote_host # rcctl restart sndiod ローカルでのコマンド実行 local_host $ aucat -f snd@remote_host/0 -o - | lame -r -s 48 - recinput.mp3 -サンプルプログラム ~ このプログラムはステレオ音声のL (Ch1)に1kHz, R (Ch2)に400Hzの正弦波を1分間出力する。 /* sin_osc ... generates sine waves * and outputs to sndio default device * * Ch. 1: 1kHz, -6dBFS * Ch. 2: 400Hz, -6dBFS * * Duration: 60 secs */ #include <stdint.h> #include <stdlib.h> #include <stdio.h> #include <math.h> #include <sndio.h> /* frequency parameters in Hz */ #define SAMPLE_FREQ 48000.0 #define OUTPUT_FREQ1 1000.0 #define OUTPUT_FREQ2 400.0 /* buffer size of waveform */ #define SAMPLES 480 /* SAMPLES per CYCLE */ #define SPC1 (SAMPLE_FREQ/OUTPUT_FREQ1) #define SPC2 (SAMPLE_FREQ/OUTPUT_FREQ2) /* output level of sine wave with: * sampling point 't', * samples per cycle 'spc' */ #define SINVAL(t, spc) ((int16_t)(INT16_MAX*sin(2.0*M_PI*t/spc)/2)) void fail_exit(const char *msg) { fprintf(stderr, "%s failed\n", msg); exit(1); } int main() { /* waveform buffer */ int16_t wf[2*SAMPLES], /* signed 16bit per sample */ *p_wf; /* fill buffer */ p_wf = wf; for (int i = 0; i<SAMPLES; i++) { *p_wf++ = SINVAL(i, SPC1); /* 1kHz to Ch1 */ *p_wf++ = SINVAL(i, SPC2); /* 400Hz to Ch2 */ } /* initialization of sndio */ struct sio_par par; struct sio_hdl *hdl; ssize_t n; sio_initpar(&par); par.sig = 1; par.bits = 16; par.pchan = 2; par.rate = 48000; hdl = sio_open("snd/0", SIO_PLAY, 0); /* "snd/0" may be SIO_DEVANY */ if (hdl == NULL) /* or "default". */ fail_exit("sio_open()"); if (!sio_setpar(hdl, &par)) fail_exit("sio_setpar()"); if (!sio_getpar(hdl, &par)) fail_exit("sio_getpar()"); if (!sio_start(hdl)) fail_exit("sio_start()"); /* output for 60 secs */ for (int i=0; i<6000; i++) { n = sio_write(hdl, wf, sizeof(wf)); if (n == 0) fail_exit("sio_write()"); } sio_close(hdl); return 0; } 実行例 $ LDFLAGS='-lm -lsndio' make sin_osc cc -O2 -pipe -lm -lsndio -o sin_osc sin_osc.c $ ./sin_osc 実際に出力された音声((実際にはWAV形式の音声ファイルが出力されるが、サイズが大きいためMP3形式に変換してアップロードした。))- &ref(sin_osc.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