summaryrefslogtreecommitdiff
path: root/src/output/Control.hxx
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2017-04-28 21:45:47 +0200
committerMax Kellermann <max@musicpd.org>2017-04-28 22:04:30 +0200
commit89b900432e0bfb324356f6ae62a09241eb75d251 (patch)
treed43df69bf9084c00d7a2257ab8e2e80c0217c8aa /src/output/Control.hxx
parent8bb9d0960bb44ee9e274ae2dde5ed8240c376622 (diff)
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.
Diffstat (limited to 'src/output/Control.hxx')
-rw-r--r--src/output/Control.hxx223
1 files changed, 218 insertions, 5 deletions
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 <utility>
@@ -32,7 +36,6 @@
#include <stdint.h>
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,29 +182,104 @@ 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