非ブロッキングモード
前回は、PCM をブロックモードで開いたので、データを書き込む際、すべてのデータが書き込めるまで、関数はブロックされます。
PCM を開く時に、mode を SND_PCM_NONBLOCK にすると、非ブロッキングモードになります。
この場合、書き込みを行った時、バッファがいっぱいで書き込めない場合は、-EAGAIN エラーが返ります。
この状態では、すぐに書き込めないので、他の処理などをして待つか、書き込みが可能になるまで待つ必要があります。
スレッドを使わずに、他の処理と並行させたい場合などは、非ブロッキングモードを使います。
PCM を開いた後に、非ブロッキングモードを変更する場合に使います。
nonblock は、0 でブロックモード、1 で非ブロッキングモードになります。
poll() を使って、読み書きが可能になるなどのイベントが来るまで待ちます。
timeout は、ミリ秒単位のタイムアウト時間です。
SND_PCM_WAIT_INFINITE (-1) で無制限。
以下の特定の値を指定すると、指定した状態になるまで待つことができます。
戻り値は、0 でタイムアウト、1 で読み書きの準備が出来ている。
負の値でエラーコードです。
PCM を開く時に、mode を SND_PCM_NONBLOCK にすると、非ブロッキングモードになります。
この場合、書き込みを行った時、バッファがいっぱいで書き込めない場合は、-EAGAIN エラーが返ります。
この状態では、すぐに書き込めないので、他の処理などをして待つか、書き込みが可能になるまで待つ必要があります。
スレッドを使わずに、他の処理と並行させたい場合などは、非ブロッキングモードを使います。
非ブロッキングモードの設定
int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock);
PCM を開いた後に、非ブロッキングモードを変更する場合に使います。
nonblock は、0 でブロックモード、1 で非ブロッキングモードになります。
待つ
int snd_pcm_wait(snd_pcm_t *pcm, int timeout);
poll() を使って、読み書きが可能になるなどのイベントが来るまで待ちます。
timeout は、ミリ秒単位のタイムアウト時間です。
SND_PCM_WAIT_INFINITE (-1) で無制限。
以下の特定の値を指定すると、指定した状態になるまで待つことができます。
SND_PCM_WAIT_IO (-10001) | 読み書き可能になるまで |
---|---|
SND_PCM_WAIT_DRAIN (-10002) | drain が終わるまで |
戻り値は、0 でタイムアウト、1 で読み書きの準備が出来ている。
負の値でエラーコードです。
drain
非ブロッキングモードで snd_pcm_drain() を実行した時、未再生のデータがある場合は -EAGAIN エラーが返ります。
その場合、PCM の状態が SND_PCM_STATE_DRAINING になり、drain で停止を待っている状態になります。
その場合、PCM の状態が SND_PCM_STATE_DRAINING になり、drain で停止を待っている状態になります。
poll
poll() を使って、バッファへの読み書きが可能になるまで待ちたい場合は、コントロールインターフェイスでイベントを待った時と同じような形で、struct pollfd の情報を取得し、イベントを待ちます。
そのため、直接 pollfd の revents の値をチェックせずに、snd_pcm_poll_descriptors_revents() を使って、起こったイベントを取得する必要があります。
snd_pcm_poll_descriptors_revents() で取得できる revents では、書き込みが可能になったら POLLOUT が、読み込みが可能になったら POLLIN がセットされます (常にどちらか1つ)。
revents が 0 になる場合もあるので、その時は何もせずに、もう一度 poll() を実行します。
//必要な pollfd の数を返す int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm); //PCM 用の pollfd の情報をセット int snd_pcm_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space); //poll 後、起こったイベントを取得 int snd_pcm_poll_descriptors_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
POLLIN/POLLOUT
PCM の pollfd 情報では、POLLIN や POLLOUT が、そのままの意味で、読み書きの方向を意味しない場合があります。そのため、直接 pollfd の revents の値をチェックせずに、snd_pcm_poll_descriptors_revents() を使って、起こったイベントを取得する必要があります。
snd_pcm_poll_descriptors_revents() で取得できる revents では、書き込みが可能になったら POLLOUT が、読み込みが可能になったら POLLIN がセットされます (常にどちらか1つ)。
revents が 0 になる場合もあるので、その時は何もせずに、もう一度 poll() を実行します。
プログラム
非ブロッキングモードで、poll() を使って、書き込みが可能になるまで待ちながら、1秒分再生します。
$ cc -o 15-nonblock 15-nonblock.c util.c -lasound
#include <stdio.h> #include <stdlib.h> #include <poll.h> #include <alsa/asoundlib.h> #include "util.h" #define SAMPRATE 48000 int poll_cnt; struct pollfd *poll_pfd; //書き込み可能になるまで待つ static void _wait_event(snd_pcm_t *pcm) { int ret; unsigned short ev; while(1) { ret = poll(poll_pfd, poll_cnt, -1); if(ret > 0) { if(snd_pcm_poll_descriptors_revents(pcm, poll_pfd, poll_cnt, &ev) == 0) { printf("event: %d\n", ev); if(ev & POLLOUT) return; } } } } static void _put_state(snd_pcm_t *pcm) { snd_pcm_state_t state; state = snd_pcm_state(pcm); printf("> %s\n", snd_pcm_state_name(state)); } int main(int argc,char **argv) { snd_pcm_t *pcm; snd_pcm_uframes_t bufs,period; uint8_t *buf; int ret,frames; if(snd_pcm_open(&pcm, (argc >= 2)? argv[1]: "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) { return 1; } //ハードウェア構成 (16bit LE, 2ch) snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, SAMPRATE, 1, 500 * 1000); snd_pcm_get_params(pcm, &bufs, &period); //poll poll_cnt = snd_pcm_poll_descriptors_count(pcm); poll_pfd = (struct pollfd *)malloc(sizeof(struct pollfd) * poll_cnt); snd_pcm_poll_descriptors(pcm, poll_pfd, poll_cnt); printf("poll cnt: %d\n", poll_cnt); //書き込み buf = (uint8_t *)malloc(SAMPRATE * 4); util_write_buf_sawtooth(buf, SAMPRATE, SAMPRATE, AUDIO_FREQ_DO); frames = 0; while(frames < SAMPRATE) { ret = snd_pcm_writei(pcm, buf + frames * 4, SAMPRATE - frames); if(ret >= 0) { printf("write: %d\n", ret); frames += ret; } else if(ret == -EAGAIN) { _wait_event(pcm); //snd_pcm_wait(pcm, SND_PCM_WAIT_IO); } else printf("error: %d\n", ret); } free(buf); //drain ret = snd_pcm_drain(pcm); printf("> drain: %d\n", ret); if(ret == -EAGAIN) { _put_state(pcm); ret = snd_pcm_wait(pcm, SND_PCM_WAIT_DRAIN); printf("> wait:%d\n", ret); _put_state(pcm); } // free(poll_pfd); snd_pcm_close(pcm); return 0; }
実行結果
poll cnt: 1 write: 16384 event: 0 event: 0 event: 4 write: 1736 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2056 event: 0 event: 4 write: 2040 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2056 event: 0 event: 4 write: 2040 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 2048 event: 0 event: 4 write: 1208 > drain: -11 > DRAINING > wait:1 > DRAINING
最初にバッファ全体の 16384 frames を書き込んだ後は、-EAGAIN が返るたびに、poll() で書き込み可能になるまで待ちます。
poll の代わりに、snd_pcm_wait(pcm, SND_PCM_WAIT_IO) を使うことも出来ます。
snd_pcm_poll_descriptors_revents() で取得したイベントが、0 になっている場合があることに、注意してください。
この場合、POLLOUT が来るまで、再度 poll() を実行する必要があります。
poll() で待ってから書き込みを行うことを続けて、1秒分のデータがすべて書き込めたら、drain で再生が終わるまで待ちます。
drain 後は、PCM が DRAINING の状態になっています。
drain では、実際には再生が終わるまで待つことはなく、戻り値で -EAGAIN が返ってくるので、snd_pcm_wait(pcm, SND_PCM_WAIT_DRAIN) で、実際に再生が終わるまで待ちます。
ただし、直後に PCM 状態を取得しても、DRAINING のままになっています。
書き込みが可能になった時に、wait が戻っているような気がします。
hw
"hw" で開いた場合は、drain 後、wait で -EIO (I/O Error) が返っています。
その後、SETUP 状態になっているので、再生を待つことは出来ているようです。
どうやら、ハードウェアと直接通信するかどうかで、drain を待つ動作が異なるようです。
ブロックモードの場合、drain 後は正常に SETUP 状態になるので、正常に drain を待ちたい場合は、ブロックモードに変更してから drain を行った方が良いでしょう。
その後、SETUP 状態になっているので、再生を待つことは出来ているようです。
$ ./15-nonblock hw poll cnt: 1 write: 24000 write: 48 event: 4 write: 12000 write: 8 event: 4 write: 11944 > drain: -11 > DRAINING > wait:-5 > SETUP
どうやら、ハードウェアと直接通信するかどうかで、drain を待つ動作が異なるようです。
ブロックモードの場合、drain 後は正常に SETUP 状態になるので、正常に drain を待ちたい場合は、ブロックモードに変更してから drain を行った方が良いでしょう。