Closed peesock closed 6 months ago
The "multi" alsa plugin that makes this happen seems to be the cause
The multi plugin was originally designed to be used with just one soundcard. That card provides a single clock for all the devices of the multi, so they naturally all run at exactly the same rate. When you use devices from different sound cards within a multi plugin, then they each have their own clock and so inevitably there is some very small rate drift between them. This causes one device to run down its buffer more quickly than the other.
The ALSA device seen by MPD is the multi plugin itself, and this runs at the rate set by the clock of its "master" child device. If the other child is faster than the master then it will eventually underrun because MPD is supplying samples at the rate set by the master child. There is nothing that MPD can do to work around this, it is a (design) feature of the ALSA multi plugin.
One thing you could try is to use the "speakermixer" device as the master child (you do not give the definition of "pcm.speakermixer"). The easiest way to do this is to add the key "master" to you multi definition:
pcm.mpdquader {
type multi
master 1
slaves.a.pcm "mpdmixer"
slaves.a.channels 2
# slaves.b.pcm "mpdfifo"
slaves.b.pcm "mpdsoftvol"
slaves.b.channels 2
bindings.0 { slave a; channel 0; }
bindings.1 { slave a; channel 1; }
bindings.2 { slave b; channel 0; }
bindings.3 { slave b; channel 1; }
}
That will make "mpdsoftvol" the master. (by default the master child is the first one given in the multi definition). Change to master 0
to make "mpdmixer" the master again.
This is not guaranteed to work. The master child not only supplies the clock, but also takes precedence when setting the hardware parameters so you may find other effects as well as the relative timing of the two devices.
Note also that the ALSA "rate" plugin can have an even greater effect on relative timing than the hardware device clock. PCM frame transfers happen at time intervals determined by the period time; but a transfer can only move a whole number of frames. If a rate conversion results in a fractional number of frames for the period size, then the rate plugin will round the period size down to the nearest whole number. This makes the child prone to underruns. The solution is to always choose a period time that is a whole number of frames at both the parent device rate and the child device rate. For example, if converting from 48000 to 44100, then choose a period time that is a multiple of 10ms: 10ms is 480 frames at 48000 and 441 frames at 44100.
output: Failed to play on "desktop (alsa)" (alsa): snd_pcm_writei() failed: Broken pipe
ALSA PCM functions return error code EPIPE to indicate underrun or overrun. Within the function AlsaOutput::DrainInternal()
MPD does not attempt to recover from ALSA errors, so if an underrun occurs when MPD is draining its internal buffer then MPD stops the player. @MaxKellermann perhaps MPD should use AlsaInputStream::Recover()
here to prevent recoverable errors from stopping playback?
"speakermixer" is the dmix plugin to my speakers:
pcm.speakermixer {
type dmix
ipc_key 1112
slave {
pcm "hw:PCH,0"
rate 48000
format S16_LE
channels 2
period_size 1024
# buffer_size 2048
buffer_size 4096
}
}
Setting that to the master child seems to work perfectly from short tests on both my MPD alsa device and my normal alsa device, which makes sense in hindsight (setting a loopback as the master doesn't make sense). This might also fix my long-running audio/video sync issues when recording with ffmpeg.
Thanks for the help. Should this be closed, or is mpd's stopped playback issue still an issue?
Update: I played around a lot and noticed that sometimes when opening pcm.mpd (and also my normal alsa device, which has the same config), it explodes and gives the same broken pipe error. On clients that don't immediately give up on errors, it seems to cause worse and worse xruns over time. But if it doesn't error at first, it seems to play forever. Edit: it does not play forever. It also constantly fails with aplay
, but my normal alsa device is fine with aplay, so there must be some difference...
Increasing the number of periods from 2 to 3 or 4 eliminates the error when opening the device, but the pipe will eventually break over time. Setting lower period sizes (256) with a period of 3 or 4 shows mpd clearly increasing cpu usage over time to 100% of a core, before dropping back down and repeating.
I also tried combining alsaloop
from alsa-utils with the loopback device and dmix to avoid using the multi plugin completely, but always resulted in a hundred xruns and failure. Audio is very hard.
Audio is very hard
Using low-level tools such as alsa-lib and alsaloop to achieve complex systems is hard. You need a deep understanding of what each ALSA plugin and utility does, and what their limitations are. This is not the proper place to get into such discussions.
Audio is much easier if you used audio tools that are designed to solve the 2 main problems that you are struggling with: synchronizing 2 soundcards and mixing audio streams from 2 or more applications into both those devices. Pipewire/Wireplumber and Pulseaudio do these things for free. You may have reached the point where your time is better spent learning to use use one of those rather than (the rather sparsely documented) ALSA.
Bug report
Describe the bug
Maybe this is a skill issue, but my alsa pcm device for mpd has a special config that mixes audio between mpd playback and a loopback device, letting me record my mpv audio if needed.
Playing a long playlist will eventually cause an alsa underrun followed by the
snd_pcm_writei() failed: Broken pipe
error and playback goes quiet until i pause and play the music again. Setting higher alsa buffer sizes delays the error, but it still happens.The "multi" alsa plugin that makes this happen seems to be the cause, as using any pcm device without multi in its chain works perfectly fine, even with lower buffer sizes (2048 vs 8192).
Another thing to note is i also get alsa underruns on other programs using the same multi plugin setup, and no underruns without it. That's what makes me think i'm configuring alsa incorrectly, but mpd's reaction to it isn't good.
Expected Behavior
Play music forever and ever
Actual Behavior
Play music and occasionally go mute for a while or until pause+play
Version
Configuration
ALSA config
Log