output/alsa: add option "close_on_pause"

This allows keeping the ALSA PCM open even if playback is paused.  As
a side effect, this allows using the "always_on" option with ALSA
outputs, because "always_on" pauses the output.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1623
This commit is contained in:
Max Kellermann 2024-07-29 16:04:23 +02:00
parent 7ab789fbaf
commit 0a035f3ce0
4 changed files with 43 additions and 8 deletions

1
NEWS
View File

@ -32,6 +32,7 @@ ver 0.24 (not yet released)
- alsa: limit ALSA buffer time to 2 seconds - alsa: limit ALSA buffer time to 2 seconds
- alsa: set up a channel map - alsa: set up a channel map
- alsa: support the alsa-lib 1.2.11 API - alsa: support the alsa-lib 1.2.11 API
- alsa: add option "close_on_pause"
- curl: add "connect_timeout" configuration - curl: add "connect_timeout" configuration
* decoder * decoder
- ffmpeg: require FFmpeg 4.0 or later - ffmpeg: require FFmpeg 4.0 or later

View File

@ -896,6 +896,11 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
Example: "96000:16:* 192000:24:* dsd64:*=dop *:dsd:*". Example: "96000:16:* 192000:24:* dsd64:*=dop *:dsd:*".
* - **close_on_pause yes|no**
- Close the ALSA device while playback is paused? This defaults
to *yes* because this allows other applications to use the
device while MPD is paused.
The according hardware mixer plugin understands the following settings: The according hardware mixer plugin understands the following settings:
.. list-table:: .. list-table::

View File

@ -532,7 +532,8 @@ The following table lists the audio_output options valid for all plugins:
- If set to yes, then :program:`MPD` attempts to keep this audio - If set to yes, then :program:`MPD` attempts to keep this audio
output always open. Instead of closing at the end output always open. Instead of closing at the end
of playback, it puts the device in "pause" mode. This works of playback, it puts the device in "pause" mode. This works
only with output plugins that suport "pause" mode. only with output plugins that suport "pause" mode (see
:ref:`ALSA option "close_on_pause" <alsa_plugin>`).
This may be useful for streaming servers, when you don't want This may be useful for streaming servers, when you don't want
to disconnect all listeners even when playback is accidentally to disconnect all listeners even when playback is accidentally
stopped. stopped.

View File

@ -33,6 +33,7 @@
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <atomic>
#include <string> #include <string>
#include <forward_list> #include <forward_list>
@ -222,6 +223,15 @@ class AlsaOutput final
*/ */
bool interrupted; bool interrupted;
/**
* Close the ALSA PCM while playback is paused? This defaults
* to true because this allows other applications to use the
* PCM while MPD is paused.
*/
const bool close_on_pause;
std::atomic_bool paused;
public: public:
AlsaOutput(EventLoop &loop, const ConfigBlock &block); AlsaOutput(EventLoop &loop, const ConfigBlock &block);
@ -261,6 +271,7 @@ private:
void Close() noexcept override; void Close() noexcept override;
void Interrupt() noexcept override; void Interrupt() noexcept override;
std::chrono::steady_clock::duration Delay() const noexcept override;
std::size_t Play(std::span<const std::byte> src) override; std::size_t Play(std::span<const std::byte> src) override;
void Drain() override; void Drain() override;
@ -414,7 +425,7 @@ GetAlsaOpenMode(const ConfigBlock &block)
} }
AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block) AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
:AudioOutput(FLAG_ENABLE_DISABLE), :AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE),
MultiSocketMonitor(_loop), MultiSocketMonitor(_loop),
defer_invalidate_sockets(_loop, BIND_THIS_METHOD(InvalidateSockets)), defer_invalidate_sockets(_loop, BIND_THIS_METHOD(InvalidateSockets)),
silence_timer(_loop, BIND_THIS_METHOD(OnSilenceTimer)), silence_timer(_loop, BIND_THIS_METHOD(OnSilenceTimer)),
@ -422,16 +433,16 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
buffer_time(block.GetPositiveValue("buffer_time", buffer_time(block.GetPositiveValue("buffer_time",
MPD_ALSA_BUFFER_TIME_US)), MPD_ALSA_BUFFER_TIME_US)),
period_time(block.GetPositiveValue("period_time", 0U)), period_time(block.GetPositiveValue("period_time", 0U)),
mode(GetAlsaOpenMode(block)) mode(GetAlsaOpenMode(block)),
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
,
dop_setting(block.GetBlockValue("dop", false) || dop_setting(block.GetBlockValue("dop", false) ||
/* legacy name from MPD 0.18 and older: */ /* legacy name from MPD 0.18 and older: */
block.GetBlockValue("dsd_usb", false)), block.GetBlockValue("dsd_usb", false)),
stop_dsd_silence(block.GetBlockValue("stop_dsd_silence", false)), stop_dsd_silence(block.GetBlockValue("stop_dsd_silence", false)),
thesycon_dsd_workaround(block.GetBlockValue("thesycon_dsd_workaround", thesycon_dsd_workaround(block.GetBlockValue("thesycon_dsd_workaround",
false)) false)),
#endif #endif
close_on_pause(block.GetBlockValue("close_on_pause", true))
{ {
const char *allowed_formats_string = const char *allowed_formats_string =
block.GetBlockValue("allowed_formats", nullptr); block.GetBlockValue("allowed_formats", nullptr);
@ -752,6 +763,8 @@ Play_44_1_Silence(snd_pcm_t *pcm)
void void
AlsaOutput::Open(AudioFormat &audio_format) AlsaOutput::Open(AudioFormat &audio_format)
{ {
paused = false;
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
bool dop; bool dop;
#endif #endif
@ -866,6 +879,15 @@ AlsaOutput::Interrupt() noexcept
cond.notify_one(); cond.notify_one();
} }
std::chrono::steady_clock::duration
AlsaOutput::Delay() const noexcept
{
if (paused)
return std::chrono::hours{1};
return AudioOutput::Delay();
}
inline int inline int
AlsaOutput::Recover(int err) noexcept AlsaOutput::Recover(int err) noexcept
{ {
@ -1119,9 +1141,13 @@ AlsaOutput::Pause() noexcept
std::lock_guard lock{mutex}; std::lock_guard lock{mutex};
interrupted = false; interrupted = false;
/* not implemented - this override exists only to reset the if (close_on_pause)
"interrupted" flag */
return false; return false;
// TODO use snd_pcm_pause()?
paused = true;
return true;
} }
void void
@ -1188,6 +1214,8 @@ AlsaOutput::Play(std::span<const std::byte> src)
assert(!src.empty()); assert(!src.empty());
assert(src.size() % in_frame_size == 0); assert(src.size() % in_frame_size == 0);
paused = false;
const size_t max_frames = LockWaitWriteAvailable(); const size_t max_frames = LockWaitWriteAvailable();
const size_t max_size = max_frames * in_frame_size; const size_t max_size = max_frames * in_frame_size;
if (src.size() > max_size) if (src.size() > max_size)