@@ -232,6 +232,7 @@ class AlsaOutput final
232232 const bool close_on_pause;
233233
234234 std::atomic_bool paused;
235+ bool hw_can_pause = false ;
235236
236237public:
237238 AlsaOutput (EventLoop &loop, const ConfigBlock &block);
@@ -398,6 +399,11 @@ class AlsaOutput final
398399 /* virtual methods from class MultiSocketMonitor */
399400 Event::Duration PrepareSockets () noexcept override ;
400401 void DispatchSockets () noexcept override ;
402+
403+ bool SupportsPauseWithoutCancel () const noexcept override {
404+ return !close_on_pause && hw_can_pause;
405+ }
406+
401407};
402408
403409static constexpr Domain alsa_output_domain (" alsa_output" );
@@ -563,6 +569,8 @@ AlsaOutput::Setup(AudioFormat &audio_format,
563569 AlsaSetupSw (pcm, hw_result.buffer_size - hw_result.period_size ,
564570 hw_result.period_size );
565571
572+ hw_can_pause = hw_result.can_pause ;
573+
566574 auto alsa_period_size = hw_result.period_size ;
567575 if (alsa_period_size == 0 )
568576 /* this works around a SIGFPE bug that occurred when
@@ -1095,6 +1103,7 @@ AlsaOutput::CancelInternal() noexcept
10951103 ring_buffer.Clear ();
10961104
10971105 active = false ;
1106+ paused = false ;
10981107 waiting = false ;
10991108
11001109 UnregisterSockets ();
@@ -1139,13 +1148,46 @@ AlsaOutput::Cancel() noexcept
11391148bool
11401149AlsaOutput::Pause () noexcept
11411150{
1142- std::lock_guard lock{mutex};
1143- interrupted = false ;
1151+ {
1152+ std::lock_guard lock{mutex};
1153+ interrupted = false ;
11441154
1145- if (close_on_pause)
1146- return false ;
1155+ if (close_on_pause)
1156+ return false ;
1157+
1158+ if (!hw_can_pause) {
1159+ paused = true ;
1160+ return true ;
1161+ }
1162+ }
11471163
1148- // TODO use snd_pcm_pause()?
1164+ if (LockIsActive ()) {
1165+ BlockingCall (GetEventLoop (), [this ](){
1166+ /* only pause if actually running; if the PCM is
1167+ still in PREPARED state, there is nothing in
1168+ the hardware buffer worth preserving */
1169+ if (snd_pcm_state (pcm) == SND_PCM_STATE_RUNNING ) {
1170+ int err = snd_pcm_pause (pcm, /* enable */ 1 );
1171+ if (err < 0 ) {
1172+ LogError (alsa_output_domain,
1173+ " snd_pcm_pause() failed" );
1174+ hw_can_pause = false ;
1175+ return ;
1176+ }
1177+ }
1178+
1179+ UnregisterSockets ();
1180+ silence_timer.Cancel ();
1181+
1182+ /* set waiting=true so that Activate() inside
1183+ Play() will re-schedule the event loop on
1184+ resume; without this, Activate() sees
1185+ active=true/waiting=false and returns early */
1186+ waiting = true ;
1187+ });
1188+ }
1189+ if (!hw_can_pause)
1190+ return Pause (); // try again without hw pause
11491191
11501192 paused = true ;
11511193 return true ;
@@ -1215,7 +1257,12 @@ AlsaOutput::Play(std::span<const std::byte> src)
12151257 assert (!src.empty ());
12161258 assert (src.size () % in_frame_size == 0 );
12171259
1218- paused = false ;
1260+ const bool was_paused = paused.exchange (false );
1261+
1262+ if (was_paused) {
1263+ std::lock_guard lock{mutex};
1264+ Activate ();
1265+ }
12191266
12201267 const size_t max_frames = LockWaitWriteAvailable ();
12211268 const size_t max_size = max_frames * in_frame_size;
@@ -1242,6 +1289,15 @@ AlsaOutput::PrepareSockets() noexcept
12421289 }
12431290
12441291 try {
1292+ /* if the PCM was paused via snd_pcm_pause() (i.e. Pause()
1293+ was called with hw_can_pause), resume it now that Play()
1294+ has delivered new data and re-activated the I/O thread */
1295+ if (snd_pcm_state (pcm) == SND_PCM_STATE_PAUSED ) {
1296+ int err = snd_pcm_pause (pcm, /* disable */ 0 );
1297+ if (err < 0 )
1298+ throw Alsa::MakeError (err, " snd_pcm_pause() failed" );
1299+ }
1300+
12451301 return non_block.PrepareSockets (*this , pcm);
12461302 } catch (...) {
12471303 ClearSocketList ();
0 commit comments