Pipewire環境でalsa-card-profileをホームディレクトリに置く
Pipewire環境でalsa-card-profileをホームディレクトリに置く
BluetoothヘッドホンでLDACを使うためにpulseaudio-modules-btを使っていたが、開発終了がアナウンスされたためPipewireに乗り換えることにした。移ってみるとネイティブでLDACをサポートしているし使い勝手もよい。
だが、スピーカーは大音量で鳴る。
そういうわけで音量に制限をかける方法をまとめる。
alsa-card-profileを編集する
PipewireもPulseaudioと同様にalsa-card-profileを読み込む。なのでこのファイルでvolume-limitを設定すればPulseaudio同様に音量にリミットがかけられる。
ファイルのありかはPulseaudioとは変わって /usr/share/alsa-card-profile/mixer/paths となる(もともとはPulseaudioが使っていたパスだが)。前回同様に analog-output.conf.common を編集する。
[Element PCM]
switch = mute
; volume = merge
volume-limit = 1
override-map.1 = all
override-map.2 = all-left,all-right終わったらPipewireを再起動する。
ホームディレクトリにalsa-card-profileを置く
上記の方法では /usr 以下のファイルを編集するため、当然ながら以下のような問題がある。
- 管理者でないと変更できない
- 全ユーザーに反映される
- パッケージが更新されるたびに手動で直す必要がある
結論から述べると、PipewireもPulseaudioと同様にホームディレクトリ(正しくは任意のディレクトリ)からalsa-card-profileを読み込める。必要な手続きは以下のとおり。
- ホームディレクトリにalsa-card-profileをコピーする
- alsa-card-profileを編集する
ACP_PATHS_DIR環境変数で読み込み先( /usr/share/alsa-card-profile/mixer/paths に相当するパス)を指定する
これだけ。
$ mkdir -p ~/.config/pipewire/alsa-card-profile/mixer/paths
$ cp /usr/share/alsa-card-profile/alsa-card-profile/mixer/paths/* ~/.config/pipewire/alsa-card-profile/mixer/paths/
#編集
$ vim ~/.config/pipewire/alsa-card-profile/mixer/paths/analog-output.conf.common
$ echo "export ACP_PATHS_DIR=~/.config/pipewire/alsa-card-profile/mixer/paths" >> ~/.xprofileログインしなおせば環境変数のセットとpipewireの再起動が行われる。
調査
環境変数ACP_PATHS_DIRはドキュメント化されていない。見つけた方法をまとめておく。
最初はそもそもpipewireがvolume-limitを解釈できるのかという疑問からはじまった。pipewireのソースで
volume-limit をgrepしてみる。
static int element_parse_volume_limit(pa_config_parser_state *state) {
pa_alsa_path *p;
pa_alsa_element *e;
long volume_limit;
pa_assert(state);
p = state->userdata;
if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] volume-limit makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
if (pa_atol(state->rvalue, &volume_limit) < 0 || volume_limit < 0) {
pa_log("[%s:%u] Invalid value for volume-limit", state->filename, state->lineno);
return -1;
}
e->volume_limit = volume_limit;
return 0;
}こんな関数がみつかる。volume-limitを読む機能はちゃんとある。呼び出し元を探すとこんなのが見つかる。
pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction) {
pa_alsa_path *p;
char *fn;
int r;
const char *n;
bool mute_during_activation = false;
pa_config_item items[] = {
/* [General] */
{ "priority", pa_config_parse_unsigned, NULL, "General" },
{ "description-key", pa_config_parse_string, NULL, "General" },
{ "description", pa_config_parse_string, NULL, "General" },
{ "mute-during-activation", pa_config_parse_bool, NULL, "General" },
{ "type", parse_type, NULL, "General" },
{ "eld-device", parse_eld_device, NULL, "General" },
/* [Option ...] */
{ "priority", option_parse_priority, NULL, NULL },
{ "name", option_parse_name, NULL, NULL },
/* [Jack ...] */
{ "state.plugged", jack_parse_state, NULL, NULL },
{ "state.unplugged", jack_parse_state, NULL, NULL },
{ "append-pcm-to-name", jack_parse_append_pcm_to_name, NULL, NULL },
/* [Element ...] */
{ "switch", element_parse_switch, NULL, NULL },
{ "volume", element_parse_volume, NULL, NULL },
{ "enumeration", element_parse_enumeration, NULL, NULL },
{ "override-map.1", element_parse_override_map, NULL, NULL },
{ "override-map.2", element_parse_override_map, NULL, NULL },
{ "override-map.3", element_parse_override_map, NULL, NULL },
{ "override-map.4", element_parse_override_map, NULL, NULL },
{ "override-map.5", element_parse_override_map, NULL, NULL },
{ "override-map.6", element_parse_override_map, NULL, NULL },
{ "override-map.7", element_parse_override_map, NULL, NULL },
{ "override-map.8", element_parse_override_map, NULL, NULL },
#if POSITION_MASK_CHANNELS > 8
#error "Add override-map.9+ definitions"
#endif
/* ... later on we might add override-map.3 and so on here ... */
{ "required", element_parse_required, NULL, NULL },
{ "required-any", element_parse_required, NULL, NULL },
{ "required-absent", element_parse_required, NULL, NULL },
{ "direction", element_parse_direction, NULL, NULL },
{ "direction-try-other", element_parse_direction_try_other, NULL, NULL },
{ "volume-limit", element_parse_volume_limit, NULL, NULL },
{ NULL, NULL, NULL, NULL }
};こんなのがみつかる。はあはあ、設定項目名に対して関数のポインタをテーブルにしているらしい。引数にpaths_dirなんてのがあるからここから読むのだろう。続きを見るとこうなっている。
if (!paths_dir)
paths_dir = get_default_paths_dir();get_default_paths_dir()はこう。
static const char *get_default_paths_dir(void) {
const char *str;
#ifdef HAVE_RUNNING_FROM_BUILD_TREE
if (pa_run_from_build_tree())
return PA_SRCDIR "mixer/paths";
else
#endif
if (getenv("ACP_BUILDDIR") != NULL)
return "mixer/paths";
if ((str = getenv("ACP_PATHS_DIR")) != NULL)
return str;
return PA_ALSA_PATHS_DIR;
}なるほど。 ACP_BUILD_DIR 環境変数がなくて ACP_PATHS_DIR 環境変数があれば
ACP_PATHS_DIR に指定したパスから読んでくれるということか。なければ
PA_ALSA_PATHS_DIR マクロの指す値だ。
念のため。本当にpa_alsa_path_new()の引数paths_dirにはNULLが渡されているのか?呼び出しをたどってみる。
mapping_paths_probe() -> pa_alsa_path_set_new() ->
pa_alsa_path_new()となっていて、mapping_paths_probe()の処理はこう。
static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
pa_alsa_direction_t direction, pa_hashmap *used_paths,
pa_hashmap *mixers) {
pa_alsa_path *p;
void *state;
snd_pcm_t *pcm_handle;
pa_alsa_path_set *ps;
snd_mixer_t *mixer_handle;
if (direction == PA_ALSA_DIRECTION_OUTPUT) {
if (m->output_path_set)
return; /* Already probed */
m->output_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */
pcm_handle = m->output_pcm;
} else {
if (m->input_path_set)
return; /* Already probed */
m->input_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */
pcm_handle = m->input_pcm;
}pa_alsa_path_set_new()の第3引数がconst char *paths_dirなのでどのみちNULLが渡されている。(FIXMEが少し気がかりだが)これで問題解決。