From 0a035f3ce02293011a68b5dd0611dc1d0c84198f Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 29 Jul 2024 16:04:23 +0200 Subject: [PATCH] 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 --- NEWS | 1 + doc/plugins.rst | 5 +++ doc/user.rst | 3 +- src/output/plugins/AlsaOutputPlugin.cxx | 42 ++++++++++++++++++++----- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index f4d1d1fd5..7a87dd67f 100644 --- a/NEWS +++ b/NEWS @@ -32,6 +32,7 @@ ver 0.24 (not yet released) - alsa: limit ALSA buffer time to 2 seconds - alsa: set up a channel map - alsa: support the alsa-lib 1.2.11 API + - alsa: add option "close_on_pause" - curl: add "connect_timeout" configuration * decoder - ffmpeg: require FFmpeg 4.0 or later diff --git a/doc/plugins.rst b/doc/plugins.rst index 9e4d0d7fd..a8599ea79 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -896,6 +896,11 @@ The `Advanced Linux Sound Architecture (ALSA) `_ p 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: .. list-table:: diff --git a/doc/user.rst b/doc/user.rst index 9738182b1..7406ad777 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -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 output always open. Instead of closing at the end 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" `). This may be useful for streaming servers, when you don't want to disconnect all listeners even when playback is accidentally stopped. diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 4c64e75cd..f6bb22432 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -33,6 +33,7 @@ #include +#include #include #include @@ -222,6 +223,15 @@ class AlsaOutput final */ 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: AlsaOutput(EventLoop &loop, const ConfigBlock &block); @@ -261,6 +271,7 @@ private: void Close() noexcept override; void Interrupt() noexcept override; + std::chrono::steady_clock::duration Delay() const noexcept override; std::size_t Play(std::span src) override; void Drain() override; @@ -414,7 +425,7 @@ GetAlsaOpenMode(const ConfigBlock &block) } AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block) - :AudioOutput(FLAG_ENABLE_DISABLE), + :AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE), MultiSocketMonitor(_loop), defer_invalidate_sockets(_loop, BIND_THIS_METHOD(InvalidateSockets)), silence_timer(_loop, BIND_THIS_METHOD(OnSilenceTimer)), @@ -422,16 +433,16 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block) buffer_time(block.GetPositiveValue("buffer_time", MPD_ALSA_BUFFER_TIME_US)), period_time(block.GetPositiveValue("period_time", 0U)), - mode(GetAlsaOpenMode(block)) + mode(GetAlsaOpenMode(block)), #ifdef ENABLE_DSD -, dop_setting(block.GetBlockValue("dop", false) || /* legacy name from MPD 0.18 and older: */ block.GetBlockValue("dsd_usb", false)), stop_dsd_silence(block.GetBlockValue("stop_dsd_silence", false)), thesycon_dsd_workaround(block.GetBlockValue("thesycon_dsd_workaround", - false)) + false)), #endif + close_on_pause(block.GetBlockValue("close_on_pause", true)) { const char *allowed_formats_string = block.GetBlockValue("allowed_formats", nullptr); @@ -752,6 +763,8 @@ Play_44_1_Silence(snd_pcm_t *pcm) void AlsaOutput::Open(AudioFormat &audio_format) { + paused = false; + #ifdef ENABLE_DSD bool dop; #endif @@ -866,6 +879,15 @@ AlsaOutput::Interrupt() noexcept cond.notify_one(); } +std::chrono::steady_clock::duration +AlsaOutput::Delay() const noexcept +{ + if (paused) + return std::chrono::hours{1}; + + return AudioOutput::Delay(); +} + inline int AlsaOutput::Recover(int err) noexcept { @@ -1119,9 +1141,13 @@ AlsaOutput::Pause() noexcept std::lock_guard lock{mutex}; interrupted = false; - /* not implemented - this override exists only to reset the - "interrupted" flag */ - return false; + if (close_on_pause) + return false; + + // TODO use snd_pcm_pause()? + + paused = true; + return true; } void @@ -1188,6 +1214,8 @@ AlsaOutput::Play(std::span src) assert(!src.empty()); assert(src.size() % in_frame_size == 0); + paused = false; + const size_t max_frames = LockWaitWriteAvailable(); const size_t max_size = max_frames * in_frame_size; if (src.size() > max_size)