ALSA:アンダーラン

アンダーラン
アンダーランは、再生時に書き込みが追いつかず、再生がデータの終端に達してしまった時に起こります。

その場合、デフォルトでは、自動的に再生が停止され、SND_PCM_STATE_XRUN 状態になります。

XRUN は、アンダーラン (再生) とオーバーラン (録音) の2つを示します。〜RUN という意味です。

データを読み書きしようとした際に、戻り値で -EPIPE が返った場合は、XRUN 状態なので、エラー状態を回復させる必要があります。
エラーの回復
int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent);

PCM のエラーを回復し、バッファへの読み書きが出来るようにします。

err実際に起きたエラーのコード
silent0 で、エラーの内容を出力する。
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 にしているので、"アンダーランが発生した" というエラーの内容が出力されています。

回復に成功したら、再び書き込みを行います。