MusicPlayerDaemon / MPD

Music Player Daemon
https://www.musicpd.org/
GNU General Public License v2.0
2.19k stars 350 forks source link

Changing volume also alters relative volume levels of channels (ALSA) #2131

Closed bartkl closed 1 month ago

bartkl commented 1 month ago

Bug report

Describe the bug

I am running MPD on a Raspberry Pi and use ALSA for my device output. I've got the setup working fine basically, but I've stumbled upon an issue.

I've rearranged my room and now I can't sit perfectly between my speakers anymore. To compensate for this you would usually adjust the center balance on the amp, but on my amp this feature doesn't work anymore (it's old).

So, I solved it in alsamixer. I increased the volume of the left channel to compensate for my sitting a bit to the right. image

This solution works great, until I change my volume. If I change the volume in any MPD client, it equalizes the volumes of both channels. I suspect MPD calls the ALSA API such that it overwrites the volume levels of both channels with the new value?

Thanks!

Expected Behavior

If I increase or decrease the volume, I expect MPD to adjust each channel with the same fixed amount, not have it alter the relative volume levels per channel.

Actual Behavior

When changing the volume with MPD, it does not just change the volume, it also equalizes the volume levels of both channels.

Version

mpd --version
Music Player Daemon 0.24 (v0.23.12-578-g20a527656)
Copyright 2003-2007 Warren Dukes 
Copyright 2008-2021 Max Kellermann 
This is free software; see the source for copying conditions.  There is NO
warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Database plugins:
 simple proxy upnp

Storage plugins:
 local udisks nfs curl

Neighbor plugins:
 upnp udisks

Decoders plugins:
 [mad] mp3 mp2
 [mpg123] mp3
 [vorbis] ogg oga
 [oggflac] ogg oga
 [flac] flac
 [opus] opus ogg oga
 [sndfile] wav aiff aif au snd paf iff svx sf voc w64 pvf xi htk caf sd2
 [audiofile] wav au aiff aif
 [dsdiff] dff
 [dsf] dsf
 [faad] aac
 [mpcdec] mpc
 [wavpack] wv
 [modplug] 669 amf ams dbm dfm dsm far it med mdl mod mtm mt2 okt s3m stm ult umx xm
 [mikmod] amf dsm far gdm imf it med mod mtm s3m stm stx ult uni xm
 [sidplay] sid mus str prg P00
 [wildmidi] mid
 [fluidsynth] mid
 [adplug] amd d00 hsc laa rad raw sa2
 [ffmpeg] 16sv 3g2 3gp 4xm 8svx aa3 aac ac3 adx afc aif aifc aiff al alaw amr anim apc ape asf atrac au aud avi avm2 avs bap bfi c93 cak cin cmv cpk daud dct divx dts dv dvd dxa eac3 film flac flc fli fll flx flv g726 gsm gxf iss m1v m2v m2t m2ts m4a m4b m4v mad mj2 mjpeg mjpg mka mkv mlp mm mmf mov mp+ mp1 mp2 mp3 mp4 mpc mpeg mpg mpga mpp mpu mve mvi mxf nc nsv nut nuv oga ogm ogv ogx oma ogg omg opus psp pva qcp qt r3d ra ram rl2 rm rmvb roq rpl rvc shn smk snd sol son spx str swf tak tgi tgq tgv thp ts tsp tta xa xvid uv uv2 vb vid vob voc vp6 vmd wav webm wma wmv wsaud wsvga wv wve rtp:// rtsp:// rtsps://
 [gme] ay gbs gym hes kss nsf nsfe rsn sap spc vgm vgz
 [pcm]

Filters:
 libsamplerate soxr

Tag plugins:
 id3tag

Output plugins:
 shout null fifo sndio pipe alsa ao openal pipewire pulse jack httpd snapcast recorder

Encoder plugins:
 null vorbis opus lame twolame wave flac shine

Archive plugins:
 [bz2] bz2
 [zzip] zip
 [iso] iso

Input plugins:
 file archive alsa qobuz curl ffmpeg nfs mms cdio_paranoia

Playlist plugins:
 extm3u m3u pls xspf asx rss soundcloud flac cue embcue

Protocols:
 file:// alsa:// cdda:// ftp:// ftps:// gopher:// hls+http:// hls+https:// http:// https:// mms:// mmsh:// mmst:// mmsu:// nfs:// qobuz:// rtmp:// rtmps:// rtmpt:// rtmpts:// rtp:// rtsp:// rtsps:// scp:// sftp:// smb:// srtp://

Other features:
 avahi dbus udisks epoll icu inotify ipv6 systemd tcp un

Configuration

mpd.conf
## Files
music_directory          "/media/droppie/libraries/music"
playlist_directory       "/var/lib/mpd/playlists"
log_file                 "/var/log/mpd/mpd.log"
state_file               "/var/lib/mpd/state"
sticker_file             "/var/lib/mpd/sticker.sql"

## General options
user                     "mpd"
bind_to_address          "any"
port                     "6600"
log_level                "default"  # Choices: "default", "secure", "verbose".
restore_paused           "no"
metadata_to_use          "artist,artistsort,album,albumsort,albumartist,albumartistsort,title,titlesort,track,genre,date,originaldate,composer,composersort,performer,conductor,work,grouping,disc,label"
auto_update              "no"
follow_outside_symlinks  "no"
follow_inside_symlinks   "yes"

default_permissions      ""
password                 ""
password                 ""

## Database
database {
    plugin               "simple"
    path                 "/media/droppie/libraries/music/.meta/mpd/tag_cache"  # TODO.
    compress             "no"
    port                 "6600"
}

## Decoders
decoder {
    plugin               "wildmidi"
    enabled              "no"
}

## Partitions
partition {
    name                 "living-room"
}

partition {
    name                 "frasier"
}

## Audio outputs
audio_output {
    type                 "alsa"
    name                 "HiFiBerry"
    device               "hw:1,0"
    auto_resample        "no"  # Let MPD do the resampling.
    mixer_device         "hw:1"
    mixer_control        "Digital"
}

audio_output {
    type                 "pulse"
    name                 "KACL 780"
    server               "frasier"
}

## Normalization automatic volume adjustments
replaygain               "off"

## Character encoding
filesystem_charset       "UTF-8"

Log

n/a

borine commented 1 month ago

What you are seeing is the expected (designed) behaviour of mpd.

One workaround is to perform the channel balance adjustment before the stream is passed to the sound card, using alsalib. Create the file /etc/asound.conf with the following contents:

pcm.channelbalance {
    type plug
    slave.pcm "hw:IQaudIODAC" # change this for different card
    slave.channels 2
    ttable.0.0 1.00  # left channel at 100%
    ttable.1.1 0.94  # right channel at 94%
}

Adjust the ttable.1.1 value according to how much you wish to "move" the sound to the left, and set the slave.pcm value appropriately for your card.

Modify your mpd.conf to use the new ALSA pcm definition; something like:

audio_output {
    type "alsa"
    name "Balanced Sound Card"
    device "channelbalance"
    mixer_type "hardware"
    mixer_device "hw:IQaudIODAC" # change this for different card
    mixer_control "Digital"
}

Restart mpd so that it reads the new ALSA configuration, and hopefully the stereo balance will then be more to your liking, even as you use the mpd volume control.

bartkl commented 1 month ago

Works like a charm. Thank you!

bartkl commented 1 month ago

Hmm it doesn't work after all. I must have tested it sloppily.

What does work is when I change the relative volume levels, MPD picks up on that. But the issue still remains that as soon as I change volume using MPD, the channel volumes equalize. Maybe this is the case because we still point to the same mixer device (hw:IQaudIODAC in your example), and we should add some kind of virtual mixer device that sets the volume of the channelbalance?

Thanks.

PS I'm still curious why this is intented/designed MPD behavior. If I change the balance of my speakers on my amp I would be surprised if it jumpd to center as soon as I turn the volume up or down.

borine commented 1 month ago

when I change the relative volume levels

I didn't explain the "channelbalance" pcm solution very well. The idea is that you use that instead of changing the relative volume levels in alsamixer. We know that MPD will undo any channel volume difference applied by the mixer, so we apply the desired attenuation of the right channel before the stream reaches the mixer. So (in very imprecise language), say MPD requests volume of 60%, and the channelbalance pcm has reduced the right channel to 90%; in that case the mixer changes the left channel to 100% 60% == 60%; and the right channel to 90% 60% == 54%. So the left channel is still higher volume than the right, and there is still a perceived balance shift to the left. Those numbers will not be exact, because it all depends on exactly how each element calculates volume (taking into account the logarithmic relationship between perceived loudness and output power, different algorithms and parameters, etc).

we should add some kind of virtual mixer device

To apply such a solution to the channelbalance pcm would require writing a new ALSA plugin; so it would probably be simpler to code a solution for the MPD ALSA mixer plugin and raise a PR here.

There are two options for introducing virtual mixer that completely removes the need for a channelbalance pcm:

pcm.softvol {
    type softvol
    slave.pcm "hw:1,0"
    control {
        name "PCM Playback Volume"
        card 1
    }
}

You will need to open the pcm once to cause ALSA to create the "PCM" control, so do

aplay -D plug:softvol /usr/share/sounds/alsa/Front_Center.wav

Now modify mpd.conf so that it uses the "softvol" pcm and control called PCM

audio_output {
    type "alsa"
    name "Balanced Sound Card"
    device "plug:softvol"
    mixer_type "hardware"
    mixer_device "hw:1" # change this for different card
    mixer_control "PCM"
}

and then restart MPD.

To remove the PCM control if you later decide not to use this solution, delete the pcm.softvol entry from /etc/asound.conf, then do: (change the 1 to match your card)

alsactl clean 1 'name="PCM Playback Volume"'
bartkl commented 1 month ago

The idea is that you use that instead of changing the relative volume levels in alsamixer. We know that MPD will undo any channel volume difference applied by the mixer

Of course, that makes perfect sense. I honestly feel like I understood that before and got tangled up in a brainfart, haha. Anyways, I increased the difference in volume levels and yes, it is clearly working.

Thanks so much for the detailed explanation, for the alternatives as well. I might look into those later, but I'm pretty happy with this solution as it stands.