sndio in FreeBSD
Table of Contents
sndio in FreeBSD
ports/audio/sndioに、こんな広告が出てた。
Sndio is a small audio and MIDI framework part of the OpenBSD project. It provides an lightweight audio & MIDI server and a fully documented user-space API to access either the server or directly the hardware in a uniform way. Sndio is designed to work for desktop applications, but pays special attention to synchronization mechanisms and reliability required by music applications. Reliability through simplicity are part of the project goals.
OpenBSDの成果は、頑張って他のOSにも提供されている。どこかの奴みたいに、 自分の事しか考えない狭い心を持ったものとは、大違い。たとえば、systemd みたいな奴ね。
example/play.c
作者さんの手による模範的な音の鳴らし方。
/* * defaults parameters */ sio_initpar(&par); par.sig = 1; par.bits = 16; par.pchan = 2; par.rate = 48000;
これが現実的な設定だよな。前回の24Bitって、安物PCの音装置ではありえな い設定だろう。
while ((ch = getopt(argc, argv, "r:c:e:b:x:")) != -1) { switch(ch) { case 'r': if (sscanf(optarg, "%u", &par.rate) != 1) { fprintf(stderr, "%s: bad rate\n", optarg); exit(1); } break;
sscanfの上手な利用方法。
bufsz = par.bps * par.pchan * par.round; buf = malloc(bufsz);
適切なバッファーサイズの決め方。
for (;;) { len = read(STDIN_FILENO, buf, bufsz); : if (len == 0) { fprintf(stderr, "eof\n"); break; } n = sio_write(hdl, buf, len); if (n == 0) { fprintf(stderr, "sio_write: failed\n"); exit(1); } : } sio_close(hdl);
メインループ。読み込みバイト数がゼロなら、それはeofに逹っした証拠ですっ て、きちんと明言してるな。前回のオイラーみたいにwhile(read){..} みたい に、判定をwhile任せにしていない。
run
大音量なので、ヴォリュームを絞っておく。
sakae@slfb:~/snd $ mixer vol=0.3:0.3 vol.volume: 0.50:0.50 -> 0.30:0.30 pcm0:mixer: <Realtek ALC269 (Analog 2.0+HP/2.0)> on hdaa0 (play/rec) (default) vol = 0.30:0.30 pbk pcm = 0.98:0.98 pbk speaker = 0.59:0.59 rec rec = 0.37:0.37 pbk igain = 0.00:0.00 pbk ogain = 1.00:1.00 pbk monitor = 0.80:0.80 rec src
こうしておいて、リソースが一時的に読めないか、trussを使って確認。まずは、標準のやつ。 case aucat
read(4,"\0\0\0\a\M^?\M^?\M^?\M^?\0\0\^A"...,40) = 40 (0x28) read(4,"\0\0\0\^F\M^?\M^?\M^?\M^?\0\0\^A"...,40) = 40 (0x28) read(4,0x20a31090,40) ERR#35 'Resource temporarily unavailable' poll({ 4/POLLIN|POLLOUT },1,-1) = 1 (0x1) write(4,"\0\0\0\^E\M^?\M^?\M^?\M^?\0\0\b"...,40) = 40 (0x28) write(4,"\M-%&c\0\0\0\0\0\M^S\M-Eg\0\0\0"...,2048) = 2048 (0x800) poll({ 4/POLLIN },1,-1) = 1 (0x1) read(4,"\0\0\0\a\M^?\M^?\M^?\M^?\0\0\^A"...,40) = 40 (0x28) read(4,"\0\0\0\^F\M^?\M^?\M^?\M^?\0\0\^A"...,40) = 40 (0x28) read(4,0x20a31090,40) ERR#35 'Resource temporarily unavailable' poll({ 4/POLLIN|POLLOUT },1,-1) = 1 (0x1) write(4,"\0\0\0\^E\M^?\M^?\M^?\M^?\0\0\b"...,40) = 40 (0x28) write(4,"\^_\^D\M-L\M^?\0\0\0\0\M-#$\M-E"...,2048) = 2048 (0x800) poll({ 4/POLLIN },1,-1) = 1 (0x1)
次はオイラーが前回作成した恐怖のサンプル。case myapp
read(3,"\0\0\0\a\M^?\M^?\M^?\M^?\0\0\0"...,40) = 40 (0x28) read(3,0x20a08090,40) ERR#35 'Resource temporarily unavailable' poll({ 3/POLLIN },1,-1) = 1 (0x1) read(3,"\0\0\0\a\M^?\M^?\M^?\M^?\0\0\0@"...,40) = 40 (0x28) read(3,"\0\0\0\^F\M^?\M^?\M^?\M^?\0\0\^A"...,40) = 40 (0x28) read(3,0x20a08090,40) ERR#35 'Resource temporarily unavailable' poll({ 3/POLLIN|POLLOUT },1,-1) = 1 (0x1) write(3,"\0\0\0\^E\M^?\M^?\M^?\M^?\0\0\b"...,40) = 40 (0x28) write(3,"\0\M^N\M-H\M-$\0\0\0\0\0bE\240\0"...,2048) = 2048 (0x800)
OSが代わっても、発生するな。安心したぞ。
他の方法でもチェック
河童版の作者さんにも問い合わせ。河童版 7.4 では、どうでしょうか? そうか、タイムマシンで古いOSを使ってみたらというアドバイスだな。 結果は、やはりエラーが発現した。エンバグの可能性は、これで消滅した。
ふと、sndioとの組み合わせで、まずい事が起きていると想像。ならば、音装 置に書き出すのを止めて、普通のファイルへ出力しよう。
oo = open("dmy", O_WRONLY | O_CREAT | O_TRUNC, 0666); while(read(fd, buf, SZ)) { //sio_write(my, buf, SZ); write(oo, buf,SZ); }
もはや、ファイルのコピー・アプリだな。勿論、結果は問題なし。そして、あ たり前だけど、瞬時に終了する。ここまできたら、アドバイザー頼リだな。
ask ChatGPT
Q: ブロックモードでオープンしてるので、こういうエラーは 発生しないと思っていたのですが、いかがでしょうか?
A: `errno 35` (Resource temporarily unavailable) は、通常 `EAGAIN` や `EWOULDBLOCK` として知られ、非ブロッキング I/O の操作で発生します。このエラーは、リソースが一時的に利用できないため、操作が完了できなかったことを意味しますが、OpenBSD の場合でもこのエラーは一般的です。
`fd = open(sndf, O_RDONLY
);` はデフォルトでブロッキングモードで開かれますが、`read` システムコールが `errno 35` を返すことがあるのは、音声デバイスの遅延や、オーディオバッファが一時的にデータを受け入れられない状態にある可能性が考えられます。
対策としては、以下のいくつかの方法を試すことができます:
エラー処理を追加: `read` や `
sio_write
` の戻り値が `-1` で、`errno` が `EAGAIN` または `EWOULDBLOCK` の場合は、リトライするか、`select` や `poll` で再度操作が可能になるのを待つロジックを追加することができます。while (1) { ssize_t n = read(fd, buf, SZ); if (n == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // リトライのために待つ処理を追加 (例: sleep, select) continue; } else { // 他のエラー処理 perror("read error"); break; } } if (n == 0) { // EOF に達した場合 break; } sio_write(my, buf, n); }
ファイルディスクリプタが非ブロッキングモードになっていないか確認: `fcntl` でファイルディスクリプタが非ブロッキングモードに設定されているかどうかを確認し、必要に応じてブロッキングモードに戻すことも可能です。
int flags = fcntl(fd, F_GETFL, 0); if (flags & O_NONBLOCK) { fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); // ブロッキングモードに設定 }
- バッファサイズの調整: `SZ` の値が適切でない場合、オーディオバッファが一時的に書き込みを拒否することがあります。バッファサイズを調整するか、書き込むデータのサイズを小さくすることも考慮してみてください。
これらの方法で、音声データの読み込みと書き込みが安定するか試してみてく ださい。
example/play.c
付属してたサンプルを、あえてOpenBSDでやってみる。その前に音量制御も確 認。更にその前に、sndiodを走らせておくのを忘れるな。永続的に使うなら、 /etc/rc.confに起動設定を記述しておけ。
vol
制御卓の全てを確認するには、特権が必要らしい。
ob$ doas mixerctl -av inputs.dac-0:1=126,126 inputs.dac-2:3=126,126 record.adc-0:1_mute=off [ off on ] : outputs.master=126,126 outputs.master.mute=off [ off on ] outputs.master.slaves=dac-0:1,dac-2:3,spkr,hp { dac-0:1 dac-2:3 spkr hp mic2 }
ユーザー・レベルでは、簡単操作のみが許される?
ob$ sndioctl input.level=0.490 input.mute=0 output.level=0.494 output.mute=0 server.device=0 app/aout0.level=1.000 app/qemusys0.level=1.000 app/qemusys1.level=1.000 ob$ sndioctl output.mute=1
コンパイル & ラン
play.cをコンパイルするには、tools.[ch]も必要。
ob$ cc -c tools.c -I. ob$ cc -g tools.o play.c -I. -lsndio
これで実行できる。
ob$ cat z.raw | ./a.out 1920 bytes buffer, max latency 17280 frames playpos = 0, writepos = 69120, latency = 69120 playpos = 1920, writepos = 71040, latency = 69120 playpos = 3840, writepos = 72960, latency = 69120 : playpos = 1080960, writepos = 1150080, latency = 69120 playpos = 1082880, writepos = 1152000, latency = 69120 eof
on qemu
qemuでアプリを動かし、カーネルを追求。結論は、現象が発現せず。タイミン グの問題なのかなあ?
(gdb) bt 2 #0 dofilereadv (p=0xd19824ec, fd=3, uio=0xf3aa2d50, flags=0, retval=0xf3aa2e2c\ ) at /usr/src/sys/kern/sys_generic.c:248 #1 0xd0a4e783 in sys_read (p=0xd19824ec, v=0xf3aa2e34, retval=0xf3aa2e2c) at /\ usr/src/sys/kern/sys_generic.c:167 (More stack frames follow...)
sys_read()
の実体は、dofilereadv。
cnt = uio->uio_resid; error = (*fp->f_ops->fo_read)(fp, uio, flags); => if (error) { if (uio->uio_resid != cnt && (error == ERESTART || error == EINTR || error == EWOULDBLOCK)) error = 0; }
エラーを握り潰してしまう事のあるのね。EWOULDBLOCKはChatGPも指摘してく れているけど、実はEAGAIN と同値だったりする。
(gdb) p *fp->f_ops->fo_read $7 = {int (struct file *, struct uio *, int)} 0xd098a0a0 <vn_read> (gdb) c Continuing. Breakpoint 3, vn_read (fp=0xd181ade4, uio=0xf3aa2d50, fflags=0) at /usr/src/sys\ /kern/vfs_vnops.c:343
動的に関数がcallされる場合、関数名を割り出しておいてBPを設定するのが、 楽だったりする。
掘り下げる
こんなアドバイスを河豚板の作者さんから頂いた。
そうすると、sndioは全二重なので、音声の入力ストリーム側の何かですかね。 それだと、バッファが空という状況は有り得ますから、resource unavailable とかも出そうですね。
それから、GPTさんのアドバイスも参考に、基本に立ち返ってみる。
while (1) { n = read(fd, buf, SZ); printf("n = %d\n" , n); if (n == 0) { break; } n = sio_write(my, buf, n); printf(" n = %d\n", n); }
何の事は無い。読み書きしたバイト数を、正直に報告する様にしただけ。
ob$ ./a.out : n = 9600 n = 9600 n = 9600 n = 9600 n = 0
実行結果の最後の部分。EOFに逹っすると、読みだしが0で、while(1)を脱出。 それに対するktrace/kdumpの結果(見易い様に編集)。
CALL read(4,0x38e35138,0x2580) -> RET read 9600/0x2580 CALL write(1,0x5c8f4000,0x9) -> RET write 9 CALL write(3,0x767356f8,0x28) -> RET write 40/0x28 CALL write(3,0x38e35138,0x780) -> RET write 1920/0x780 *CALL poll(0xcf7f8580,1,INFTIM) -> RET poll 1 *CALL read(3,0x767356d0,0x28) -> RET read 40/0x28 *CALL read(3,0x767356d0,0x28) -> RET read 40/0x28 *CALL read(3,0x767356d0,0x28) -> RET read -1 errno 35 Resource temporarily unavailable *CALL poll(0xcf7f8580,1,INFTIM) -> RET poll 1 CALL write(3,0x767356f8,0x28) -> RET write 40/0x28 CALL write(3,0x38e358b8,0xf00) -> RET write 3840/0xf00 *CALL poll(0xcf7f8580,1,INFTIM) -> RET poll 1 *CALL read(3,0x767356d0,0x28) -> RET read 40/0x28 *CALL read(3,0x767356d0,0x28) -> RET read 40/0x28 *CALL read(3,0x767356d0,0x28) -> RET read -1 errno 35 Resource temporarily unavailable *CALL poll(0xcf7f8580,1,INFTIM) -> RET poll 1 CALL write(3,0x767356f8,0x28) -> RET write 40/0x28 CALL write(3,0x38e367b8,0xf00) -> RET write 3840/0xf00 CALL write(1,0x5c8f4000,0xa) -> RET write 10/0xa CALL read(4,0x38e35138,0x2580) -> RET read 0 CALL write(1,0x5c8f4000,0x6) -> RET write 6
冒頭に付けた*は、ソース上には存在しない余分な物。尚、read/writeのファ
イルデイスクリプターは、1: printf, 3: sio_write
, 4: readを担当してる
(はず)。
pollが出現するのが特徴的なんで、そこにBPを置いてみる。なお、ここからは FreeBSDのportsにあるsndioのlibsndioをgdb出来るようにした環境で実施する。 理由は、OpenBSDの環境だとlibsndioが一応gdbできる様になってるけど、-O2 でコンパイルされてて具合が悪いため。
(gdb) bt #0 poll (pfd=0xffbfea0c, nfds=1, timeout=-1) at /usr/src/lib/libc/sys/poll.c:43 #1 0x2044c822 in sio_psleep (hdl=0x20a08000, event=4) at sio.c:269 #2 0x2044cb1b in sio_write (hdl=0x20a08000, buf=0x403ab8 <buf>, len=9600) at sio.c:391 #3 0x00401855 in main () at s.c:31
read(fd=3 ..)で止めてみると、こんな感じ。
(gdb) bt #0 read (fd=3, buf=0x20a08090, nbytes=40) at /usr/src/lib/libc/sys/read.c:44 #1 0x204480cd in _aucat_rmsg (hdl=0x20a0808c, eof=0x20a08020) at aucat.c:95 #2 0x2044e986 in sio_aucat_runmsg (hdl=0x20a08000) at sio_aucat.c:91 #3 0x2044e73e in sio_aucat_revents (sh=0x20a08000, pfd=0xffbfea0c) at sio_aucat.c:476 #4 0x2044cd91 in sio_revents (hdl=0x20a08000, pfd=0xffbfea0c) at sio.c:433 #5 0x2044c8a8 in sio_psleep (hdl=0x20a08000, event=4) at sio.c:276 #6 0x2044cb1b in sio_write (hdl=0x20a08000, buf=0x403ab8 <buf>, len=9600) at sio.c:391 #7 0x00401855 in main () at s.c:31
hdlな中にはpar情報の他に、関数ポインターが多数隠されている。
(gdb) p *hdl->ops $5 = { close = 0x2044da30 <sio_aucat_close>, setpar = 0x2044dab0 <sio_aucat_setpar>, getpar = 0x2044dc30 <sio_aucat_getpar>, getcap = 0x2044de80 <sio_aucat_getcap>, write = 0x2044e220 <sio_aucat_write>, read = 0x2044e380 <sio_aucat_read>, start = 0x2044e420 <sio_aucat_start>, stop = 0x2044e5e0 <sio_aucat_stop>, flush = 0x2044e610 <sio_aucat_flush>, nfds = 0x2044e650 <sio_aucat_nfds>, pollfd = 0x2044e660 <sio_aucat_pollfd>, revents = 0x2044e6e0 <sio_aucat_revents>, setvol = 0x2044e820 <sio_aucat_setvol>, getvol = 0x2044e850 <sio_aucat_getvol> }
FreeBSD-update
13.3でアップデートすると、こんなメッセージが出てくる様になった。
WARNING: FreeBSD 13.3-RELEASE-p5 is approaching its End-of-Life date. It is strongly recommended that you upgrade to a newer release within the next 2 months.
これは、脅迫か。
freebsd-update -r 13.4-RELEASE upgrade
これをヤレって事かな。ヤレヤレですよ。
さくらの VPS で FreeBSD 13.1-RELEASE をセットアップ
こんな頑張りさんも中にはおられるけど。 そう言えば、OpenBSDもそろそろ降ってくるなあ。こちらは半年の猶予が有る んで、どっしりと構えていれば良い。
The current release is OpenBSD 7.6, released October 8, 2024. This is the 57th release.
来たね。