From 89b900432e0bfb324356f6ae62a09241eb75d251 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 28 Apr 2017 21:45:47 +0200 Subject: 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.hxx | 223 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 218 insertions(+), 5 deletions(-) (limited to 'src/output/Control.hxx') 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. + */ + const std::exception_ptr &GetLastError() const { + return last_error; + } + + void StartThread(); + void StopThread(); /** * Caller must lock the mutex. */ - gcc_const - const std::exception_ptr &GetLastError() const; + 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 -- cgit v1.2.3