Skip to content

Commit d6f21b2

Browse files
authored
[Feature] Allow additional alsa outputs #156 (#157)
1 parent b2dcb1c commit d6f21b2

File tree

4 files changed

+246
-31
lines changed

4 files changed

+246
-31
lines changed

README.md

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -65,38 +65,11 @@ Getting the image from DockerHub is as simple as typing:
6565

6666
`docker pull giof71/mpd-alsa`
6767

68-
You might want to pull the `stable` image as opposed to the default `latest`.
69-
7068
## Usage
7169

7270
### User mode
7371

74-
You can enable user-mode by specifying `USER_MODE` to `Y` or `YES`.
75-
For `alsa` mode, it is important that the container knows the group id of the host `audio` group. On my system it's `995`, however it is possible to verify using the following command:
76-
77-
```code
78-
getent group audio
79-
```
80-
81-
On my system, this commands outputs:
82-
83-
```text
84-
audio:x:995:brltty,mpd,squeezelite
85-
```
86-
87-
In any case, make sure to set the variable `AUDIO_GID` accordingly. The variable is mandatory for user mode with alsa output.
88-
Also, if your user/group id are not both `1000`, set `PUID` and `PGID` accordingly.
89-
It is possible to verify the uid and gid of the currently logged user using the following command:
90-
91-
```code
92-
id
93-
```
94-
95-
On my system this command outputs:
96-
97-
```text
98-
uid=1000(giovanni) gid=1000(giovanni) groups=1000(giovanni),3(sys),90(network),98(power),957(autologin),965(docker),967(libvirt),991(lp),992(kvm),998(wheel)
99-
```
72+
See [this](doc/user-mode.md) page.
10073

10174
### Volumes
10275

@@ -142,12 +115,12 @@ ALSA_ALLOWED_FORMATS||Sets the `alsa` output allowed formats
142115
AUTO_RESAMPLE||If set to no, then libasound will not attempt to resample. In this case, the user is responsible for ensuring that the requested sample rate can be produced natively by the device, otherwise an error will occur.
143116
THESYCON_DSD_WORKAROUND||If enabled, enables a workaround for a bug in Thesycon USB audio receivers. On these devices, playing DSD512 or PCM causes all subsequent attempts to play other DSD rates to fail, which can be fixed by briefly playing PCM at 44.1 kHz.
144117
ALSA_ALLOWED_FORMATS_PRESET||Alternative to `ALSA_ALLOWED_FORMATS`. Possible values: `8x`, `4x`, `2x`, `8x-nodsd`, `4x-nodsd`, `2x-nodsd`
118+
INTEGER_UPSAMPLING||If one or more `ALSA_ALLOWED_FORMATS` are set and `INTEGER_UPSAMPLING` is set to `yes`, the formats which are evenly divided by the source sample rate are preferred. The `ALSA_ALLOWED_FORMATS` list is processed in order as provided to the container. So if you want to upsample, put higher sampling rates first. Using this feature causes a patched version of mpd to be run. Use at your own risk.
145119
PULSEAUDIO_OUTPUT_NAME||PulseAudio output name, defaults to `PulseAudio`
146120
PULSEAUDIO_OUTPUT_ENABLED||Sets the output as enabled if set to `yes`, otherwise mpd's default behavior applies
147121
PULSEAUDIO_OUTPUT_SINK||Specifies the name of the PulseAudio sink MPD should play on
148122
PULSEAUDIO_OUTPUT_MEDIA_ROLE||Specifies a custom media role that MPD reports to PulseAudio, defaults to `music`
149123
PULSEAUDIO_OUTPUT_SCALE_FACTOR||Specifies a linear scaling coefficient (ranging from `0.5` to `5.0`) to apply when adjusting volume through MPD. For example, chosing a factor equal to `0.7` means that setting the volume to 100 in MPD will set the PulseAudio volume to 70%, and a factor equal to `3.5` means that volume 100 in MPD corresponds to a 350% PulseAudio volume.
150-
INTEGER_UPSAMPLING||If one or more `ALSA_ALLOWED_FORMATS` are set and `INTEGER_UPSAMPLING` is set to `yes`, the formats which are evenly divided by the source sample rate are preferred. The `ALSA_ALLOWED_FORMATS` list is processed in order as provided to the container. So if you want to upsample, put higher sampling rates first. Using this feature causes a patched version of mpd to be run. Use at your own risk.
151124
INPUT_CACHE_SIZE||Sets the input cache size. Example value: `1 GB`
152125
NULL_OUTPUT_NAME||Name of the `null` output
153126
NULL_OUTPUT_SYNC||Sync mode for the `null` output, can be `yes` (default) or `no`
@@ -194,6 +167,32 @@ RESTORE_PAUSED||If set to `yes`, then MPD is put into pause mode instead of star
194167
STATE_FILE_INTERVAL||Auto-save the state file this number of seconds after each state change, defaults to `10` seconds
195168
STARTUP_DELAY_SEC|0|Delay before starting the application. This can be useful if your container is set up to start automatically, so that you can resolve race conditions with mpd and with squeezelite if all those services run on the same audio device. I experienced issues with my Asus Tinkerboard, while the Raspberry Pi has never really needed this. Your mileage may vary. Feel free to report your personal experience.
196169

170+
#### ALSA additional outputs
171+
172+
Additional alsa outputs can be configured using the following variables:
173+
174+
VARIABLE|OPTIONAL|DESCRIPTION
175+
:---|:---:|:---
176+
ALSA_OUTPUT_CREATE|yes|Set to `yes` if you want to create and additional httpd output
177+
ALSA_OUTPUT_ENABLED|yes|Sets the output as enabled if set to `yes`, otherwise mpd's default behavior applies
178+
ALSA_OUTPUT_PRESET|yes|Use an Alsa preset for easier configuration
179+
ALSA_OUTPUT_DEVICE|yes|Sets alsa device
180+
ALSA_OUTPUT_AUTO_FIND_MIXER|yes|Allows to auto-select the mixer for easy hardware volume configuration
181+
ALSA_OUTPUT_MIXER_TYPE|yes|Mixer type
182+
ALSA_OUTPUT_MIXER_DEVICE|yes|Mixer device
183+
ALSA_OUTPUT_MIXER_CONTROL|yes|Mixer Control
184+
ALSA_OUTPUT_MIXER_INDEX|yes|Mixer Index
185+
ALSA_OUTPUT_ALLOWED_FORMATS_PRESET|yes|Sets allowed formats using a preset
186+
ALSA_OUTPUT_ALLOWED_FORMATS|yes|Sets allowed formats
187+
ALSA_OUTPUT_OUTPUT_FORMAT|yes|Sets output format
188+
ALSA_OUTPUT_AUTO_RESAMPLE|yes|Sets auto resample
189+
ALSA_OUTPUT_THESYCON_DSD_WORKAROUND|yes|Enables workaround
190+
ALSA_OUTPUT_INTEGER_UPSAMPLING|yes|Enables integer upsampling
191+
ALSA_OUTPUT_DOP|yes|Enables Dsd-Over-Pcm. Possible values: `yes` or `no`. Empty by default: this it lets mpd handle dop setting.
192+
193+
For the meaning, refer to the corresponding values in the first list of environment variables.
194+
Note that you can add up to 5 (or what is specified for the variable `MAX_ADDITIONAL_OUTPUTS_BY_TYPE`) httpd outputs. In order to specify distinct values, you can add `_1`, `_2` to every variable names in this set. The first output does *not* require to specify `_0`, that index is implicit.
195+
197196
#### HTTPD additional outputs
198197

199198
Additional httpd outputs can be configured using the following variables:
@@ -285,6 +284,7 @@ Just be careful to use the tag you have built.
285284

286285
Date|Major Changes
287286
:---|:---
287+
2022-12-27|Support for additional `alsa` outputs
288288
2022-12-24|`MAX_ADDITIONAL_OUTPUTS_BY_TYPE` now defaults to `20`
289289
2022-12-17|Add `MPD_ENABLE_LOGGING`
290290
2022-12-16|Preset `fiio-e18` now includes mixer
@@ -295,7 +295,7 @@ Date|Major Changes
295295
2022-12-12|Support for `state_file_interval`
296296
2022-12-12|Mount for `shout` has an index-aware default now
297297
2022-12-12|Do not force `enabled` by default for additional outputs
298-
2022-12-12|Support for optional `shout` outputs
298+
2022-12-12|Support for additional `shout` outputs
299299
2022-12-12|Support for `restore_paused`
300300
2022-12-10|Support for `mixer_type` in httpd outputs
301301
2022-12-10|Lookup table for more convenient `samplerate_converter` values

app/bin/build-additional.sh

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,187 @@ add_output_parameter() {
5151
fi
5252
}
5353

54+
add_alsa_output_parameter() {
55+
out_file=$1
56+
idx=$2
57+
env_var_name=$3
58+
param_name=$4
59+
param_default=$5
60+
param_default_type=$6
61+
key_name=$7
62+
c_var=$(alsa_get_stored_or_named $env_var_name $idx $key_name)
63+
if [ -n "${c_var}" ]; then
64+
final_var=${c_var}
65+
else
66+
if [ ${param_default_type} == "num" ]; then
67+
calc=$(get_indexed_default_num $param_default $idx)
68+
elif [ ${param_default_type} == "str" ]; then
69+
calc=$(get_indexed_default $param_default $idx)
70+
elif [ ${param_default_type} == "constant" ]; then
71+
calc=$param_default
72+
elif [ ${param_default_type} == "none" ]; then
73+
# parameter has no default
74+
calc=""
75+
else
76+
echo "Invalid default type [${param_default_type}]"
77+
exit 8
78+
fi
79+
final_var=$calc
80+
fi
81+
# only write non-empty values
82+
if [ -n "${final_var}" ]; then
83+
echo " ${param_name} \"${final_var}\"" >> $out_file
84+
fi
85+
}
86+
87+
get_alsa_preset_value() {
88+
preset_name=$1
89+
preset_key=$2
90+
preset_value=""
91+
if [ -n ${preset_name} ]; then
92+
alsa_preset_key="${preset_name}.${preset_key}"
93+
alsa_preset_value="${alsa_presets[${alsa_preset_key}]}"
94+
if [[ -v alsa_preset_value ]]; then
95+
preset_value=$alsa_preset_value
96+
fi
97+
fi
98+
echo ${preset_value}
99+
}
100+
101+
declare -A alsa_out_set_values
102+
103+
track_alsa_out_set_value() {
104+
parameter_name=$1
105+
parameter_index=$2
106+
parameter_value=$3
107+
alsa_set_key="${parameter_name}.${parameter_index}"
108+
echo "track_alsa_out_set_value setting [${alsa_set_key}] to ["${parameter_value}"]"
109+
alsa_out_set_values[${alsa_set_key}]="${parameter_value}"
110+
}
111+
112+
load_preset_alsa_param() {
113+
idx=$1
114+
parameter_name=$2
115+
preset_name=$3
116+
preset_key=$4
117+
echo "Searching alsa preset value for PresetName [${preset_name}] Key [${preset_key}]"
118+
preset_value=$(get_alsa_preset_value ${preset_name} ${preset_key})
119+
if [ -n "${preset_value}" ]; then
120+
echo "Found alsa preset value for PresetName [${preset_name}] Key [${preset_key}] = [${preset_value}]"
121+
track_alsa_out_set_value "${parameter_name}" $idx "${preset_value}"
122+
else
123+
echo "Not found alsa preset value for PresetName [${preset_name}] Key [${preset_key}]"
124+
fi
125+
}
126+
127+
alsa_get_stored_or_named() {
128+
VAR_NAME=$1
129+
VAR_INDEX=$2
130+
KEY_NAME=$3
131+
select_var=$(get_named_env $VAR_NAME $VAR_INDEX)
132+
if [ -z ${select_var} ]; then
133+
#look in stored values
134+
alsa_set_key="${KEY_NAME}.${VAR_INDEX}"
135+
stored="${alsa_out_set_values[${alsa_set_key}]}"
136+
if [ -n "${stored}" ]; then
137+
select_var=${stored}
138+
fi
139+
fi
140+
echo ${select_var}
141+
}
142+
143+
build_alsa() {
144+
out_file=$1
145+
idx=$2
146+
create=$(get_named_env "ALSA_OUTPUT_CREATE" $idx)
147+
if [[ "${create^^}" == "YES" || "${create^^}" == "Y" ]]; then
148+
echo "Creating Alsa output for output [$idx]"
149+
open_output $out_file
150+
set_output_type $out_file alsa
151+
add_output_parameter $out_file $idx ALSA_OUTPUT_NAME name alsa str
152+
add_output_parameter $out_file $idx ALSA_OUTPUT_ENABLED enabled "" none
153+
current_preset=$(get_named_env ALSA_OUTPUT_PRESET $idx)
154+
if [ -n ${current_preset} ]; then
155+
echo "Alsa preset for ALSA Out [$idx] is [${current_preset}]"
156+
# alsa name preset is not used for additional outputs
157+
load_preset_alsa_param $idx "device" ${current_preset} "device"
158+
load_preset_alsa_param $idx "mixer_type" ${current_preset} "mixer-type"
159+
load_preset_alsa_param $idx "mixer_device" ${current_preset} "mixer-device"
160+
load_preset_alsa_param $idx "mixer_control" ${current_preset} "mixer-control"
161+
load_preset_alsa_param $idx "mixer_index" ${current_preset} "mixer-index"
162+
fi
163+
# try auto find if mixer is not already set
164+
auto_find_mixer=$(get_named_env $ALSA_OUTPUT_AUTO_FIND_MIXER $idx)
165+
mixer_device_key="mixer_device.${idx}"
166+
c_mixer_device=$alsa_out_set_values[$mixer_device_key]
167+
if [ -z "${c_mixer_device}" ]; then
168+
if [[ "${auto_find_mixer^^}" == "YES" || "${auto_find_mixer^^}" == "Y" ]]; then
169+
echo "Trying to find mixer ..."
170+
# find device
171+
# tentative #1 explicitly declared?
172+
c_device=$(get_named_env $ALSA_OUTPUT_DEVICE $idx)
173+
if [ -z "${c_device}" ]; then
174+
# tentative 2 - look from presets
175+
alsa_set_key="device.${idx}"
176+
c_device=$alsa_out_set_values[$alsa_set_key]
177+
fi
178+
if [ -z "${c_device}" ]; then
179+
c_raw_mixer_device="$(amixer -D ${c_device} scontrols | head -n 1)"
180+
c_mixer=$(echo ${c_raw_mixer_device} | cut -d "'" -f 2)
181+
# set mixer control
182+
alsa_set_key="mixer_control.${idx}"
183+
alsa_out_set_values[$alsa_set_key]=$c_mixer
184+
alsa_set_key="mixer_device.${idx}"
185+
alsa_out_set_values[$alsa_set_key]=$c_device
186+
alsa_set_key="mixer_type.${idx}"
187+
alsa_out_set_values[$alsa_set_key]="hardware"
188+
fi
189+
elif [[ "${auto_find_mixer^^}" != "NO" || "${auto_find_mixer^^}" != "N" ]]; then
190+
echo "Invalid ALSA_OUTPUT_AUTO_FIND_MIXER=[${auto_find_mixer}] for index [{$idx}]"
191+
exit 9
192+
fi
193+
fi
194+
# allowed format presets
195+
c_allowed_formats_preset=$(get_named_env ALSA_OUTPUT_ALLOWED_FORMATS_PRESET $idx)
196+
if [ -n "${c_allowed_formats_preset}" ]; then
197+
echo "Allowed formats preset set for alsa output [$idx] -> [${c_allowed_formats_preset}]"
198+
c_allowed_formats="${allowed_formats_presets[${c_allowed_formats_preset}]}"
199+
echo " translates to [${c_allowed_formats}]"
200+
if [[ -n "${c_allowed_formats}" ]]; then
201+
alsa_set_key="allowed_formats.${idx}"
202+
alsa_out_set_values[$alsa_set_key]="${c_allowed_formats}"
203+
fi
204+
fi
205+
# debug dump values
206+
## sz=`echo "${#alsa_out_set_values[@]}"`
207+
## echo "There are [$sz] available alsa_presets"
208+
## for key in "${!alsa_out_set_values[@]}"; do
209+
## echo "Alsa_out_value ["$key"]=["${alsa_out_set_values[$key]}"]"
210+
## done
211+
# end debug
212+
# write to config file!
213+
add_alsa_output_parameter $out_file $idx ALSA_OUTPUT_DEVICE device "" none "device"
214+
add_alsa_output_parameter $out_file $idx ALSA_OUTPUT_MIXER_TYPE mixer_type "" none "mixer_type"
215+
add_alsa_output_parameter $out_file $idx ALSA_OUTPUT_MIXER_DEVICE mixer_device "" none "mixer_device"
216+
add_alsa_output_parameter $out_file $idx ALSA_OUTPUT_MIXER_CONTROL mixer_control "" none "mixer_control"
217+
add_alsa_output_parameter $out_file $idx ALSA_OUTPUT_MIXER_INDEX mixer_index "" none "mixer_index"
218+
add_alsa_output_parameter $out_file $idx ALSA_OUTPUT_ALLOWED_FORMATS allowed_formats "" none "allowed_formats"
219+
add_output_parameter $out_file $idx ALSA_OUTPUT_OUTPUT_FORMAT output_format "" none
220+
add_output_parameter $out_file $idx ALSA_OUTPUT_AUTO_RESAMPLE auto_resample "" none
221+
add_output_parameter $out_file $idx ALSA_OUTPUT_THESYCON_DSD_WORKAROUND thesycon_dsd_workaround "" none
222+
add_output_parameter $out_file $idx ALSA_OUTPUT_INTEGER_UPSAMPLING integer_upsampling "" none
223+
add_output_parameter $out_file $idx ALSA_OUTPUT_DOP dop "" none
224+
close_output $out_file
225+
# see if the ups version must be enforced
226+
c_integer_upsampling=$(get_named_env "ALSA_OUTPUT_INTEGER_UPSAMPLING" $idx)
227+
echo "ALSA OUTPUT [$idx] requires INTEGER_UPSAMPLING [${c_integer_upsampling}]"
228+
if [[ "${c_integer_upsampling^^}" == "YES" || "${c_integer_upsampling^^}" == "Y" ]]; then
229+
echo "Setting mpd_binary to [${UPSAMPLING_MPD_BINARY}]"
230+
mpd_binary=$UPSAMPLING_MPD_BINARY
231+
fi
232+
fi
233+
}
234+
54235
build_httpd() {
55236
out_file=$1
56237
idx=$2
@@ -100,4 +281,4 @@ build_shout() {
100281
add_output_parameter $out_file $idx SHOUT_OUTPUT_PUBLIC public no constant
101282
close_output $out_file
102283
fi
103-
}
284+
}

app/bin/run-mpd.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,12 @@ do
474474
build_shout $MPD_ALSA_CONFIG_FILE $i
475475
done
476476

477+
## ALSA output
478+
for i in $( eval echo {0..$output_by_type_limit} )
479+
do
480+
build_alsa $MPD_ALSA_CONFIG_FILE $i
481+
done
482+
477483
## additional outputs
478484
ADDITIONAL_OUTPUTS_FILE=/user/config/additional-outputs.txt
479485
if [ -f "$ADDITIONAL_OUTPUTS_FILE" ]; then

doc/user-mode.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# User mode
2+
3+
You can enable user-mode by specifying `USER_MODE` to `Y` or `YES`.
4+
For `alsa` mode, it is important that the container knows the group id of the host `audio` group. On my system it's `995`, however it is possible to verify using the following command:
5+
6+
```code
7+
getent group audio
8+
```
9+
10+
On my system, this commands outputs:
11+
12+
```text
13+
audio:x:995:brltty,mpd,squeezelite
14+
```
15+
16+
In any case, make sure to set the variable `AUDIO_GID` accordingly. The variable is mandatory for user mode with alsa output.
17+
Also, if your user/group id are not both `1000`, set `PUID` and `PGID` accordingly.
18+
It is possible to verify the uid and gid of the currently logged user using the following command:
19+
20+
```code
21+
id
22+
```
23+
24+
On my system this command outputs:
25+
26+
```text
27+
uid=1000(giovanni) gid=1000(giovanni) groups=1000(giovanni),3(sys),90(network),98(power),957(autologin),965(docker),967(libvirt),991(lp),992(kvm),998(wheel)
28+
```

0 commit comments

Comments
 (0)