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;
|
struct notify audio_output_client_notify;
|
||||||
|
|
||||||
AudioOutputControl::AudioOutputControl(AudioOutput *_output)
|
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();
|
return output->IsOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
AudioOutputControl::IsBusy() const
|
|
||||||
{
|
|
||||||
return output->IsBusy();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::exception_ptr &
|
|
||||||
AudioOutputControl::GetLastError() const
|
|
||||||
{
|
|
||||||
return output->GetLastError();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::WaitForCommand()
|
AudioOutputControl::WaitForCommand()
|
||||||
{
|
{
|
||||||
while (!IsCommandFinished()) {
|
while (!IsCommandFinished()) {
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
@ -114,13 +104,7 @@ AudioOutput::WaitForCommand()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutputControl::WaitForCommand()
|
AudioOutputControl::CommandAsync(Command cmd)
|
||||||
{
|
|
||||||
output->WaitForCommand();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
AudioOutput::CommandAsync(Command cmd)
|
|
||||||
{
|
{
|
||||||
assert(IsCommandFinished());
|
assert(IsCommandFinished());
|
||||||
|
|
||||||
@ -129,28 +113,28 @@ AudioOutput::CommandAsync(Command cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::CommandWait(Command cmd)
|
AudioOutputControl::CommandWait(Command cmd)
|
||||||
{
|
{
|
||||||
CommandAsync(cmd);
|
CommandAsync(cmd);
|
||||||
WaitForCommand();
|
WaitForCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockCommandWait(Command cmd)
|
AudioOutputControl::LockCommandWait(Command cmd)
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
CommandWait(cmd);
|
CommandWait(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::EnableAsync()
|
AudioOutputControl::EnableAsync()
|
||||||
{
|
{
|
||||||
if (!thread.IsDefined()) {
|
if (!thread.IsDefined()) {
|
||||||
if (plugin.enable == nullptr) {
|
if (output->plugin.enable == nullptr) {
|
||||||
/* don't bother to start the thread now if the
|
/* don't bother to start the thread now if the
|
||||||
device doesn't even have a enable() method;
|
device doesn't even have a enable() method;
|
||||||
just assign the variable and we're done */
|
just assign the variable and we're done */
|
||||||
really_enabled = true;
|
output->really_enabled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,15 +145,15 @@ AudioOutput::EnableAsync()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::DisableAsync()
|
AudioOutputControl::DisableAsync()
|
||||||
{
|
{
|
||||||
if (!thread.IsDefined()) {
|
if (!thread.IsDefined()) {
|
||||||
if (plugin.disable == nullptr)
|
if (output->plugin.disable == nullptr)
|
||||||
really_enabled = false;
|
output->really_enabled = false;
|
||||||
else
|
else
|
||||||
/* if there's no thread yet, the device cannot
|
/* if there's no thread yet, the device cannot
|
||||||
be enabled */
|
be enabled */
|
||||||
assert(!really_enabled);
|
assert(!output->really_enabled);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -180,27 +164,27 @@ AudioOutput::DisableAsync()
|
|||||||
void
|
void
|
||||||
AudioOutputControl::EnableDisableAsync()
|
AudioOutputControl::EnableDisableAsync()
|
||||||
{
|
{
|
||||||
output->EnableDisableAsync();
|
if (output->enabled == output->really_enabled)
|
||||||
}
|
return;
|
||||||
|
|
||||||
void
|
if (output->enabled)
|
||||||
AudioOutputControl::LockPauseAsync()
|
EnableAsync();
|
||||||
{
|
else
|
||||||
output->LockPauseAsync();
|
DisableAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
|
AudioOutputControl::Open(const AudioFormat audio_format, const MusicPipe &mp)
|
||||||
{
|
{
|
||||||
assert(allow_play);
|
assert(allow_play);
|
||||||
assert(audio_format.IsValid());
|
assert(audio_format.IsValid());
|
||||||
|
|
||||||
fail_timer.Reset();
|
fail_timer.Reset();
|
||||||
|
|
||||||
if (open && audio_format == request.audio_format) {
|
if (output->open && audio_format == request.audio_format) {
|
||||||
assert(request.pipe == &mp || (always_on && pause));
|
assert(request.pipe == &mp || (output->always_on && output->pause));
|
||||||
|
|
||||||
if (!pause)
|
if (!output->pause)
|
||||||
/* already open, already the right parameters
|
/* already open, already the right parameters
|
||||||
- nothing needs to be done */
|
- nothing needs to be done */
|
||||||
return true;
|
return true;
|
||||||
@ -213,13 +197,14 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
|
|||||||
StartThread();
|
StartThread();
|
||||||
|
|
||||||
CommandWait(Command::OPEN);
|
CommandWait(Command::OPEN);
|
||||||
const bool open2 = open;
|
const bool open2 = output->open;
|
||||||
|
|
||||||
if (open2 && mixer != nullptr) {
|
if (open2 && output->mixer != nullptr) {
|
||||||
try {
|
try {
|
||||||
mixer_open(mixer);
|
mixer_open(output->mixer);
|
||||||
} catch (const std::runtime_error &e) {
|
} 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
|
void
|
||||||
AudioOutput::CloseWait()
|
AudioOutputControl::CloseWait()
|
||||||
{
|
{
|
||||||
assert(allow_play);
|
assert(allow_play);
|
||||||
|
|
||||||
if (mixer != nullptr)
|
if (output->mixer != nullptr)
|
||||||
mixer_auto_close(mixer);
|
mixer_auto_close(output->mixer);
|
||||||
|
|
||||||
assert(!open || !fail_timer.IsDefined());
|
assert(!output->open || !fail_timer.IsDefined());
|
||||||
|
|
||||||
if (open)
|
if (output->open)
|
||||||
CommandWait(Command::CLOSE);
|
CommandWait(Command::CLOSE);
|
||||||
else
|
else
|
||||||
fail_timer.Reset();
|
fail_timer.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AudioOutput::LockUpdate(const AudioFormat audio_format,
|
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
|
||||||
const MusicPipe &mp,
|
const MusicPipe &mp,
|
||||||
bool force)
|
bool force)
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
|
|
||||||
if (enabled && really_enabled) {
|
if (output->enabled && output->really_enabled) {
|
||||||
if (force || !fail_timer.IsDefined() ||
|
if (force || !fail_timer.IsDefined() ||
|
||||||
fail_timer.Check(REOPEN_AFTER * 1000)) {
|
fail_timer.Check(REOPEN_AFTER * 1000)) {
|
||||||
return Open(audio_format, mp);
|
return Open(audio_format, mp);
|
||||||
@ -260,26 +245,6 @@ AudioOutput::LockUpdate(const AudioFormat audio_format,
|
|||||||
return false;
|
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
|
bool
|
||||||
AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const
|
AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const
|
||||||
{
|
{
|
||||||
@ -293,7 +258,7 @@ AudioOutputControl::ClearTailChunk(const MusicChunk &chunk)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockPlay()
|
AudioOutputControl::LockPlay()
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
|
|
||||||
@ -306,13 +271,13 @@ AudioOutput::LockPlay()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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,
|
/* the device has no pause mode: close the mixer,
|
||||||
unless its "global" flag is set (checked by
|
unless its "global" flag is set (checked by
|
||||||
mixer_auto_close()) */
|
mixer_auto_close()) */
|
||||||
mixer_auto_close(mixer);
|
mixer_auto_close(output->mixer);
|
||||||
|
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
|
|
||||||
@ -322,7 +287,7 @@ AudioOutput::LockPauseAsync()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockDrainAsync()
|
AudioOutputControl::LockDrainAsync()
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
|
|
||||||
@ -332,7 +297,7 @@ AudioOutput::LockDrainAsync()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockCancelAsync()
|
AudioOutputControl::LockCancelAsync()
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
|
|
||||||
@ -343,7 +308,7 @@ AudioOutput::LockCancelAsync()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockAllowPlay()
|
AudioOutputControl::LockAllowPlay()
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
|
|
||||||
@ -353,18 +318,18 @@ AudioOutput::LockAllowPlay()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockRelease()
|
AudioOutputControl::LockRelease()
|
||||||
{
|
{
|
||||||
if (always_on)
|
if (output->always_on)
|
||||||
LockPauseAsync();
|
LockPauseAsync();
|
||||||
else
|
else
|
||||||
LockCloseWait();
|
LockCloseWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockCloseWait()
|
AudioOutputControl::LockCloseWait()
|
||||||
{
|
{
|
||||||
assert(!open || !fail_timer.IsDefined());
|
assert(!output->open || !fail_timer.IsDefined());
|
||||||
|
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
CloseWait();
|
CloseWait();
|
||||||
@ -377,7 +342,7 @@ AudioOutputControl::SetReplayGainMode(ReplayGainMode _mode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::StopThread()
|
AudioOutputControl::StopThread()
|
||||||
{
|
{
|
||||||
assert(thread.IsDefined());
|
assert(thread.IsDefined());
|
||||||
assert(allow_play);
|
assert(allow_play);
|
||||||
@ -391,6 +356,12 @@ AudioOutput::BeginDestroy()
|
|||||||
{
|
{
|
||||||
if (mixer != nullptr)
|
if (mixer != nullptr)
|
||||||
mixer_auto_close(mixer);
|
mixer_auto_close(mixer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AudioOutputControl::BeginDestroy()
|
||||||
|
{
|
||||||
|
output->BeginDestroy();
|
||||||
|
|
||||||
if (thread.IsDefined()) {
|
if (thread.IsDefined()) {
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
@ -398,48 +369,18 @@ AudioOutput::BeginDestroy()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
AudioOutputControl::BeginDestroy()
|
|
||||||
{
|
|
||||||
output->BeginDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::FinishDestroy()
|
AudioOutput::FinishDestroy()
|
||||||
{
|
{
|
||||||
if (thread.IsDefined())
|
|
||||||
thread.Join();
|
|
||||||
|
|
||||||
audio_output_free(this);
|
audio_output_free(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutputControl::FinishDestroy()
|
AudioOutputControl::FinishDestroy()
|
||||||
{
|
{
|
||||||
|
if (thread.IsDefined())
|
||||||
|
thread.Join();
|
||||||
|
|
||||||
output->FinishDestroy();
|
output->FinishDestroy();
|
||||||
output = nullptr;
|
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
|
#ifndef MPD_OUTPUT_CONTROL_HXX
|
||||||
#define 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 "Compiler.h"
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@ -32,7 +36,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
enum class ReplayGainMode : uint8_t;
|
enum class ReplayGainMode : uint8_t;
|
||||||
struct AudioFormat;
|
|
||||||
struct AudioOutput;
|
struct AudioOutput;
|
||||||
struct MusicChunk;
|
struct MusicChunk;
|
||||||
class MusicPipe;
|
class MusicPipe;
|
||||||
@ -46,6 +49,99 @@ class AudioOutputClient;
|
|||||||
class AudioOutputControl {
|
class AudioOutputControl {
|
||||||
AudioOutput *output;
|
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:
|
public:
|
||||||
Mutex &mutex;
|
Mutex &mutex;
|
||||||
|
|
||||||
@ -53,6 +149,8 @@ public:
|
|||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
~AudioOutputControl() {
|
~AudioOutputControl() {
|
||||||
|
assert(!fail_timer.IsDefined());
|
||||||
|
assert(!thread.IsDefined());
|
||||||
assert(output == nullptr);
|
assert(output == nullptr);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -84,28 +182,103 @@ public:
|
|||||||
gcc_pure
|
gcc_pure
|
||||||
bool IsOpen() const;
|
bool IsOpen() const;
|
||||||
|
|
||||||
gcc_pure
|
/**
|
||||||
bool IsBusy() const;
|
* Caller must lock the mutex.
|
||||||
|
*/
|
||||||
|
bool IsBusy() const {
|
||||||
|
return IsOpen() && !IsCommandFinished();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caller must lock the mutex.
|
* 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();
|
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 BeginDestroy();
|
||||||
void FinishDestroy();
|
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 EnableDisableAsync();
|
||||||
void LockPauseAsync();
|
void LockPauseAsync();
|
||||||
|
|
||||||
|
void CloseWait();
|
||||||
void LockCloseWait();
|
void LockCloseWait();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the audio output, but if the "always_on" flag is set, put it
|
||||||
|
* into pause mode instead.
|
||||||
|
*/
|
||||||
void LockRelease();
|
void LockRelease();
|
||||||
|
|
||||||
void SetReplayGainMode(ReplayGainMode _mode);
|
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"
|
* Opens or closes the device, depending on the "enabled"
|
||||||
* flag.
|
* flag.
|
||||||
@ -124,8 +297,48 @@ public:
|
|||||||
|
|
||||||
void LockPlay();
|
void LockPlay();
|
||||||
void LockDrainAsync();
|
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();
|
void LockCancelAsync();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the "allow_play" and signal the thread.
|
||||||
|
*/
|
||||||
void LockAllowPlay();
|
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
|
#endif
|
||||||
|
@ -28,8 +28,6 @@
|
|||||||
AudioOutput::~AudioOutput()
|
AudioOutput::~AudioOutput()
|
||||||
{
|
{
|
||||||
assert(!open);
|
assert(!open);
|
||||||
assert(!fail_timer.IsDefined());
|
|
||||||
assert(!thread.IsDefined());
|
|
||||||
|
|
||||||
if (mixer != nullptr)
|
if (mixer != nullptr)
|
||||||
mixer_free(mixer);
|
mixer_free(mixer);
|
||||||
@ -43,8 +41,6 @@ void
|
|||||||
audio_output_free(AudioOutput *ao)
|
audio_output_free(AudioOutput *ao)
|
||||||
{
|
{
|
||||||
assert(!ao->IsOpen());
|
assert(!ao->IsOpen());
|
||||||
assert(!ao->fail_timer.IsDefined());
|
|
||||||
assert(!ao->thread.IsDefined());
|
|
||||||
|
|
||||||
ao_plugin_finish(ao);
|
ao_plugin_finish(ao);
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,7 @@
|
|||||||
|
|
||||||
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin,
|
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin,
|
||||||
const ConfigBlock &block)
|
const ConfigBlock &block)
|
||||||
:plugin(_plugin),
|
:plugin(_plugin)
|
||||||
thread(BIND_THIS_METHOD(Task))
|
|
||||||
{
|
{
|
||||||
assert(plugin.finish != nullptr);
|
assert(plugin.finish != nullptr);
|
||||||
assert(plugin.open != nullptr);
|
assert(plugin.open != nullptr);
|
||||||
|
@ -21,15 +21,9 @@
|
|||||||
#define MPD_OUTPUT_INTERNAL_HXX
|
#define MPD_OUTPUT_INTERNAL_HXX
|
||||||
|
|
||||||
#include "Source.hxx"
|
#include "Source.hxx"
|
||||||
#include "SharedPipeConsumer.hxx"
|
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "filter/Observer.hxx"
|
#include "filter/Observer.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
#include "thread/Cond.hxx"
|
|
||||||
#include "thread/Thread.hxx"
|
|
||||||
#include "system/PeriodClock.hxx"
|
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
class PreparedFilter;
|
class PreparedFilter;
|
||||||
class MusicPipe;
|
class MusicPipe;
|
||||||
@ -43,30 +37,6 @@ struct AudioOutputPlugin;
|
|||||||
struct ReplayGainConfig;
|
struct ReplayGainConfig;
|
||||||
|
|
||||||
struct AudioOutput {
|
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.
|
* The device's configured display name.
|
||||||
*/
|
*/
|
||||||
@ -124,37 +94,6 @@ struct AudioOutput {
|
|||||||
*/
|
*/
|
||||||
bool pause = false;
|
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.
|
* The configured audio format.
|
||||||
*/
|
*/
|
||||||
@ -209,43 +148,11 @@ struct AudioOutput {
|
|||||||
*/
|
*/
|
||||||
FilterObserver convert_filter;
|
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.
|
* This mutex protects #open, #fail_timer, #pipe.
|
||||||
*/
|
*/
|
||||||
mutable Mutex mutex;
|
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
|
* The PlayerControl object which "owns" this output. This
|
||||||
* object is needed to signal command completion.
|
* object is needed to signal command completion.
|
||||||
@ -257,14 +164,6 @@ struct AudioOutput {
|
|||||||
*/
|
*/
|
||||||
AudioOutputSource source;
|
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.
|
* Throws #std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
@ -282,9 +181,6 @@ public:
|
|||||||
MixerListener &mixer_listener,
|
MixerListener &mixer_listener,
|
||||||
const ConfigBlock &block);
|
const ConfigBlock &block);
|
||||||
|
|
||||||
void StartThread();
|
|
||||||
void StopThread();
|
|
||||||
|
|
||||||
void BeginDestroy();
|
void BeginDestroy();
|
||||||
void FinishDestroy();
|
void FinishDestroy();
|
||||||
|
|
||||||
@ -306,137 +202,10 @@ public:
|
|||||||
return open;
|
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) {
|
void SetReplayGainMode(ReplayGainMode _mode) {
|
||||||
source.SetReplayGainMode(_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?
|
* Did we already consumed this chunk?
|
||||||
*
|
*
|
||||||
@ -455,9 +224,6 @@ public:
|
|||||||
source.ClearTailChunk(chunk);
|
source.ClearTailChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
void CommandFinished();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws #std::runtime_error on error.
|
* Throws #std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
@ -468,8 +234,11 @@ private:
|
|||||||
/**
|
/**
|
||||||
* Throws #std::runtime_error on error.
|
* 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
|
* Invoke OutputPlugin::open() and configure the
|
||||||
* #ConvertFilter.
|
* #ConvertFilter.
|
||||||
@ -480,8 +249,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
void OpenOutputAndConvert(AudioFormat audio_format);
|
void OpenOutputAndConvert(AudioFormat audio_format);
|
||||||
|
|
||||||
void Close(bool drain);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the output plugin.
|
* Close the output plugin.
|
||||||
*
|
*
|
||||||
@ -494,37 +261,13 @@ private:
|
|||||||
*/
|
*/
|
||||||
void CloseFilter();
|
void CloseFilter();
|
||||||
|
|
||||||
/**
|
public:
|
||||||
* 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 BeginPause();
|
void BeginPause();
|
||||||
bool IteratePause();
|
bool IteratePause();
|
||||||
|
|
||||||
void Pause();
|
void EndPause() {
|
||||||
|
pause = false;
|
||||||
/**
|
}
|
||||||
* The OutputThread.
|
|
||||||
*/
|
|
||||||
void Task();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::CommandFinished()
|
AudioOutputControl::CommandFinished()
|
||||||
{
|
{
|
||||||
assert(command != Command::NONE);
|
assert(command != Command::NONE);
|
||||||
command = Command::NONE;
|
command = Command::NONE;
|
||||||
@ -98,11 +98,9 @@ AudioOutput::CloseFilter()
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
AudioOutput::Open()
|
AudioOutput::Open(AudioFormat audio_format, const MusicPipe &pipe)
|
||||||
{
|
{
|
||||||
assert(request.audio_format.IsValid());
|
assert(audio_format.IsValid());
|
||||||
|
|
||||||
fail_timer.Reset();
|
|
||||||
|
|
||||||
/* enable the device (just in case the last enable has failed) */
|
/* enable the device (just in case the last enable has failed) */
|
||||||
Enable();
|
Enable();
|
||||||
@ -110,7 +108,7 @@ AudioOutput::Open()
|
|||||||
AudioFormat f;
|
AudioFormat f;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
f = source.Open(request.audio_format, *request.pipe,
|
f = source.Open(audio_format, pipe,
|
||||||
prepared_replay_gain_filter,
|
prepared_replay_gain_filter,
|
||||||
prepared_other_replay_gain_filter,
|
prepared_other_replay_gain_filter,
|
||||||
prepared_filter);
|
prepared_filter);
|
||||||
@ -239,10 +237,10 @@ AudioOutput::CloseOutput(bool drain)
|
|||||||
* was issued
|
* was issued
|
||||||
*/
|
*/
|
||||||
inline bool
|
inline bool
|
||||||
AudioOutput::WaitForDelay()
|
AudioOutputControl::WaitForDelay()
|
||||||
{
|
{
|
||||||
while (true) {
|
while (true) {
|
||||||
const auto delay = ao_plugin_delay(*this);
|
const auto delay = ao_plugin_delay(*output);
|
||||||
if (delay <= std::chrono::steady_clock::duration::zero())
|
if (delay <= std::chrono::steady_clock::duration::zero())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -254,14 +252,14 @@ AudioOutput::WaitForDelay()
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AudioOutput::FillSourceOrClose()
|
AudioOutputControl::FillSourceOrClose()
|
||||||
try {
|
try {
|
||||||
return source.Fill(mutex);
|
return output->source.Fill(mutex);
|
||||||
} catch (const std::runtime_error &e) {
|
} catch (const std::runtime_error &e) {
|
||||||
FormatError(e, "Failed to filter for output \"%s\" [%s]",
|
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
|
/* don't automatically reopen this device for 10
|
||||||
seconds */
|
seconds */
|
||||||
@ -270,23 +268,23 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
AudioOutput::PlayChunk()
|
AudioOutputControl::PlayChunk()
|
||||||
{
|
{
|
||||||
if (tags) {
|
if (output->tags) {
|
||||||
const auto *tag = source.ReadTag();
|
const auto *tag = output->source.ReadTag();
|
||||||
if (tag != nullptr) {
|
if (tag != nullptr) {
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
try {
|
try {
|
||||||
ao_plugin_send_tag(*this, *tag);
|
ao_plugin_send_tag(*output, *tag);
|
||||||
} catch (const std::runtime_error &e) {
|
} catch (const std::runtime_error &e) {
|
||||||
FormatError(e, "Failed to send tag to \"%s\" [%s]",
|
FormatError(e, "Failed to send tag to \"%s\" [%s]",
|
||||||
name, plugin.name);
|
GetName(), output->plugin.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (command == Command::NONE) {
|
while (command == Command::NONE) {
|
||||||
const auto data = source.PeekData();
|
const auto data = output->source.PeekData();
|
||||||
if (data.IsEmpty())
|
if (data.IsEmpty())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -297,17 +295,17 @@ AudioOutput::PlayChunk()
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const ScopeUnlock unlock(mutex);
|
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);
|
assert(nbytes <= data.size);
|
||||||
} catch (const std::runtime_error &e) {
|
} catch (const std::runtime_error &e) {
|
||||||
FormatError(e, "\"%s\" [%s] failed to play",
|
FormatError(e, "\"%s\" [%s] failed to play",
|
||||||
name, plugin.name);
|
GetName(), output->plugin.name);
|
||||||
|
|
||||||
nbytes = 0;
|
nbytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbytes == 0) {
|
if (nbytes == 0) {
|
||||||
Close(false);
|
output->Close(false);
|
||||||
|
|
||||||
/* don't automatically reopen this device for
|
/* don't automatically reopen this device for
|
||||||
10 seconds */
|
10 seconds */
|
||||||
@ -317,16 +315,16 @@ AudioOutput::PlayChunk()
|
|||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
AudioOutput::Play()
|
AudioOutputControl::Play()
|
||||||
{
|
{
|
||||||
if (!FillSourceOrClose())
|
if (!FillSourceOrClose())
|
||||||
/* no chunk available */
|
/* no chunk available */
|
||||||
@ -351,7 +349,7 @@ AudioOutput::Play()
|
|||||||
give it a chance to refill the pipe before
|
give it a chance to refill the pipe before
|
||||||
it runs empty */
|
it runs empty */
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
client->ChunksConsumed();
|
output->client->ChunksConsumed();
|
||||||
n = 0;
|
n = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,7 +358,7 @@ AudioOutput::Play()
|
|||||||
} while (FillSourceOrClose());
|
} while (FillSourceOrClose());
|
||||||
|
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
client->ChunksConsumed();
|
output->client->ChunksConsumed();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -397,26 +395,26 @@ AudioOutput::IteratePause()
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
AudioOutput::Pause()
|
AudioOutputControl::Pause()
|
||||||
{
|
{
|
||||||
BeginPause();
|
output->BeginPause();
|
||||||
CommandFinished();
|
CommandFinished();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!WaitForDelay())
|
if (!WaitForDelay())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (!IteratePause())
|
if (!output->IteratePause())
|
||||||
break;
|
break;
|
||||||
} while (command == Command::NONE);
|
} while (command == Command::NONE);
|
||||||
|
|
||||||
pause = false;
|
output->EndPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::Task()
|
AudioOutputControl::Task()
|
||||||
{
|
{
|
||||||
FormatThreadName("output:%s", name);
|
FormatThreadName("output:%s", GetName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SetThreadRealtime();
|
SetThreadRealtime();
|
||||||
@ -438,7 +436,7 @@ AudioOutput::Task()
|
|||||||
last_error = nullptr;
|
last_error = nullptr;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Enable();
|
output->Enable();
|
||||||
} catch (const std::runtime_error &e) {
|
} catch (const std::runtime_error &e) {
|
||||||
LogError(e);
|
LogError(e);
|
||||||
fail_timer.Update();
|
fail_timer.Update();
|
||||||
@ -449,15 +447,17 @@ AudioOutput::Task()
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::DISABLE:
|
case Command::DISABLE:
|
||||||
Disable();
|
output->Disable();
|
||||||
CommandFinished();
|
CommandFinished();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::OPEN:
|
case Command::OPEN:
|
||||||
last_error = nullptr;
|
last_error = nullptr;
|
||||||
|
fail_timer.Reset();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Open();
|
output->Open(request.audio_format,
|
||||||
|
*request.pipe);
|
||||||
} catch (const std::runtime_error &e) {
|
} catch (const std::runtime_error &e) {
|
||||||
LogError(e);
|
LogError(e);
|
||||||
fail_timer.Update();
|
fail_timer.Update();
|
||||||
@ -468,13 +468,13 @@ AudioOutput::Task()
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::CLOSE:
|
case Command::CLOSE:
|
||||||
if (open)
|
if (IsOpen())
|
||||||
Close(false);
|
output->Close(false);
|
||||||
CommandFinished();
|
CommandFinished();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::PAUSE:
|
case Command::PAUSE:
|
||||||
if (!open) {
|
if (!output->open) {
|
||||||
/* the output has failed after
|
/* the output has failed after
|
||||||
audio_output_all_pause() has
|
audio_output_all_pause() has
|
||||||
submitted the PAUSE command; bail
|
submitted the PAUSE command; bail
|
||||||
@ -491,46 +491,46 @@ AudioOutput::Task()
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
case Command::DRAIN:
|
case Command::DRAIN:
|
||||||
if (open) {
|
if (output->open) {
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
ao_plugin_drain(*this);
|
ao_plugin_drain(*output);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandFinished();
|
CommandFinished();
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case Command::CANCEL:
|
case Command::CANCEL:
|
||||||
source.Cancel();
|
output->source.Cancel();
|
||||||
|
|
||||||
if (open) {
|
if (output->open) {
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
ao_plugin_cancel(*this);
|
ao_plugin_cancel(*output);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandFinished();
|
CommandFinished();
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case Command::KILL:
|
case Command::KILL:
|
||||||
Disable();
|
output->Disable();
|
||||||
source.Cancel();
|
output->source.Cancel();
|
||||||
CommandFinished();
|
CommandFinished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open && allow_play && Play())
|
if (output->open && allow_play && Play())
|
||||||
/* don't wait for an event if there are more
|
/* don't wait for an event if there are more
|
||||||
chunks in the pipe */
|
chunks in the pipe */
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (command == Command::NONE) {
|
if (command == Command::NONE) {
|
||||||
woken_for_play = false;
|
woken_for_play = false;
|
||||||
cond.wait(mutex);
|
cond.wait(output->mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::StartThread()
|
AudioOutputControl::StartThread()
|
||||||
{
|
{
|
||||||
assert(command == Command::NONE);
|
assert(command == Command::NONE);
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "mixer/MixerList.hxx"
|
#include "mixer/MixerList.hxx"
|
||||||
#include "pcm/PcmExport.hxx"
|
#include "pcm/PcmExport.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "system/ByteOrder.hxx"
|
||||||
|
#include "thread/Cond.hxx"
|
||||||
#include "util/Manual.hxx"
|
#include "util/Manual.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "output/Internal.hxx"
|
#include "output/Internal.hxx"
|
||||||
#include "output/Timer.hxx"
|
#include "output/Timer.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
|
#include "thread/Cond.hxx"
|
||||||
#include "event/ServerSocket.hxx"
|
#include "event/ServerSocket.hxx"
|
||||||
#include "event/DeferredMonitor.hxx"
|
#include "event/DeferredMonitor.hxx"
|
||||||
#include "util/Cast.hxx"
|
#include "util/Cast.hxx"
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "AndroidSimpleBufferQueue.hxx"
|
#include "AndroidSimpleBufferQueue.hxx"
|
||||||
#include "../../OutputAPI.hxx"
|
#include "../../OutputAPI.hxx"
|
||||||
#include "../../Wrapper.hxx"
|
#include "../../Wrapper.hxx"
|
||||||
|
#include "thread/Cond.hxx"
|
||||||
#include "util/Macros.hxx"
|
#include "util/Macros.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "system/ByteOrder.hxx"
|
||||||
|
@ -43,8 +43,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
void AudioOutput::Task() {}
|
|
||||||
|
|
||||||
class DummyAudioOutputClient final : public AudioOutputClient {
|
class DummyAudioOutputClient final : public AudioOutputClient {
|
||||||
public:
|
public:
|
||||||
/* virtual methods from AudioOutputClient */
|
/* virtual methods from AudioOutputClient */
|
||||||
|
Loading…
Reference in New Issue
Block a user