言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

ALSA PCM プログラミングのサウンドバッファ


Linuxサウンド関係のプログラムを開発する際に必須になる ALSA のバッファについて書きます。単にゲームを作る程度であれば互換性や利便性を考えて SDL 等の上位のライブラリを使うべきだと思いますが、特にライブ音源やボイスチャット等、タイミングが要求される物を開発する際に必要になる知識です。

ALSA は様々なサウンドカードを同じ方法で扱う手段を提供しています。その一番重要な構造はサウンドバッファです。アナログと違って、デジタル音源の音声信号の伝播は単なるデータのコピーですので、ある纏まった長さのメモリを用意しておきアプリケーションとサウンドカード間の音声の受け渡しに使います。このメモリはエンドレステープのように端っこどうしが繋がっているのでリングバッファと呼びます。再生も録音も構造は同じですが、再生についてだけ書きます。

リングバッファには二つの重要な位置があります。

  • 再生位置 : デバイスがこれから音声を再生しようという位置を示します。
  • 転送位置 : アプリケーションがこれから音声を書き込む位置を示します。

サウンドを出力する際には、この二つのポインタが追いかけっこをしている状態を思い浮かべて下さい。アプリケーションがどんどん再生位置に音声を書き込む一方、デバイスがどんどん再生してゆきます。遅延の無いサウンド出力のキモは、この追いかけっこを出来るだけスリリングに保つ事です。つまり、あまり沢山あらかじめ音声を書き込むとなかなかデバイスが追いつかなくて遅延が長くなります。逆にギリギリまで書かないとデバイスが追い越し音が途切れてしまいます。今まさにデバイスが追いつく瞬間に次のデータを渡すというのが理想的な状態です。

ALSA では、これらを扱うのに割りと独特の単語を使います。

  • buffer とは、リングバッファ全体の事です。
  • periods size とは、一度に書込み可能な区間の長さです。
  • avail (snd_pcm_avail_update() で取得) は、空いたバッファ全体です。
  • delay (snd_pcm_delay() で取得)とは、まだ再生してない音声データの長さです。
  • つまり、avail + delay = buffer になります。
  • バッファの長さを現すのに、色々な方法があります。
    • frames とは、サンプルの数です。
    • bytes とは、そのままバイト数です。16ビットステレオなら 4 bytes = 1 frames
    • 時間で表現する事もあります。
  • XRUN とは、タイミングが合わない二つの状態を纏めて言う言い方です。
    • underrun とは、再生位置が追い越して再生音が途切れる事です。
    • overrun とは、録音位置が追い越して未処理の録音データを上書きする事です。

つまり、delay を最小化する方向で書けば良いという結論です。

と、こう書くとなんて事の無い知識ですが、API ドキュメントを読んでこれをイメージするのは至難の業でした。特に、サンプルの pcm.c では出来るだけ沢山バッファに書き込んでゆったり遅延を取る方向で書かれてありますので、これに影響されるとのんびりしたプログラムになってしまいます。また、ALSA は未だにどんどん関数が増えていますので、サンプルを入手するためにライブラリのソースコードをダウンロードする際は自分の Linux にインストールされている物 ($ cat /proc/asound/version) とぴったりバージョンを合わせる必要があります。