アンダーラン
アンダーランは、再生時に書き込みが追いつかず、再生がデータの終端に達してしまった時に起こります。
その場合、デフォルトでは、自動的に再生が停止され、SND_PCM_STATE_XRUN 状態になります。
XRUN は、アンダーラン (再生) とオーバーラン (録音) の2つを示します。〜RUN という意味です。
データを読み書きしようとした際に、戻り値で -EPIPE が返った場合は、XRUN 状態なので、エラー状態を回復させる必要があります。
PCM のエラーを回復し、バッファへの読み書きが出来るようにします。
この関数は、以下のエラーを回復します。
読み書きなどの時に -ESTRPIPE が返った場合は、snd_pcm_resume() を使って、サスペンドを回復します。
snd_pcm_recover() で -ESTRPIPE を渡した場合は、内部でこの関数が実行されます。
※resume は、すべてのハードウェアでサポートされているとは限らないので、エラーが返った場合は、snd_pcm_prepare() を行って、再度 PCM の準備を行う必要があります。
この関数で -EAGAIN が返った場合は、まだサスペンド状態なので、しばらく待つ必要があります。
その場合、デフォルトでは、自動的に再生が停止され、SND_PCM_STATE_XRUN 状態になります。
XRUN は、アンダーラン (再生) とオーバーラン (録音) の2つを示します。〜RUN という意味です。
データを読み書きしようとした際に、戻り値で -EPIPE が返った場合は、XRUN 状態なので、エラー状態を回復させる必要があります。
エラーの回復
int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent);
PCM のエラーを回復し、バッファへの読み書きが出来るようにします。
err | 実際に起きたエラーのコード |
---|---|
silent | 0 で、エラーの内容を出力する。 0 以外で出力しない。 |
戻り値 | 正常に処理された場合は 0。 それ以外は、負のエラーコード。 -EAGAIN など、この関数で処理できないエラーの場合は、元のエラーコードが返る。 |
この関数は、以下のエラーを回復します。
- -EINTR: システムコールで中断した。
ただし、実際は何もせずに、0 を返すだけです。 - -EPIPE: オーバーランまたはアンダーラン。
内部では、snd_pcm_prepare() が実行されます。 - -ESTRPIPE: サスペンド状態。
サスペンドとは、電源管理によって、ハードウェアが停止している状態です。
この場合、PCM は SND_PCM_STATE_SUSPENDED 状態になります。
内部では、snd_pcm_resume() が実行されます。
サスペンドの回復
int snd_pcm_resume(snd_pcm_t *pcm);
読み書きなどの時に -ESTRPIPE が返った場合は、snd_pcm_resume() を使って、サスペンドを回復します。
snd_pcm_recover() で -ESTRPIPE を渡した場合は、内部でこの関数が実行されます。
※resume は、すべてのハードウェアでサポートされているとは限らないので、エラーが返った場合は、snd_pcm_prepare() を行って、再度 PCM の準備を行う必要があります。
この関数で -EAGAIN が返った場合は、まだサスペンド状態なので、しばらく待つ必要があります。
エラー処理
エラーの処理をまとめると、以下のようになります。
if(ret == -EPIPE) snd_pcm_prepare(pcm); //snd_pcm_recover(pcm, ret, 1) でも同じ else if(ret == -ESTRPIPE) { while(1) { ret = snd_pcm_resume(pcm); if(ret != -EAGAIN) break; //待つ } if(ret < 0) snd_pcm_prepare(pcm); }
プログラム
0.5 秒分の 'ド' の音を書き込んだ後、sleep() を使って、意図的にアンダーランを起こし、snd_pcm_recover() で回復させた後、0.5 秒分の 'レ' の音を書き込みます。
$ cc -o 16-xrun 16-xrun.c util.c -lasound
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <alsa/asoundlib.h> #include "util.h" #define SAMPRATE 48000 int main(int argc,char **argv) { snd_pcm_t *pcm; snd_pcm_uframes_t bufs,periods; uint8_t *buf; int ret,buf_frames; if(snd_pcm_open(&pcm, (argc >= 2)? argv[1]: "default", SND_PCM_STREAM_PLAYBACK, 0)) { 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, &periods); // buf_frames = SAMPRATE / 2; buf = (uint8_t *)malloc(buf_frames * 4); //ド util_write_buf_sawtooth(buf, buf_frames, SAMPRATE, AUDIO_FREQ_DO); snd_pcm_writei(pcm, buf, buf_frames); printf("> sleep\n"); sleep(1); //レ util_write_buf_sawtooth(buf, buf_frames, SAMPRATE, AUDIO_FREQ_RE); ret = snd_pcm_writei(pcm, buf, buf_frames); printf("write: %d\n", ret); printf("> %s\n", snd_pcm_state_name(snd_pcm_state(pcm))); if(ret == -EPIPE) { ret = snd_pcm_recover(pcm, ret, 0); printf("recover: %d\n", ret); snd_pcm_writei(pcm, buf, buf_frames); } free(buf); // snd_pcm_drain(pcm); snd_pcm_close(pcm); return 0; }
実行結果
> sleep write: -32 > XRUN ALSA lib pcm.c:8772:(snd_pcm_recover) underrun occurred recover: 0
0.5 秒分の 'ド' を書き込んだ後、1秒スリープします。
スリープの間に、アンダーランが発生します。
その後、0.5 秒分の 'レ' を書き込もうとすると、-32 (-EPIPE) が返ります。
PCM 状態を調べると、XRUN になっています。
snd_pcm_recover() を実行して、エラー状態を回復させます。
この時、silent 引数を 0 にしているので、"アンダーランが発生した" というエラーの内容が出力されています。
回復に成功したら、再び書き込みを行います。