ALSAの作法とか

自分で試してみた限りのことを書きます。

snd_pcm_openの引数に"hw:0,0"を渡す例が多くみられるがこれはデバイスを独占してしまうためお行儀がよろしくないと思われる。そこで出力先は"default"やミキサーの"plug:dmix"にするとよい。これでほかのプロセスからも音声を出力できる。"default"はPulseAudioにフォールバックしているようなので一番好ましいかもしれない。
そしてさらに出力先をコントロールしたい場合は"/home/USERNAME/.asoundrc"や "/etc/asound.conf"などの設定ファイルで予め適当な出力先を作っておき、snd_pcm_openに"plug:***"の形で指定するのが良い。

またhw:0,0の場合periodを指定しないと音がプツプツになる。snd_pcm_hw_params_set_periodsを使用して設定できるが値は8,16,32以外は音がおかしくなった。plug:dmixならperiodを指定せずとも滑らかに再生された。

snd_pcm_hw_params_set_channelsでモノラルのデータを使用しようと1を指定すると失敗した。(snd_pcm_openにhwを指定した時なのでplug:dmixではどうなのかわからないが)ステレオを指定するのが無難だと思う。.asoundrcなどの側でパラメータをいじれるので適当な出力先を作るのが良いと思われる。

snd_pcm_availを使用してバッファの空きを確認できる。
snd_pcm_writeiでデータを書き込む。snd_pcm_writeiはデフォルトでデータの再生完了を待たずに制御を返す。snd_pcm_avail, snd_pcm_writeiは両方ともアンダーラン発生時に-EPIPEエラーを返すのでsnd_pcm_recoverを呼んで復帰させる必要がある。これがないとかなり信頼性に欠ける。
データの再生完了まで待つにはsnd_pcm_drainを呼び出す。ちなみにsnd_pcm_drainはsnd_pcm_openにSND_PCM_NONBLOCKを指定すると待機せずに-EAGAINを返して素通りする。SND_PCM_NONBLOCKを使用するメリットは感じられなかった。

まあLinuxなのでsystem関数からaplayに丸投げするのが簡単なのかもしれない。

ここまでALSAAPIについて書いてきてあれだが、PulseAudioを使った方が遥かに柔軟で簡単で適切だ。Simple APIとやらも用意されていて同期再生するだけならこれで十分。

#include <pulse/simple.h>
#include <iostream>

int play(const void* buffer, int size)
{
    pa_sample_spec pas{};
    pas.format = PA_SAMPLE_S16NE;
    pas.channels = 2;
    pas.rate = 44100;

    int err;
    pa_simple* handle = pa_simple_new(NULL, "test", PA_STREAM_PLAYBACK, NULL, "tes", &pas, NULL, NULL, &err);
    if (!handle) {
        cout << "open error" << endl;
    }

    int n;
    if ((n = pa_simple_write(handle, buffer, size, &err)) < 0) {
        cout << "write error" << endl;
    }

    pa_simple_drain(handle, &err);
    pa_simple_free(handle);
}