ALSA:音量の変更

音量の変更
音量の値を取得できたので、今回は、要素の値を変更することで、音量を変更してみます。

前回は TLV データを読み込んで、そこから音量の dB 情報を取得しました。
しかし、音量を変更する場合、TLV で書き込むことはしません。

音量が持っている TLV データは、あくまで dB 値と生の音量値を変換するための補足情報であって、読み込みのみが可能になっているので、書き込むことはできません。

音量を変更する場合は、要素の生の数値で直接指定するか、TLV データで dB 値から生の音量値に変換した値を使います。
それを、要素の新しい値として書き込みます。
要素に値を書き込む
int snd_ctl_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *data);

要素の値を変更する場合、snd_ctl_elem_value_t に、変更したい要素の識別子と、要素の値をセットして、snd_ctl_elem_write() を実行します。

戻り値は、0 で成功。
正の値で、値が変更された時に成功。
負の値で、エラーコードです。

要素の値をセットする関数は、要素:値 を参照してください。
dB 関連関数
なお、生の音量値と dB 値を変換するのに、いちいち TLV の読み込みを行うのは面倒なので、内部で TLV の読み込みと変換を行う、ラッパー関数が存在します。

//dB の最小値と最大値を取得
int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, long *min, long *max);

//生の音量値を dB 値に変換
int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, long volume, long *db_gain);

//dB 値を生の音量値に変換
int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
    long db_gain, long *value, int xdir);

ただし、dB の範囲を取得した上で、値の変換が必要な場合は、複数回同じ TLV の読み込みを行うことになるので、多少非効率にはなります。
プログラム
"Master Playback Volume" の音量を、dB 範囲の中間位置に変更します。

$ cc -o 06-volume 06-volume.c -lasound

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

int main(int argc,char **argv)
{
    int ret;
    snd_ctl_t *ctl;
    snd_ctl_elem_list_t *list;
    snd_ctl_elem_value_t *value;
    snd_ctl_elem_id_t *eid;
    uint32_t i,cnt;
    long vol,min,max;

    if(snd_ctl_open(&ctl, (argc >= 2)? argv[1]: "default", 0))
        return 1;

    //開く

    snprintf(m, 16, "hw:%d", card);

    if(snd_ctl_open(&ctl, m, 0)) return 1;

    //リスト取得

    snd_ctl_elem_list_malloc(&list);

    snd_ctl_elem_list(ctl, list);

    cnt = snd_ctl_elem_list_get_count(list);

    snd_ctl_elem_list_alloc_space(list, cnt);
    snd_ctl_elem_list(ctl, list);

    //リスト

    snd_ctl_elem_value_malloc(&value);
    snd_ctl_elem_id_malloc(&eid);

    for(i = 0; i < cnt; i++)
    {
        snd_ctl_elem_list_get_id(list, i, eid);

        if(strcmp(snd_ctl_elem_id_get_name(eid), "Master Playback Volume") == 0)
        {
            //dB 範囲の中間位置
            snd_ctl_get_dB_range(ctl, eid, &min, &max);
            snd_ctl_convert_from_dB(ctl, eid, (max - min) / 2 + min, &vol, 0);

            //書き込み
            snd_ctl_elem_value_set_id(value, eid);
            snd_ctl_elem_value_set_integer(value, 0, vol);

            ret = snd_ctl_elem_write(ctl, value);
            printf("value:%ld, ret:%d\n", vol, ret);
        }
    }

    snd_ctl_elem_id_free(eid);
    snd_ctl_elem_value_free(value);

    //

    snd_ctl_elem_list_free_space(list);
    snd_ctl_elem_list_free(list);

    snd_ctl_close(ctl);

    return 0;
}
解説
value:32, ret:0

dB 範囲が min = -6400, max = 0 の場合、中間位置は -3200 です。
※dB 値は、0.01 dB 単位の整数。

snd_ctl_elem_value_t に、要素の識別子 (snd_ctl_elem_id_t) と、数値の値をセットして、snd_ctl_elem_write() で書き込みます。

alsamixer で確認してみると、dB は -32.0 で、音量値 (0〜100) は 23 でした。

dB は、単純な線形グラフにはならないので、dB 範囲の中間位置は、実際は 50% より下の位置になります。

書き込まれた生の値は、0〜64 の範囲の中での 32 なので、中間位置となっています。
dB 範囲と生の値は、線形の関係にあるようです。