ALSA:PCM ハードウェア構成 (2)

ハードウェア構成のセット
ハードウェア構成に対して、各項目ごとのセット関数を使うと、一つの値または複数の値をセット(制限)できます。

ハードウェア構成内に、指定した単一の値のみをセットしたい場合は、以下のような関数を使います。

//アクセスタイプをセット
int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
    snd_pcm_access_t access);

//フォーマットをセット
int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
    snd_pcm_format_t format);

//サブフォーマットをセット
int snd_pcm_hw_params_set_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
    snd_pcm_subformat_t subformat);

ただし、その PCM で利用できない値はセットできないので、その場合は負のエラーコードが返ります。

関数の数が多いので、詳しくは以下をご覧ください。

ハードウェア構成(1)
ハードウェア構成(2)
ハードウェア構成(3) - ピリオド
ハードウェア構成(4) - バッファ

ほとんどの項目では、有効な値を列挙できないので、指定した値に近いものだけを残したり、最小値を指定して、それ未満の値を除外したり、最大値を指定して、それを超える値を除外したりするなどして、ハードウェア構成内の値を制限します。
ハードウェア構成のインストール
ハードウェア構成が準備できたら、PCM にインストールします。

int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);

params で設定されている項目の、「最初のアクセスタイプ、最初のフォーマット、最初のサブフォーマット、最小のチャンネル数、最小のサンプルレート、最小のピリオド時間、最大のバッファサイズ」を選択して、params を一つの値に更新し、構成をインストールします。
ハードウェア構成の削除
int snd_pcm_hw_free(snd_pcm_t *pcm);

現在のハードウェア構成を削除し、リソースを解放します。
プログラム
「16bit LE、48000 Hz (に近い値)、最小のチャンネル数、他はデフォルト」でハードウェア構成をインストールした後、実際のハードウェア構成の情報を出力します。

$ cc -o 11-hwparam2 11-hwparam2.c -lasound

#include <stdio.h>
#include <alsa/asoundlib.h>

int main(int argc,char **argv)
{
    snd_pcm_t *pcm;
    snd_pcm_hw_params_t *hw;
    snd_pcm_access_t access;
    snd_pcm_format_t format;
    snd_pcm_subformat_t subformat;
    unsigned int num,den,val;
    int dir;
    snd_pcm_uframes_t frames;

    if(snd_pcm_open(&pcm,
        (argc >= 2)? argv[1]: "default", SND_PCM_STREAM_PLAYBACK, 0))
    {
        printf("open error\n");
        return 1;
    }

    snd_pcm_hw_params_malloc(&hw);

    snd_pcm_hw_params_any(pcm, hw);

    //16bit LE

    snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S16_LE);

    //48000 Hz に近い値

    val = 48000;
    dir = 0;
    snd_pcm_hw_params_set_rate_near(pcm, hw, &val, &dir);

    snd_pcm_hw_params(pcm, hw);

    //

    snd_pcm_hw_params_get_access(hw, &access);
    printf("access: %s\n", snd_pcm_access_name(access));

    snd_pcm_hw_params_get_format(hw, &format);
    printf("format: %s\n", snd_pcm_format_name(format));

    snd_pcm_hw_params_get_subformat(hw, &subformat);
    printf("subformat: %s\n", snd_pcm_subformat_name(subformat));

    snd_pcm_hw_params_get_rate_numden(hw, &num, &den);
    printf("rate: %u/%u\n", num, den);

    snd_pcm_hw_params_get_channels(hw, &val);
    printf("channels: %u\n", val);

    printf("sbits: %d\n", snd_pcm_hw_params_get_sbits(hw));
    printf("FIFO size: %d\n", snd_pcm_hw_params_get_fifo_size(hw));

    snd_pcm_hw_params_get_min_align(hw, &frames);
    printf("min align: %lu frames\n", frames);

    //

    snd_pcm_hw_params_get_period_time(hw, &val, &dir);
    printf("\nperiod time: %u us, dir:%d\n", val, dir);

    snd_pcm_hw_params_get_period_size(hw, &frames, &dir);
    printf("period size: %lu frames, dir:%d\n", frames, dir);

    snd_pcm_hw_params_get_periods(hw, &val, &dir);
    printf("periods: %u, dir:%d\n", val, dir);

    snd_pcm_hw_params_get_buffer_time(hw, &val, &dir);
    printf("buffer time: %u us, dir:%d\n", val, dir);

    snd_pcm_hw_params_get_buffer_size(hw, &frames);
    printf("buffer size: %lu frames\n", frames);

    //

    printf("\ncan_mmap_sample_resolution: %d\n",
        snd_pcm_hw_params_can_mmap_sample_resolution(hw));
    printf("is_double: %d\n", snd_pcm_hw_params_is_double(hw));
    printf("is_batch: %d\n", snd_pcm_hw_params_is_batch(hw));
    printf("is_block_transfer: %d\n", snd_pcm_hw_params_is_block_transfer(hw));
    printf("is_monotonic: %d\n", snd_pcm_hw_params_is_monotonic(hw));
    printf("can_overrange: %d\n", snd_pcm_hw_params_can_overrange(hw));
    printf("can_pause: %d\n", snd_pcm_hw_params_can_pause(hw));
    printf("can_resume: %d\n", snd_pcm_hw_params_can_resume(hw));
    printf("is_half_duplex: %d\n", snd_pcm_hw_params_is_half_duplex(hw));
    printf("is_joint_duplex: %d\n", snd_pcm_hw_params_is_joint_duplex(hw));
    printf("can_sync_start: %d\n", snd_pcm_hw_params_can_sync_start(hw));
    printf("can_disable_period_wakeup: %d\n", snd_pcm_hw_params_can_disable_period_wakeup(hw));
    printf("is_perfect_drain: %d\n", snd_pcm_hw_params_is_perfect_drain(hw));
    printf("supports_audio_wallclock_ts: %d\n",
        snd_pcm_hw_params_supports_audio_wallclock_ts(hw));

    //
    
    snd_pcm_hw_params_free(hw);
    
    snd_pcm_close(pcm);

    return 0;
}
実行結果
access: MMAP_INTERLEAVED
format: S16_LE
subformat: STD
rate: 48000/1
channels: 1
sbits: 16
FIFO size: 0
min align: 1 frames

period time: 21333 us, dir:1
period size: 1024 frames, dir:0
periods: 16, dir:0
buffer time: 341333 us, dir:1
buffer size: 16384 frames

can_mmap_sample_resolution: 0
is_double: 0
is_batch: 0
is_block_transfer: 1
is_monotonic: 1
can_overrange: 0
can_pause: 0
can_resume: 0
is_half_duplex: 0
is_joint_duplex: 0
can_sync_start: 1
can_disable_period_wakeup: 1
is_perfect_drain: 0
supports_audio_wallclock_ts: 1

アクセスタイプは MMAP_INTERLEAVED です。
前回の出力時、最初のタイプは MMAP_INTERLEAVED だったので、最初のアクセスタイプが選択されています。

サブフォーマットも同様です。

サンプルレートは、48000 Hz になっています。
チャンネル数は、一番小さいチャンネル数が選択されるので、1 (モノラル) です。

ピリオドの詳細は後述しますが、サウンドバッファは小さい塊の単位に分割され、その1単位をピリオドと呼びます。
バッファは、ピリオド単位のサイズになります。

ピリオド
ピリオドの長さは、「マイクロ秒単位の時間」「サンプルのフレーム単位」のいずれかで指定できます。

period time は 21333 us、period size は 1024 frames です。

48000 Hz における 1024 フレームを、以下の式で時間に変換すると、21333.333... マイクロ秒となり、period time の値と一致します。

time(us) = frames / samprate(Hz) * 1000 * 1000

小数点以下の値があるので、period time の dir は 1 になっています。
dir は、計算の結果、小数点以下の値がある場合、実際の正確な値が、返った整数値のどの方向にあるかという値です (-1, 0, 1)。

21333.333 が 21333 に切り捨てられて返っているので、実際の正確な値は、正 (1) の方向にあります。

バッファ
ALSA のサウンドバッファは、リングバッファになっています。

ハードウェア構成で指定するバッファのサイズは、リングバッファの長さではなく、読み書きに使用するためのバッファのサイズとなります。

バッファのサイズは、「ピリオドの数」「マイクロ秒単位の時間」「サンプルのフレーム単位」のいずれかで指定できます。
ただし、実際はピリオド単位のサイズになります。

periods は 16 なので、ちょうどピリオド 16 個分の長さになっています。

ピリオドのフレーム数は 1024 なので、1024 * 16 = 16384 で、buffer size のフレーム数の値と一致します。

バッファの長さを時間に変換すると、16384/48000*1000*1000 = 341333.333... us で、buffer time の値と一致します。
HDMI ほか
$ ./11-hwparam2 hdmi

access: MMAP_INTERLEAVED
format: S16_LE
subformat: STD
rate: 48000/1
channels: 2
sbits: 16
FIFO size: 0
min align: 1 frames

period time: 682666 us, dir:1
period size: 32768 frames, dir:0
periods: 32, dir:0
buffer time: 21845333 us, dir:1
buffer size: 1048576 frames

can_mmap_sample_resolution: 1
is_double: 0
is_batch: 0
is_block_transfer: 1
is_monotonic: 1
can_overrange: 0
can_pause: 1
can_resume: 0
is_half_duplex: 0
is_joint_duplex: 0
can_sync_start: 1
can_disable_period_wakeup: 1
is_perfect_drain: 0
supports_audio_wallclock_ts: 1

HDMI の場合も、結果はほぼ同じですが、ピリオド・バッファサイズは default よりもかなり大きくなっています。
"front" や "hw" でも同じ結果となります。

"default" の場合は、サンプルの変換が有効なので、ソフトウェア的な関与がありますが、"hdmi" などはハードウェアと直接通信する形なので、若干値が異なります。

ここでは、以下の値が 1 になっています。
can_mmap_sample_resolution
can_pause

can_pause が 1 の場合は、再生の一時停止/再開がサポートされています。
"default" の場合は 0 なので、再生の一時停止はできません。