From 89b900432e0bfb324356f6ae62a09241eb75d251 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 28 Apr 2017 21:45:47 +0200 Subject: [PATCH] output/Internal: move thread-specific stuff to AudioOutputControl The AudioOutput struct (which is exposed to all plugins) should not be aware that it's being controlled by another thread. --- src/output/Control.cxx | 177 ++++-------- src/output/Control.hxx | 223 ++++++++++++++- src/output/Finish.cxx | 4 - src/output/Init.cxx | 3 +- src/output/Internal.hxx | 273 +------------------ src/output/Thread.cxx | 96 +++---- src/output/plugins/AlsaOutputPlugin.cxx | 1 + src/output/plugins/httpd/HttpdInternal.hxx | 1 + src/output/plugins/sles/SlesOutputPlugin.cxx | 1 + test/run_output.cxx | 2 - 10 files changed, 337 insertions(+), 444 deletions(-) diff --git a/src/output/Control.cxx b/src/output/Control.cxx index 4ae8f5e08..59736ecb2 100644 --- a/src/output/Control.cxx +++ b/src/output/Control.cxx @@ -38,7 +38,9 @@ static constexpr PeriodClock::Duration REOPEN_AFTER = std::chrono::seconds(10); struct notify audio_output_client_notify; AudioOutputControl::AudioOutputControl(AudioOutput *_output) - :output(_output), mutex(output->mutex) + :output(_output), + thread(BIND_THIS_METHOD(Task)), + mutex(output->mutex) { } @@ -91,20 +93,8 @@ AudioOutputControl::IsOpen() const return output->IsOpen(); } -bool -AudioOutputControl::IsBusy() const -{ - return output->IsBusy(); -} - -const std::exception_ptr & -AudioOutputControl::GetLastError() const -{ - return output->GetLastError(); -} - void -AudioOutput::WaitForCommand() +AudioOutputControl::WaitForCommand() { while (!IsCommandFinished()) { mutex.unlock(); @@ -114,13 +104,7 @@ AudioOutput::WaitForCommand() } void -AudioOutputControl::WaitForCommand() -{ - output->WaitForCommand(); -} - -void -AudioOutput::CommandAsync(Command cmd) +AudioOutputControl::CommandAsync(Command cmd) { assert(IsCommandFinished()); @@ -129,28 +113,28 @@ AudioOutput::CommandAsync(Command cmd) } void -AudioOutput::CommandWait(Command cmd) +AudioOutputControl::CommandWait(Command cmd) { CommandAsync(cmd); WaitForCommand(); } void -AudioOutput::LockCommandWait(Command cmd) +AudioOutputControl::LockCommandWait(Command cmd) { const std::lock_guard protect(mutex); CommandWait(cmd); } void -AudioOutput::EnableAsync() +AudioOutputControl::EnableAsync() { if (!thread.IsDefined()) { - if (plugin.enable == nullptr) { + if (output->plugin.enable == nullptr) { /* don't bother to start the thread now if the device doesn't even have a enable() method; just assign the variable and we're done */ - really_enabled = true; + output->really_enabled = true; return; } @@ -161,15 +145,15 @@ AudioOutput::EnableAsync() } void -AudioOutput::DisableAsync() +AudioOutputControl::DisableAsync() { if (!thread.IsDefined()) { - if (plugin.disable == nullptr) - really_enabled = false; + if (output->plugin.disable == nullptr) + output->really_enabled = false; else /* if there's no thread yet, the device cannot be enabled */ - assert(!really_enabled); + assert(!output->really_enabled); return; } @@ -180,27 +164,27 @@ AudioOutput::DisableAsync() void AudioOutputControl::EnableDisableAsync() { - output->EnableDisableAsync(); -} + if (output->enabled == output->really_enabled) + return; -void -AudioOutputControl::LockPauseAsync() -{ - output->LockPauseAsync(); + if (output->enabled) + EnableAsync(); + else + DisableAsync(); } inline bool -AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) +AudioOutputControl::Open(const AudioFormat audio_format, const MusicPipe &mp) { assert(allow_play); assert(audio_format.IsValid()); fail_timer.Reset(); - if (open && audio_format == request.audio_format) { - assert(request.pipe == &mp || (always_on && pause)); + if (output->open && audio_format == request.audio_format) { + assert(request.pipe == &mp || (output->always_on && output->pause)); - if (!pause) + if (!output->pause) /* already open, already the right parameters - nothing needs to be done */ return true; @@ -213,13 +197,14 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) StartThread(); CommandWait(Command::OPEN); - const bool open2 = open; + const bool open2 = output->open; - if (open2 && mixer != nullptr) { + if (open2 && output->mixer != nullptr) { try { - mixer_open(mixer); + mixer_open(output->mixer); } catch (const std::runtime_error &e) { - FormatError(e, "Failed to open mixer for '%s'", name); + FormatError(e, "Failed to open mixer for '%s'", + GetName()); } } @@ -227,29 +212,29 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) } void -AudioOutput::CloseWait() +AudioOutputControl::CloseWait() { assert(allow_play); - if (mixer != nullptr) - mixer_auto_close(mixer); + if (output->mixer != nullptr) + mixer_auto_close(output->mixer); - assert(!open || !fail_timer.IsDefined()); + assert(!output->open || !fail_timer.IsDefined()); - if (open) + if (output->open) CommandWait(Command::CLOSE); else fail_timer.Reset(); } bool -AudioOutput::LockUpdate(const AudioFormat audio_format, - const MusicPipe &mp, - bool force) +AudioOutputControl::LockUpdate(const AudioFormat audio_format, + const MusicPipe &mp, + bool force) { const std::lock_guard protect(mutex); - if (enabled && really_enabled) { + if (output->enabled && output->really_enabled) { if (force || !fail_timer.IsDefined() || fail_timer.Check(REOPEN_AFTER * 1000)) { return Open(audio_format, mp); @@ -260,26 +245,6 @@ AudioOutput::LockUpdate(const AudioFormat audio_format, return false; } -void -AudioOutputControl::LockRelease() -{ - output->LockRelease(); -} - -void -AudioOutputControl::LockCloseWait() -{ - output->LockCloseWait(); -} - -bool -AudioOutputControl::LockUpdate(const AudioFormat audio_format, - const MusicPipe &mp, - bool force) -{ - return output->LockUpdate(audio_format, mp, force); -} - bool AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const { @@ -293,7 +258,7 @@ AudioOutputControl::ClearTailChunk(const MusicChunk &chunk) } void -AudioOutput::LockPlay() +AudioOutputControl::LockPlay() { const std::lock_guard protect(mutex); @@ -306,13 +271,13 @@ AudioOutput::LockPlay() } void -AudioOutput::LockPauseAsync() +AudioOutputControl::LockPauseAsync() { - if (mixer != nullptr && plugin.pause == nullptr) + if (output->mixer != nullptr && output->plugin.pause == nullptr) /* the device has no pause mode: close the mixer, unless its "global" flag is set (checked by mixer_auto_close()) */ - mixer_auto_close(mixer); + mixer_auto_close(output->mixer); const std::lock_guard protect(mutex); @@ -322,7 +287,7 @@ AudioOutput::LockPauseAsync() } void -AudioOutput::LockDrainAsync() +AudioOutputControl::LockDrainAsync() { const std::lock_guard protect(mutex); @@ -332,7 +297,7 @@ AudioOutput::LockDrainAsync() } void -AudioOutput::LockCancelAsync() +AudioOutputControl::LockCancelAsync() { const std::lock_guard protect(mutex); @@ -343,7 +308,7 @@ AudioOutput::LockCancelAsync() } void -AudioOutput::LockAllowPlay() +AudioOutputControl::LockAllowPlay() { const std::lock_guard protect(mutex); @@ -353,18 +318,18 @@ AudioOutput::LockAllowPlay() } void -AudioOutput::LockRelease() +AudioOutputControl::LockRelease() { - if (always_on) + if (output->always_on) LockPauseAsync(); else LockCloseWait(); } void -AudioOutput::LockCloseWait() +AudioOutputControl::LockCloseWait() { - assert(!open || !fail_timer.IsDefined()); + assert(!output->open || !fail_timer.IsDefined()); const std::lock_guard protect(mutex); CloseWait(); @@ -377,7 +342,7 @@ AudioOutputControl::SetReplayGainMode(ReplayGainMode _mode) } void -AudioOutput::StopThread() +AudioOutputControl::StopThread() { assert(thread.IsDefined()); assert(allow_play); @@ -391,6 +356,12 @@ AudioOutput::BeginDestroy() { if (mixer != nullptr) mixer_auto_close(mixer); +} + +void +AudioOutputControl::BeginDestroy() +{ + output->BeginDestroy(); if (thread.IsDefined()) { const std::lock_guard protect(mutex); @@ -398,48 +369,18 @@ AudioOutput::BeginDestroy() } } -void -AudioOutputControl::BeginDestroy() -{ - output->BeginDestroy(); -} - void AudioOutput::FinishDestroy() { - if (thread.IsDefined()) - thread.Join(); - audio_output_free(this); } void AudioOutputControl::FinishDestroy() { + if (thread.IsDefined()) + thread.Join(); + output->FinishDestroy(); output = nullptr; } - -void -AudioOutputControl::LockPlay() -{ - output->LockPlay(); -} - -void -AudioOutputControl::LockDrainAsync() -{ - output->LockDrainAsync(); -} - -void -AudioOutputControl::LockCancelAsync() -{ - output->LockCancelAsync(); -} - -void -AudioOutputControl::LockAllowPlay() -{ - output->LockAllowPlay(); -} diff --git a/src/output/Control.hxx b/src/output/Control.hxx index 8aa2161a1..74a90e4d9 100644 --- a/src/output/Control.hxx +++ b/src/output/Control.hxx @@ -20,6 +20,10 @@ #ifndef MPD_OUTPUT_CONTROL_HXX #define MPD_OUTPUT_CONTROL_HXX +#include "AudioFormat.hxx" +#include "thread/Thread.hxx" +#include "thread/Cond.hxx" +#include "system/PeriodClock.hxx" #include "Compiler.h" #include @@ -32,7 +36,6 @@ #include enum class ReplayGainMode : uint8_t; -struct AudioFormat; struct AudioOutput; struct MusicChunk; class MusicPipe; @@ -46,6 +49,99 @@ class AudioOutputClient; class AudioOutputControl { AudioOutput *output; + /** + * The error that occurred in the output thread. It is + * cleared whenever the output is opened successfully. + * + * Protected by #mutex. + */ + std::exception_ptr last_error; + + /** + * If not nullptr, the device has failed, and this timer is used + * to estimate how long it should stay disabled (unless + * explicitly reopened with "play"). + */ + PeriodClock fail_timer; + + /** + * The thread handle, or nullptr if the output thread isn't + * running. + */ + Thread thread; + + /** + * This condition object wakes up the output thread after + * #command has been set. + */ + Cond cond; + + /** + * Additional data for #command. Protected by #mutex. + */ + struct Request { + /** + * The #AudioFormat requested by #Command::OPEN. + */ + AudioFormat audio_format; + + /** + * The #MusicPipe passed to #Command::OPEN. + */ + const MusicPipe *pipe; + } request; + + /** + * The next command to be performed by the output thread. + */ + enum class Command { + NONE, + ENABLE, + DISABLE, + + /** + * Open the output, or reopen it if it is already + * open, adjusting for input #AudioFormat changes. + */ + OPEN, + + CLOSE, + PAUSE, + + /** + * Drains the internal (hardware) buffers of the device. This + * operation may take a while to complete. + */ + DRAIN, + + CANCEL, + KILL + } command = Command::NONE; + + /** + * When this flag is set, the output thread will not do any + * playback. It will wait until the flag is cleared. + * + * This is used to synchronize the "clear" operation on the + * shared music pipe during the CANCEL command. + */ + bool allow_play = true; + + /** + * True while the OutputThread is inside ao_play(). This + * means the PlayerThread does not need to wake up the + * OutputThread when new chunks are added to the MusicPipe, + * because the OutputThread is already watching that. + */ + bool in_playback_loop = false; + + /** + * Has the OutputThread been woken up to play more chunks? + * This is set by audio_output_play() and reset by ao_play() + * to reduce the number of duplicate wakeups. + */ + bool woken_for_play = false; + public: Mutex &mutex; @@ -53,6 +149,8 @@ public: #ifndef NDEBUG ~AudioOutputControl() { + assert(!fail_timer.IsDefined()); + assert(!thread.IsDefined()); assert(output == nullptr); } #endif @@ -84,28 +182,103 @@ public: gcc_pure bool IsOpen() const; - gcc_pure - bool IsBusy() const; + /** + * Caller must lock the mutex. + */ + bool IsBusy() const { + return IsOpen() && !IsCommandFinished(); + } /** * Caller must lock the mutex. */ - gcc_const - const std::exception_ptr &GetLastError() const; + const std::exception_ptr &GetLastError() const { + return last_error; + } + void StartThread(); + void StopThread(); + + /** + * Caller must lock the mutex. + */ + bool IsCommandFinished() const { + return command == Command::NONE; + } + + void CommandFinished(); + + /** + * Waits for command completion. + * + * Caller must lock the mutex. + */ void WaitForCommand(); + /** + * Sends a command, but does not wait for completion. + * + * Caller must lock the mutex. + */ + void CommandAsync(Command cmd); + + /** + * Sends a command to the #AudioOutput object and waits for + * completion. + * + * Caller must lock the mutex. + */ + void CommandWait(Command cmd); + + /** + * Lock the #AudioOutput object and execute the command + * synchronously. + */ + void LockCommandWait(Command cmd); + void BeginDestroy(); void FinishDestroy(); + /** + * Enables the device, but don't wait for completion. + * + * Caller must lock the mutex. + */ + void EnableAsync(); + + /** + * Disables the device, but don't wait for completion. + * + * Caller must lock the mutex. + */ + void DisableAsync(); + + /** + * Attempt to enable or disable the device as specified by the + * #enabled attribute; attempt to sync it with #really_enabled + * (wrapper for EnableAsync() or DisableAsync()). + * + * Caller must lock the mutex. + */ void EnableDisableAsync(); void LockPauseAsync(); + void CloseWait(); void LockCloseWait(); + + /** + * Closes the audio output, but if the "always_on" flag is set, put it + * into pause mode instead. + */ void LockRelease(); void SetReplayGainMode(ReplayGainMode _mode); + /** + * Caller must lock the mutex. + */ + bool Open(const AudioFormat audio_format, const MusicPipe &mp); + /** * Opens or closes the device, depending on the "enabled" * flag. @@ -124,8 +297,48 @@ public: void LockPlay(); void LockDrainAsync(); + + /** + * Clear the "allow_play" flag and send the "CANCEL" command + * asynchronously. To finish the operation, the caller has to + * call LockAllowPlay(). + */ void LockCancelAsync(); + + /** + * Set the "allow_play" and signal the thread. + */ void LockAllowPlay(); + +private: + /** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a + * command was issued + */ + bool WaitForDelay(); + + bool FillSourceOrClose(); + + bool PlayChunk(); + + /** + * Plays all remaining chunks, until the tail of the pipe has + * been reached (and no more chunks are queued), or until a + * command is received. + * + * @return true if at least one chunk has been available, + * false if the tail of the pipe was already reached + */ + bool Play(); + + void Pause(); + + /** + * The OutputThread. + */ + void Task(); }; #endif diff --git a/src/output/Finish.cxx b/src/output/Finish.cxx index d3f3ed665..3aa31b652 100644 --- a/src/output/Finish.cxx +++ b/src/output/Finish.cxx @@ -28,8 +28,6 @@ AudioOutput::~AudioOutput() { assert(!open); - assert(!fail_timer.IsDefined()); - assert(!thread.IsDefined()); if (mixer != nullptr) mixer_free(mixer); @@ -43,8 +41,6 @@ void audio_output_free(AudioOutput *ao) { assert(!ao->IsOpen()); - assert(!ao->fail_timer.IsDefined()); - assert(!ao->thread.IsDefined()); ao_plugin_finish(ao); } diff --git a/src/output/Init.cxx b/src/output/Init.cxx index 185971d8c..df28292e6 100644 --- a/src/output/Init.cxx +++ b/src/output/Init.cxx @@ -51,8 +51,7 @@ AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin, const ConfigBlock &block) - :plugin(_plugin), - thread(BIND_THIS_METHOD(Task)) + :plugin(_plugin) { assert(plugin.finish != nullptr); assert(plugin.open != nullptr); diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx index bbc8a4ae1..45bd25420 100644 --- a/src/output/Internal.hxx +++ b/src/output/Internal.hxx @@ -21,15 +21,9 @@ #define MPD_OUTPUT_INTERNAL_HXX #include "Source.hxx" -#include "SharedPipeConsumer.hxx" #include "AudioFormat.hxx" #include "filter/Observer.hxx" #include "thread/Mutex.hxx" -#include "thread/Cond.hxx" -#include "thread/Thread.hxx" -#include "system/PeriodClock.hxx" - -#include class PreparedFilter; class MusicPipe; @@ -43,30 +37,6 @@ struct AudioOutputPlugin; struct ReplayGainConfig; struct AudioOutput { - enum class Command { - NONE, - ENABLE, - DISABLE, - - /** - * Open the output, or reopen it if it is already - * open, adjusting for input #AudioFormat changes. - */ - OPEN, - - CLOSE, - PAUSE, - - /** - * Drains the internal (hardware) buffers of the device. This - * operation may take a while to complete. - */ - DRAIN, - - CANCEL, - KILL - }; - /** * The device's configured display name. */ @@ -124,37 +94,6 @@ struct AudioOutput { */ bool pause = false; - /** - * When this flag is set, the output thread will not do any - * playback. It will wait until the flag is cleared. - * - * This is used to synchronize the "clear" operation on the - * shared music pipe during the CANCEL command. - */ - bool allow_play = true; - - /** - * True while the OutputThread is inside ao_play(). This - * means the PlayerThread does not need to wake up the - * OutputThread when new chunks are added to the MusicPipe, - * because the OutputThread is already watching that. - */ - bool in_playback_loop = false; - - /** - * Has the OutputThread been woken up to play more chunks? - * This is set by audio_output_play() and reset by ao_play() - * to reduce the number of duplicate wakeups. - */ - bool woken_for_play = false; - - /** - * If not nullptr, the device has failed, and this timer is used - * to estimate how long it should stay disabled (unless - * explicitly reopened with "play"). - */ - PeriodClock fail_timer; - /** * The configured audio format. */ @@ -209,43 +148,11 @@ struct AudioOutput { */ FilterObserver convert_filter; - /** - * The thread handle, or nullptr if the output thread isn't - * running. - */ - Thread thread; - - /** - * The next command to be performed by the output thread. - */ - Command command = Command::NONE; - - /** - * Additional data for #command. Protected by #mutex. - */ - struct Request { - /** - * The #AudioFormat requested by #Command::OPEN. - */ - AudioFormat audio_format; - - /** - * The #MusicPipe passed to #Command::OPEN. - */ - const MusicPipe *pipe; - } request; - /** * This mutex protects #open, #fail_timer, #pipe. */ mutable Mutex mutex; - /** - * This condition object wakes up the output thread after - * #command has been set. - */ - Cond cond; - /** * The PlayerControl object which "owns" this output. This * object is needed to signal command completion. @@ -257,14 +164,6 @@ struct AudioOutput { */ AudioOutputSource source; - /** - * The error that occurred in the output thread. It is - * cleared whenever the output is opened successfully. - * - * Protected by #mutex. - */ - std::exception_ptr last_error; - /** * Throws #std::runtime_error on error. */ @@ -282,9 +181,6 @@ public: MixerListener &mixer_listener, const ConfigBlock &block); - void StartThread(); - void StopThread(); - void BeginDestroy(); void FinishDestroy(); @@ -306,137 +202,10 @@ public: return open; } - /** - * Caller must lock the mutex. - */ - bool IsCommandFinished() const { - return command == Command::NONE; - } - - /** - * Caller must lock the mutex. - */ - bool IsBusy() const { - return IsOpen() && !IsCommandFinished(); - } - - /** - * Caller must lock the mutex. - */ - const std::exception_ptr &GetLastError() const { - return last_error; - } - - /** - * Waits for command completion. - * - * Caller must lock the mutex. - */ - void WaitForCommand(); - - /** - * Sends a command, but does not wait for completion. - * - * Caller must lock the mutex. - */ - void CommandAsync(Command cmd); - - /** - * Sends a command to the #AudioOutput object and waits for - * completion. - * - * Caller must lock the mutex. - */ - void CommandWait(Command cmd); - - /** - * Lock the #AudioOutput object and execute the command - * synchronously. - */ - void LockCommandWait(Command cmd); - - /** - * Enables the device, but don't wait for completion. - * - * Caller must lock the mutex. - */ - void EnableAsync(); - - /** - * Disables the device, but don't wait for completion. - * - * Caller must lock the mutex. - */ - void DisableAsync(); - - /** - * Attempt to enable or disable the device as specified by the - * #enabled attribute; attempt to sync it with #really_enabled - * (wrapper for EnableAsync() or DisableAsync()). - * - * Caller must lock the mutex. - */ - void EnableDisableAsync() { - if (enabled == really_enabled) - return; - - if (enabled) - EnableAsync(); - else - DisableAsync(); - } - - void LockPauseAsync(); - - /** - * Same LockCloseWait(), but expects the lock to be - * held by the caller. - */ - void CloseWait(); - void LockCloseWait(); - - /** - * Closes the audio output, but if the "always_on" flag is set, put it - * into pause mode instead. - */ - void LockRelease(); - void SetReplayGainMode(ReplayGainMode _mode) { source.SetReplayGainMode(_mode); } - /** - * Caller must lock the mutex. - */ - bool Open(const AudioFormat audio_format, const MusicPipe &mp); - - /** - * Opens or closes the device, depending on the "enabled" - * flag. - * - * @param force true to ignore the #fail_timer - * @return true if the device is open - */ - bool LockUpdate(const AudioFormat audio_format, - const MusicPipe &mp, - bool force); - - void LockPlay(); - - void LockDrainAsync(); - - /** - * Clear the "allow_play" flag and send the "CANCEL" command - * asynchronously. To finish the operation, the caller has to - * call LockAllowPlay(). - */ - void LockCancelAsync(); - - /** - * Set the "allow_play" and signal the thread. - */ - void LockAllowPlay(); - /** * Did we already consumed this chunk? * @@ -455,9 +224,6 @@ public: source.ClearTailChunk(chunk); } -private: - void CommandFinished(); - /** * Throws #std::runtime_error on error. */ @@ -468,8 +234,11 @@ private: /** * Throws #std::runtime_error on error. */ - void Open(); + void Open(AudioFormat audio_format, const MusicPipe &pipe); + void Close(bool drain); + +private: /** * Invoke OutputPlugin::open() and configure the * #ConvertFilter. @@ -480,8 +249,6 @@ private: */ void OpenOutputAndConvert(AudioFormat audio_format); - void Close(bool drain); - /** * Close the output plugin. * @@ -494,37 +261,13 @@ private: */ void CloseFilter(); - /** - * Wait until the output's delay reaches zero. - * - * @return true if playback should be continued, false if a - * command was issued - */ - bool WaitForDelay(); - - bool FillSourceOrClose(); - - bool PlayChunk(); - - /** - * Plays all remaining chunks, until the tail of the pipe has - * been reached (and no more chunks are queued), or until a - * command is received. - * - * @return true if at least one chunk has been available, - * false if the tail of the pipe was already reached - */ - bool Play(); - +public: void BeginPause(); bool IteratePause(); - void Pause(); - - /** - * The OutputThread. - */ - void Task(); + void EndPause() { + pause = false; + } }; /** diff --git a/src/output/Thread.cxx b/src/output/Thread.cxx index 785ea947a..13b9079ac 100644 --- a/src/output/Thread.cxx +++ b/src/output/Thread.cxx @@ -48,7 +48,7 @@ #include void -AudioOutput::CommandFinished() +AudioOutputControl::CommandFinished() { assert(command != Command::NONE); command = Command::NONE; @@ -98,11 +98,9 @@ AudioOutput::CloseFilter() } inline void -AudioOutput::Open() +AudioOutput::Open(AudioFormat audio_format, const MusicPipe &pipe) { - assert(request.audio_format.IsValid()); - - fail_timer.Reset(); + assert(audio_format.IsValid()); /* enable the device (just in case the last enable has failed) */ Enable(); @@ -110,7 +108,7 @@ AudioOutput::Open() AudioFormat f; try { - f = source.Open(request.audio_format, *request.pipe, + f = source.Open(audio_format, pipe, prepared_replay_gain_filter, prepared_other_replay_gain_filter, prepared_filter); @@ -239,10 +237,10 @@ AudioOutput::CloseOutput(bool drain) * was issued */ inline bool -AudioOutput::WaitForDelay() +AudioOutputControl::WaitForDelay() { while (true) { - const auto delay = ao_plugin_delay(*this); + const auto delay = ao_plugin_delay(*output); if (delay <= std::chrono::steady_clock::duration::zero()) return true; @@ -254,14 +252,14 @@ AudioOutput::WaitForDelay() } bool -AudioOutput::FillSourceOrClose() +AudioOutputControl::FillSourceOrClose() try { - return source.Fill(mutex); + return output->source.Fill(mutex); } catch (const std::runtime_error &e) { FormatError(e, "Failed to filter for output \"%s\" [%s]", - name, plugin.name); + GetName(), output->plugin.name); - Close(false); + output->Close(false); /* don't automatically reopen this device for 10 seconds */ @@ -270,23 +268,23 @@ try { } inline bool -AudioOutput::PlayChunk() +AudioOutputControl::PlayChunk() { - if (tags) { - const auto *tag = source.ReadTag(); + if (output->tags) { + const auto *tag = output->source.ReadTag(); if (tag != nullptr) { const ScopeUnlock unlock(mutex); try { - ao_plugin_send_tag(*this, *tag); + ao_plugin_send_tag(*output, *tag); } catch (const std::runtime_error &e) { FormatError(e, "Failed to send tag to \"%s\" [%s]", - name, plugin.name); + GetName(), output->plugin.name); } } } while (command == Command::NONE) { - const auto data = source.PeekData(); + const auto data = output->source.PeekData(); if (data.IsEmpty()) break; @@ -297,17 +295,17 @@ AudioOutput::PlayChunk() try { const ScopeUnlock unlock(mutex); - nbytes = ao_plugin_play(*this, data.data, data.size); + nbytes = ao_plugin_play(*output, data.data, data.size); assert(nbytes <= data.size); } catch (const std::runtime_error &e) { FormatError(e, "\"%s\" [%s] failed to play", - name, plugin.name); + GetName(), output->plugin.name); nbytes = 0; } if (nbytes == 0) { - Close(false); + output->Close(false); /* don't automatically reopen this device for 10 seconds */ @@ -317,16 +315,16 @@ AudioOutput::PlayChunk() return false; } - assert(nbytes % out_audio_format.GetFrameSize() == 0); + assert(nbytes % output->out_audio_format.GetFrameSize() == 0); - source.ConsumeData(nbytes); + output->source.ConsumeData(nbytes); } return true; } inline bool -AudioOutput::Play() +AudioOutputControl::Play() { if (!FillSourceOrClose()) /* no chunk available */ @@ -351,7 +349,7 @@ AudioOutput::Play() give it a chance to refill the pipe before it runs empty */ const ScopeUnlock unlock(mutex); - client->ChunksConsumed(); + output->client->ChunksConsumed(); n = 0; } @@ -360,7 +358,7 @@ AudioOutput::Play() } while (FillSourceOrClose()); const ScopeUnlock unlock(mutex); - client->ChunksConsumed(); + output->client->ChunksConsumed(); return true; } @@ -397,26 +395,26 @@ AudioOutput::IteratePause() } inline void -AudioOutput::Pause() +AudioOutputControl::Pause() { - BeginPause(); + output->BeginPause(); CommandFinished(); do { if (!WaitForDelay()) break; - if (!IteratePause()) + if (!output->IteratePause()) break; } while (command == Command::NONE); - pause = false; + output->EndPause(); } void -AudioOutput::Task() +AudioOutputControl::Task() { - FormatThreadName("output:%s", name); + FormatThreadName("output:%s", GetName()); try { SetThreadRealtime(); @@ -438,7 +436,7 @@ AudioOutput::Task() last_error = nullptr; try { - Enable(); + output->Enable(); } catch (const std::runtime_error &e) { LogError(e); fail_timer.Update(); @@ -449,15 +447,17 @@ AudioOutput::Task() break; case Command::DISABLE: - Disable(); + output->Disable(); CommandFinished(); break; case Command::OPEN: last_error = nullptr; + fail_timer.Reset(); try { - Open(); + output->Open(request.audio_format, + *request.pipe); } catch (const std::runtime_error &e) { LogError(e); fail_timer.Update(); @@ -468,13 +468,13 @@ AudioOutput::Task() break; case Command::CLOSE: - if (open) - Close(false); + if (IsOpen()) + output->Close(false); CommandFinished(); break; case Command::PAUSE: - if (!open) { + if (!output->open) { /* the output has failed after audio_output_all_pause() has submitted the PAUSE command; bail @@ -491,46 +491,46 @@ AudioOutput::Task() continue; case Command::DRAIN: - if (open) { + if (output->open) { const ScopeUnlock unlock(mutex); - ao_plugin_drain(*this); + ao_plugin_drain(*output); } CommandFinished(); continue; case Command::CANCEL: - source.Cancel(); + output->source.Cancel(); - if (open) { + if (output->open) { const ScopeUnlock unlock(mutex); - ao_plugin_cancel(*this); + ao_plugin_cancel(*output); } CommandFinished(); continue; case Command::KILL: - Disable(); - source.Cancel(); + output->Disable(); + output->source.Cancel(); CommandFinished(); return; } - if (open && allow_play && Play()) + if (output->open && allow_play && Play()) /* don't wait for an event if there are more chunks in the pipe */ continue; if (command == Command::NONE) { woken_for_play = false; - cond.wait(mutex); + cond.wait(output->mutex); } } } void -AudioOutput::StartThread() +AudioOutputControl::StartThread() { assert(command == Command::NONE); diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 6b7f9a7f3..026429a5e 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -26,6 +26,7 @@ #include "mixer/MixerList.hxx" #include "pcm/PcmExport.hxx" #include "system/ByteOrder.hxx" +#include "thread/Cond.hxx" #include "util/Manual.hxx" #include "util/RuntimeError.hxx" #include "util/Domain.hxx" diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx index 762c5385a..6c0c940d8 100644 --- a/src/output/plugins/httpd/HttpdInternal.hxx +++ b/src/output/plugins/httpd/HttpdInternal.hxx @@ -30,6 +30,7 @@ #include "output/Internal.hxx" #include "output/Timer.hxx" #include "thread/Mutex.hxx" +#include "thread/Cond.hxx" #include "event/ServerSocket.hxx" #include "event/DeferredMonitor.hxx" #include "util/Cast.hxx" diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx index b00bf1909..96ed6a66d 100644 --- a/src/output/plugins/sles/SlesOutputPlugin.cxx +++ b/src/output/plugins/sles/SlesOutputPlugin.cxx @@ -25,6 +25,7 @@ #include "AndroidSimpleBufferQueue.hxx" #include "../../OutputAPI.hxx" #include "../../Wrapper.hxx" +#include "thread/Cond.hxx" #include "util/Macros.hxx" #include "util/Domain.hxx" #include "system/ByteOrder.hxx" diff --git a/test/run_output.cxx b/test/run_output.cxx index 13178957d..a4a39b826 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -43,8 +43,6 @@ #include #include -void AudioOutput::Task() {} - class DummyAudioOutputClient final : public AudioOutputClient { public: /* virtual methods from AudioOutputClient */