ALSA:ソフトウェア構成

ソフトウェア構成
PCM には、再生/録音の実行中でも変更できる、ソフトウェア構成があります。

ハードウェア構成と同じように、snd_pcm_sw_params_t を確保した後、関数で値を設定し、構成をインストールします。

関数の詳細は、ソフトウェア構成 をご覧ください。
snd_pcm_sw_params_t
//サイズ取得
size_t snd_pcm_sw_params_sizeof(void);

//確保
int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr);

//解放
void snd_pcm_sw_params_free(snd_pcm_sw_params_t *obj);
構成のインストール
int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);

ソフトウェア構成をインストールします。
現在の構成の取得
int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);

現在のソフトウェア構成を取得します。
自動停止のしきい値をセット
int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm,
    snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);

バッファに読み書き可能なフレーム数が、このしきい値以上になった場合、PCM は SND_PCM_STATE_XRUN 状態となり、自動的に停止します。

この値が、snd_pcm_sw_params_get_boundary() で取得した境界位置と同じ場合は、自動停止せず、リングバッファ内で無限ループが行われます。
プログラム
ソフトウェア構成の現在の値を取得し、自動停止のしきい値を境界と同じ値にして、自動停止を無効にします。
バッファの長さ分のデータのみ書き込み、Enter キーが押されるまで待ちます。
その後、現在のステータスを出力して終了します。

$ cc -o 17-swparam 17-swparam.c util.c -lasound

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include "util.h"

#define SAMPRATE 48000

static void _sw_params(snd_pcm_t *pcm)
{
    snd_pcm_sw_params_t *sw;
    snd_pcm_uframes_t uframes,boundary;
    snd_pcm_tstamp_t tsmode;
    snd_pcm_tstamp_type_t tstype;
    int nval;

    snd_pcm_sw_params_malloc(&sw);

    //現在の値を取得

    snd_pcm_sw_params_current(pcm, sw);

    snd_pcm_sw_params_get_boundary(sw, &boundary);
    printf("boundary: %lu frames (0x%lX)\n", boundary, boundary);

    snd_pcm_sw_params_get_tstamp_mode(sw, &tsmode);
    printf("tstamp mode: %s\n", snd_pcm_tstamp_mode_name(tsmode));

    snd_pcm_sw_params_get_tstamp_type(sw, &tstype);
    printf("tstype: %d\n", tstype);

    snd_pcm_sw_params_get_avail_min(sw, &uframes);
    printf("avail min: %lu frames\n", uframes);

    snd_pcm_sw_params_get_period_event(sw, &nval);
    printf("period event: %d\n", nval);

    snd_pcm_sw_params_get_start_threshold(sw, &uframes);
    printf("start threshold: %lu\n", uframes);

    snd_pcm_sw_params_get_stop_threshold(sw, &uframes);
    printf("stop threshold: %lu\n", uframes);

    snd_pcm_sw_params_get_silence_threshold(sw, &uframes);
    printf("silence threshold: %lu\n", uframes);

    snd_pcm_sw_params_get_silence_size(sw, &uframes);
    printf("silence size: %lu\n", uframes);

    //セット

    snd_pcm_sw_params_set_tstamp_mode(pcm, sw, SND_PCM_TSTAMP_ENABLE);
    snd_pcm_sw_params_set_stop_threshold(pcm, sw, boundary);

    snd_pcm_sw_params(pcm, sw);

    //

    snd_pcm_sw_params_free(sw);
}

static void _put_ts(const char *name,snd_htimestamp_t *t)
{
    printf("%s: %ld sec %ld ns\n", name, t->tv_sec, t->tv_nsec);
}

static void _get_status(snd_pcm_t *pcm)
{
    snd_pcm_status_t *st;
    snd_htimestamp_t hts;
    snd_pcm_audio_tstamp_report_t rep;

    snd_pcm_status_malloc(&st);
    snd_pcm_status(pcm, st);

    printf("-- status --\n");
    printf("state: %s\n", snd_pcm_state_name(snd_pcm_status_get_state(st)));

    snd_pcm_status_get_trigger_htstamp(st, &hts);
    _put_ts("trigger", &hts);

    snd_pcm_status_get_htstamp(st, &hts);
    _put_ts("tstamp", &hts);

    snd_pcm_status_get_audio_htstamp(st, &hts);
    _put_ts("audio", &hts);

    snd_pcm_status_get_audio_htstamp_report(st, &rep);
    printf("audio report: type(%d) report(%d) accuracy(%d)\n",
        rep.actual_type, rep.accuracy_report, rep.accuracy);

    snd_pcm_status_get_driver_htstamp(st, &hts);
    _put_ts("driver", &hts);

    printf("delay: %ld\n", snd_pcm_status_get_delay(st));
    printf("avail: %lu\n", snd_pcm_status_get_avail(st));
    printf("overrange: %lu\n", snd_pcm_status_get_overrange(st));

    snd_pcm_status_free(st);
}

int main(int argc,char **argv)
{
    snd_pcm_t *pcm;
    snd_pcm_uframes_t bufs,periods;
    uint8_t *buf;

    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);

    //ソフトウェア構成

    _sw_params(pcm);

    //バッファの長さ分書き込み

    buf = (uint8_t *)malloc(bufs * 4);

    util_write_buf_sawtooth(buf, bufs, SAMPRATE, AUDIO_FREQ_DO);
    snd_pcm_writei(pcm, buf, bufs);

    free(buf);

    //Enter が押されるまで待つ

    getchar();

    _get_status(pcm);

    //

    snd_pcm_drop(pcm);
    
    snd_pcm_close(pcm);

    return 0;
}
実行結果
boundary: 4611686018427387904 frames (0x4000000000000000)
tstamp mode: NONE
tstype: 1
avail min: 1024 frames
period event: 0
start threshold: 16384
stop threshold: 16384
silence threshold: 0
silence size: 0

-- status --
state: RUNNING
trigger: 14144 sec 873074695 ns
tstamp: 14146 sec 434646671 ns
audio: 1 sec 565817458 ns
audio report: type(0) report(0) accuracy(0)
driver: 14146 sec 434650490 ns
delay: -58608
avail: 74992
overrange: 0
ソフトウェア構成
  • boundary は、リングバッファの境界位置、つまり、リングバッファの長さとなります。
    見る限り、とても大きな値になっています。
  • avail min は、poll() などで読み書きが可能になるまで待つ時の、しきい値となります。
    このフレーム数以上が読み書き可能であれば、イベントが起きます。
  • start threshold は、自動開始のしきい値です。
    PCM が開始されていない状態で、リングバッファ内に指定値以上のフレーム数のデータが存在すれば、自動的に PCM を開始します。
    ここでは、バッファの長さと同じになっています。
  • stop threshold は、自動停止のしきい値です。
    ここも、バッファの長さと同じになっています。
    つまり、バッファがすべて空の状態になったら、自動停止します。
動作
自動停止のしきい値を、boundary と同じ値にセットして、自動停止を無効にします。

その後、バッファと同じ長さのデータを書き込み、Enter キーが押されるまで待ちます。
再生自体は、バッファの長さ分が終わると、無音になります。

無音状態になった後に Enter キーを押し、現在のステータスを出力します。
ステータス
高解像度のタイムスタンプである snd_htimestamp_t 型は、struct timespec と同じです。
tv_sec が秒単位、tv_nsec がナノ秒単位の値となります。

ソフトウェア構成で、タイムスタンプモードが NONE になっています。
"default" の場合は、この状態でもタイムスタンプは取得できるようですが、ハードウェアと直接通信する PCM で、タイムスタンプを取得したい場合は、タイムスタンプモードのセットが必要になります。

  • state は、RUNNING 状態です。
    通常であればアンダーランの状態ですが、自動停止を無効にしているので、実行状態のままになっています。
  • trigger は、トリガー時 (PCM が開始または停止した時) のタイムスタンプです。
    14144 sec 873074695 ns となっていますが、これは、ALSA が起動した時、つまり PC が起動してから経過した時間となっています。
  • tstamp は、現在のタイムスタンプです。
  • audio は、オーディオのタイムスタンプです。
    1 sec 565817458 ns となっているので、再生を開始してからの時間となっています。
  • driver は、ドライバによって、タイムスタンプが生成された時間です。
    tstamp よりも少し後の時間になっています。
  • delay は、書き込み済みの終端位置から、再生位置を引いた値 (フレーム単位) になります。
    バッファにある、未再生のデータのフレーム数です。
    現在はアンダーラン状態なので、負の値になっています。
  • avail は、バッファに読み書きできるフレーム数です。
    avail + delay = 74992 - 58608 = 16384 になり、ちょうどバッファの長さと一致します。
    つまり、avail の値は、bufsize - delay で計算されています。
リンクバッファ
書き込んだデータの後は、再生が無音になることと、boundary が大きな値であることから考えると、ハードウェア構成で設定されたバッファの長さと、実際のリングバッファの長さは、異なることがわかります。

ハードウェア構成のバッファの長さは、読み書きのためのバッファの長さであって、サウンドのリンクバッファは、ほぼ無限の長さであると考えることができます。
(実際にそれだけのバッファが確保されているわけではなく、表面上はそうなるように実装されている、ということだと思われます)

バッファの長さ分を書き込んでも、そのデータで無限ループになるわけではありません。