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.
This commit is contained in:
parent
8bb9d0960b
commit
89b900432e
|
@ -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<Mutex> 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<Mutex> 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<Mutex> 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<Mutex> protect(mutex);
|
||||
|
||||
|
@ -322,7 +287,7 @@ AudioOutput::LockPauseAsync()
|
|||
}
|
||||
|
||||
void
|
||||
AudioOutput::LockDrainAsync()
|
||||
AudioOutputControl::LockDrainAsync()
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
|
||||
|
@ -332,7 +297,7 @@ AudioOutput::LockDrainAsync()
|
|||
}
|
||||
|
||||
void
|
||||
AudioOutput::LockCancelAsync()
|
||||
AudioOutputControl::LockCancelAsync()
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
|
||||
|
@ -343,7 +308,7 @@ AudioOutput::LockCancelAsync()
|
|||
}
|
||||
|
||||
void
|
||||
AudioOutput::LockAllowPlay()
|
||||
AudioOutputControl::LockAllowPlay()
|
||||
{
|
||||
const std::lock_guard<Mutex> 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<Mutex> 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<Mutex> 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();
|
||||
}
|
||||
|
|
|
@ -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,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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <exception>
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
#include <string.h>
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -43,8 +43,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void AudioOutput::Task() {}
|
||||
|
||||
class DummyAudioOutputClient final : public AudioOutputClient {
|
||||
public:
|
||||
/* virtual methods from AudioOutputClient */
|
||||
|
|
Loading…
Reference in New Issue