Skip to content

Commit 85d6d86

Browse files
committed
Use snd_pcm_pause if available
1 parent 640b55e commit 85d6d86

6 files changed

Lines changed: 75 additions & 7 deletions

File tree

src/lib/alsa/HwSetup.cxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ SetupHw(snd_pcm_t *pcm,
281281
if (err < 0)
282282
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_period_size() failed");
283283

284+
result.can_pause = snd_pcm_hw_params_can_pause(hwparams);
285+
284286
return result;
285287
}
286288

src/lib/alsa/HwSetup.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace Alsa {
1515
struct HwResult {
1616
snd_pcm_format_t format;
1717
snd_pcm_uframes_t buffer_size, period_size;
18+
bool can_pause;
1819
};
1920

2021
/**

src/output/Filtered.cxx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ FilteredAudioOutput::Cancel() noexcept
192192
void
193193
FilteredAudioOutput::BeginPause() noexcept
194194
{
195-
Cancel();
195+
if (!output->SupportsPauseWithoutCancel())
196+
Cancel();
196197
}
197198

198199
bool

src/output/Interface.hxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ public:
4040
return flags & FLAG_PAUSE;
4141
}
4242

43+
virtual bool SupportsPauseWithoutCancel() const noexcept {
44+
return false;
45+
}
46+
4347
bool GetNeedFullyDefinedAudioFormat() const noexcept {
4448
return flags & FLAG_NEED_FULLY_DEFINED_AUDIO_FORMAT;
4549
}

src/output/Thread.cxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,10 @@ AudioOutputControl::Task() noexcept
519519
the actual playback */
520520
if (source_state == SourceState::OPEN)
521521
source.Cancel();
522+
{
523+
const ScopeUnlock unlock(mutex);
524+
output->Cancel();
525+
}
522526
InternalPause(lock);
523527
} else {
524528
InternalClose(false);

src/output/plugins/AlsaOutputPlugin.cxx

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

236237
public:
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

403409
static 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
11391148
bool
11401149
AlsaOutput::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

Comments
 (0)