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:
Max Kellermann 2017-04-28 21:45:47 +02:00
parent 8bb9d0960b
commit 89b900432e
10 changed files with 337 additions and 444 deletions

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}
};
/**

View File

@ -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);

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -43,8 +43,6 @@
#include <stdlib.h>
#include <stdio.h>
void AudioOutput::Task() {}
class DummyAudioOutputClient final : public AudioOutputClient {
public:
/* virtual methods from AudioOutputClient */