-
Notifications
You must be signed in to change notification settings - Fork 415
Use snd_pcm_pause if available #2496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -232,6 +232,7 @@ class AlsaOutput final | |
| const bool close_on_pause; | ||
|
|
||
| std::atomic_bool paused; | ||
| bool hw_can_pause = false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is cross-thread access to this field protected?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (see other reply about return Pause()). |
||
|
|
||
| public: | ||
| AlsaOutput(EventLoop &loop, const ConfigBlock &block); | ||
|
|
@@ -398,6 +399,11 @@ class AlsaOutput final | |
| /* virtual methods from class MultiSocketMonitor */ | ||
| Event::Duration PrepareSockets() noexcept override; | ||
| void DispatchSockets() noexcept override; | ||
|
|
||
| bool SupportsHardwarePause() const noexcept override { | ||
| return !close_on_pause && hw_can_pause; | ||
| } | ||
|
|
||
| }; | ||
|
|
||
| static constexpr Domain alsa_output_domain("alsa_output"); | ||
|
|
@@ -563,6 +569,8 @@ AlsaOutput::Setup(AudioFormat &audio_format, | |
| AlsaSetupSw(pcm, hw_result.buffer_size - hw_result.period_size, | ||
| hw_result.period_size); | ||
|
|
||
| hw_can_pause = hw_result.can_pause; | ||
|
|
||
| auto alsa_period_size = hw_result.period_size; | ||
| if (alsa_period_size == 0) | ||
| /* this works around a SIGFPE bug that occurred when | ||
|
|
@@ -1139,13 +1147,46 @@ AlsaOutput::Cancel() noexcept | |
| bool | ||
| AlsaOutput::Pause() noexcept | ||
| { | ||
| std::lock_guard lock{mutex}; | ||
| interrupted = false; | ||
| { | ||
| std::lock_guard lock{mutex}; | ||
| interrupted = false; | ||
|
|
||
| if (close_on_pause) | ||
| return false; | ||
| if (close_on_pause) | ||
| return false; | ||
|
|
||
| // TODO use snd_pcm_pause()? | ||
| if (!hw_can_pause) { | ||
| paused = true; | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| if (LockIsActive()) { | ||
| BlockingCall(GetEventLoop(), [this](){ | ||
| /* only pause if actually running; if the PCM is | ||
| still in PREPARED state, there is nothing in | ||
| the hardware buffer worth preserving */ | ||
| if (snd_pcm_state(pcm) == SND_PCM_STATE_RUNNING) { | ||
| int err = snd_pcm_pause(pcm, /* enable */ 1); | ||
| if (err < 0) { | ||
| LogError(alsa_output_domain, | ||
| "snd_pcm_pause() failed"); | ||
| hw_can_pause = false; | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| UnregisterSockets(); | ||
| silence_timer.Cancel(); | ||
|
|
||
| /* set waiting=true so that Activate() inside | ||
| Play() will re-schedule the event loop on | ||
| resume; without this, Activate() sees | ||
| active=true/waiting=false and returns early */ | ||
| waiting = true; | ||
| }); | ||
| } | ||
| if (!hw_can_pause) | ||
| return Pause(); // try again without hw pause | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please avoid recursive self-calls. This smells badly.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea here was that if somehow the snd_pcm_pause call above fails even though the driver said yes to can_pause(), we'll disable hw_can_pause and pause the old fashioned way. This recursive call would only ever happen once per session, but maybe it's a bit overly paranoid? Plus as you mentioned in the other comment, it might require protecting hw_can_pause with some lock, when we otherwise don't need it (it stays constant after the hw init, and I'm assuming Setup() is not run in parallel with playback :) ). I can remove this whole fallback thing if it's too silly?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you believe there needs to be a fallback, then there needs to be a fallback. Don't remove it. But implement it without Pause() calling itself recursively. |
||
|
|
||
| paused = true; | ||
| return true; | ||
|
|
@@ -1215,7 +1256,12 @@ AlsaOutput::Play(std::span<const std::byte> src) | |
| assert(!src.empty()); | ||
| assert(src.size() % in_frame_size == 0); | ||
|
|
||
| paused = false; | ||
| const bool was_paused = paused.exchange(false); | ||
|
|
||
| if (was_paused) { | ||
| std::lock_guard lock{mutex}; | ||
| Activate(); | ||
| } | ||
|
|
||
| const size_t max_frames = LockWaitWriteAvailable(); | ||
| const size_t max_size = max_frames * in_frame_size; | ||
|
|
@@ -1242,6 +1288,15 @@ AlsaOutput::PrepareSockets() noexcept | |
| } | ||
|
|
||
| try { | ||
| /* if the PCM was paused via snd_pcm_pause() (i.e. Pause() | ||
| was called with hw_can_pause), resume it now that Play() | ||
| has delivered new data and re-activated the I/O thread */ | ||
| if (snd_pcm_state(pcm) == SND_PCM_STATE_PAUSED) { | ||
| int err = snd_pcm_pause(pcm, /* disable */ 0); | ||
| if (err < 0) | ||
| throw Alsa::MakeError(err, "snd_pcm_pause() failed"); | ||
| } | ||
|
|
||
| return non_block.PrepareSockets(*this, pcm); | ||
| } catch (...) { | ||
| ClearSocketList(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can only guess why this is necessary - this is worth a comment explaining it.
But it's only really necessary if the output was paused-without-cancel? Can we skip the call on outputs that don't do that? And only do it if the output is really paused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is needed to drop output if we paused, and then for example decide to stop and switch to another track. We could skip it, but then we need to add some extra boilerplate, something like the below, so that we can check if this is supported on the output. The double Cancel() call is a no-op but it is an extra blocking call, idk how much that matters? I can go either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if it's effectively a no-op, it adds overhead, so we shouldn't do any calls when we already know it's a no-op.