OutputAll: convert to class, move instance to class Partition

Another big chunk of code for multi-player support.
This commit is contained in:
Max Kellermann
2014-01-27 08:20:25 +01:00
parent 36bab6ef06
commit f5a923b9d1
26 changed files with 914 additions and 1007 deletions
+477
View File
@@ -0,0 +1,477 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "MultipleOutputs.hxx"
#include "PlayerControl.hxx"
#include "OutputInternal.hxx"
#include "OutputControl.hxx"
#include "OutputError.hxx"
#include "MusicBuffer.hxx"
#include "MusicPipe.hxx"
#include "MusicChunk.hxx"
#include "system/FatalError.hxx"
#include "util/Error.hxx"
#include "config/ConfigData.hxx"
#include "config/ConfigGlobal.hxx"
#include "config/ConfigOption.hxx"
#include "notify.hxx"
#include <assert.h>
#include <string.h>
MultipleOutputs::MultipleOutputs()
:buffer(nullptr), pipe(nullptr),
elapsed_time(-1)
{
}
MultipleOutputs::~MultipleOutputs()
{
for (auto i : outputs) {
audio_output_disable(i);
audio_output_finish(i);
}
}
static audio_output *
LoadOutput(PlayerControl &pc, const config_param &param)
{
Error error;
audio_output *output = audio_output_new(param, pc, error);
if (output == nullptr) {
if (param.line > 0)
FormatFatalError("line %i: %s",
param.line,
error.GetMessage());
else
FatalError(error);
}
return output;
}
void
MultipleOutputs::Configure(PlayerControl &pc)
{
const config_param *param = nullptr;
while ((param = config_get_next_param(CONF_AUDIO_OUTPUT,
param)) != nullptr) {
auto output = LoadOutput(pc, *param);
if (FindByName(output->name) != nullptr)
FormatFatalError("output devices with identical "
"names: %s", output->name);
outputs.push_back(output);
}
if (outputs.empty()) {
/* auto-detect device */
const config_param empty;
auto output = LoadOutput(pc, empty);
outputs.push_back(output);
}
}
audio_output *
MultipleOutputs::FindByName(const char *name) const
{
for (auto i : outputs)
if (strcmp(i->name, name) == 0)
return i;
return nullptr;
}
void
MultipleOutputs::EnableDisable()
{
for (auto ao : outputs) {
bool enabled;
ao->mutex.lock();
enabled = ao->really_enabled;
ao->mutex.unlock();
if (ao->enabled != enabled) {
if (ao->enabled)
audio_output_enable(ao);
else
audio_output_disable(ao);
}
}
}
bool
MultipleOutputs::AllFinished() const
{
for (auto ao : outputs) {
const ScopeLock protect(ao->mutex);
if (audio_output_is_open(ao) &&
!audio_output_command_is_finished(ao))
return false;
}
return true;
}
void
MultipleOutputs::WaitAll()
{
while (!AllFinished())
audio_output_client_notify.Wait();
}
void
MultipleOutputs::AllowPlay()
{
for (auto ao : outputs)
audio_output_allow_play(ao);
}
static void
audio_output_reset_reopen(struct audio_output *ao)
{
const ScopeLock protect(ao->mutex);
ao->fail_timer.Reset();
}
void
MultipleOutputs::ResetReopen()
{
for (auto ao : outputs)
audio_output_reset_reopen(ao);
}
bool
MultipleOutputs::Update()
{
bool ret = false;
if (!input_audio_format.IsDefined())
return false;
for (auto ao : outputs)
ret = audio_output_update(ao, input_audio_format, *pipe)
|| ret;
return ret;
}
void
MultipleOutputs::SetReplayGainMode(ReplayGainMode mode)
{
for (auto ao : outputs)
audio_output_set_replay_gain_mode(ao, mode);
}
bool
MultipleOutputs::Play(music_chunk *chunk, Error &error)
{
assert(buffer != nullptr);
assert(pipe != nullptr);
assert(chunk != nullptr);
assert(chunk->CheckFormat(input_audio_format));
if (!Update()) {
/* TODO: obtain real error */
error.Set(output_domain, "Failed to open audio output");
return false;
}
pipe->Push(chunk);
for (auto ao : outputs)
audio_output_play(ao);
return true;
}
bool
MultipleOutputs::Open(const AudioFormat audio_format,
MusicBuffer &_buffer,
Error &error)
{
bool ret = false, enabled = false;
assert(buffer == nullptr || buffer == &_buffer);
assert((pipe == nullptr) == (buffer == nullptr));
buffer = &_buffer;
/* the audio format must be the same as existing chunks in the
pipe */
assert(pipe == nullptr || pipe->CheckFormat(audio_format));
if (pipe == nullptr)
pipe = new MusicPipe();
else
/* if the pipe hasn't been cleared, the the audio
format must not have changed */
assert(pipe->IsEmpty() || audio_format == input_audio_format);
input_audio_format = audio_format;
ResetReopen();
EnableDisable();
Update();
for (auto ao : outputs) {
if (ao->enabled)
enabled = true;
if (ao->open)
ret = true;
}
if (!enabled)
error.Set(output_domain, "All audio outputs are disabled");
else if (!ret)
/* TODO: obtain real error */
error.Set(output_domain, "Failed to open audio output");
if (!ret)
/* close all devices if there was an error */
Close();
return ret;
}
/**
* Has the specified audio output already consumed this chunk?
*/
gcc_pure
static bool
chunk_is_consumed_in(const struct audio_output *ao,
gcc_unused const MusicPipe *pipe,
const struct music_chunk *chunk)
{
if (!ao->open)
return true;
if (ao->chunk == nullptr)
return false;
assert(chunk == ao->chunk || pipe->Contains(ao->chunk));
if (chunk != ao->chunk) {
assert(chunk->next != nullptr);
return true;
}
return ao->chunk_finished && chunk->next == nullptr;
}
bool
MultipleOutputs::IsChunkConsumed(const music_chunk *chunk) const
{
for (auto ao : outputs) {
const ScopeLock protect(ao->mutex);
if (!chunk_is_consumed_in(ao, pipe, chunk))
return false;
}
return true;
}
inline void
MultipleOutputs::ClearTailChunk(gcc_unused const struct music_chunk *chunk,
bool *locked)
{
assert(chunk->next == nullptr);
assert(pipe->Contains(chunk));
for (unsigned i = 0, n = outputs.size(); i != n; ++i) {
audio_output *ao = outputs[i];
/* this mutex will be unlocked by the caller when it's
ready */
ao->mutex.lock();
locked[i] = ao->open;
if (!locked[i]) {
ao->mutex.unlock();
continue;
}
assert(ao->chunk == chunk);
assert(ao->chunk_finished);
ao->chunk = nullptr;
}
}
unsigned
MultipleOutputs::Check()
{
const struct music_chunk *chunk;
bool is_tail;
struct music_chunk *shifted;
bool locked[outputs.size()];
assert(buffer != nullptr);
assert(pipe != nullptr);
while ((chunk = pipe->Peek()) != nullptr) {
assert(!pipe->IsEmpty());
if (!IsChunkConsumed(chunk))
/* at least one output is not finished playing
this chunk */
return pipe->GetSize();
if (chunk->length > 0 && chunk->times >= 0.0)
/* only update elapsed_time if the chunk
provides a defined value */
elapsed_time = chunk->times;
is_tail = chunk->next == nullptr;
if (is_tail)
/* this is the tail of the pipe - clear the
chunk reference in all outputs */
ClearTailChunk(chunk, locked);
/* remove the chunk from the pipe */
shifted = pipe->Shift();
assert(shifted == chunk);
if (is_tail)
/* unlock all audio outputs which were locked
by clear_tail_chunk() */
for (unsigned i = 0, n = outputs.size(); i != n; ++i)
if (locked[i])
outputs[i]->mutex.unlock();
/* return the chunk to the buffer */
buffer->Return(shifted);
}
return 0;
}
bool
MultipleOutputs::Wait(PlayerControl &pc, unsigned threshold)
{
pc.Lock();
if (Check() < threshold) {
pc.Unlock();
return true;
}
pc.Wait();
pc.Unlock();
return Check() < threshold;
}
void
MultipleOutputs::Pause()
{
Update();
for (auto ao : outputs)
audio_output_pause(ao);
WaitAll();
}
void
MultipleOutputs::Drain()
{
for (auto ao : outputs)
audio_output_drain_async(ao);
WaitAll();
}
void
MultipleOutputs::Cancel()
{
/* send the cancel() command to all audio outputs */
for (auto ao : outputs)
audio_output_cancel(ao);
WaitAll();
/* clear the music pipe and return all chunks to the buffer */
if (pipe != nullptr)
pipe->Clear(*buffer);
/* the audio outputs are now waiting for a signal, to
synchronize the cleared music pipe */
AllowPlay();
/* invalidate elapsed_time */
elapsed_time = -1.0;
}
void
MultipleOutputs::Close()
{
for (auto ao : outputs)
audio_output_close(ao);
if (pipe != nullptr) {
assert(buffer != nullptr);
pipe->Clear(*buffer);
delete pipe;
pipe = nullptr;
}
buffer = nullptr;
input_audio_format.Clear();
elapsed_time = -1.0;
}
void
MultipleOutputs::Release()
{
for (auto ao : outputs)
audio_output_release(ao);
if (pipe != nullptr) {
assert(buffer != nullptr);
pipe->Clear(*buffer);
delete pipe;
pipe = nullptr;
}
buffer = nullptr;
input_audio_format.Clear();
elapsed_time = -1.0;
}
void
MultipleOutputs::SongBorder()
{
/* clear the elapsed_time pointer at the beginning of a new
song */
elapsed_time = 0.0;
}
+271
View File
@@ -0,0 +1,271 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Functions for dealing with all configured (enabled) audion outputs
* at once.
*
*/
#ifndef OUTPUT_ALL_H
#define OUTPUT_ALL_H
#include "AudioFormat.hxx"
#include "ReplayGainInfo.hxx"
#include "Compiler.h"
#include <vector>
#include <assert.h>
struct AudioFormat;
class MusicBuffer;
class MusicPipe;
struct music_chunk;
struct PlayerControl;
struct audio_output;
class Error;
class MultipleOutputs {
std::vector<audio_output *> outputs;
AudioFormat input_audio_format;
/**
* The #MusicBuffer object where consumed chunks are returned.
*/
MusicBuffer *buffer;
/**
* The #MusicPipe object which feeds all audio outputs. It is
* filled by audio_output_all_play().
*/
MusicPipe *pipe;
/**
* The "elapsed_time" stamp of the most recently finished
* chunk.
*/
float elapsed_time;
public:
/**
* Load audio outputs from the configuration file and
* initialize them.
*/
MultipleOutputs();
~MultipleOutputs();
void Configure(PlayerControl &pc);
/**
* Returns the total number of audio output devices, including
* those which are disabled right now.
*/
gcc_pure
unsigned Size() const {
return outputs.size();
}
/**
* Returns the "i"th audio output device.
*/
const audio_output &Get(unsigned i) const {
assert(i < Size());
return *outputs[i];
}
audio_output &Get(unsigned i) {
assert(i < Size());
return *outputs[i];
}
/**
* Returns the audio output device with the specified name.
* Returns nullptr if the name does not exist.
*/
gcc_pure
audio_output *FindByName(const char *name) const;
/**
* Checks the "enabled" flag of all audio outputs, and if one has
* changed, commit the change.
*/
void EnableDisable();
/**
* Opens all audio outputs which are not disabled.
*
* @param audio_format the preferred audio format
* @param buffer the #music_buffer where consumed #music_chunk objects
* should be returned
* @return true on success, false on failure
*/
bool Open(const AudioFormat audio_format, MusicBuffer &_buffer,
Error &error);
/**
* Closes all audio outputs.
*/
void Close();
/**
* Closes all audio outputs. Outputs with the "always_on"
* flag are put into pause mode.
*/
void Release();
void SetReplayGainMode(ReplayGainMode mode);
/**
* Enqueue a #music_chunk object for playing, i.e. pushes it to a
* #MusicPipe.
*
* @param chunk the #music_chunk object to be played
* @return true on success, false if no audio output was able to play
* (all closed then)
*/
bool Play(music_chunk *chunk, Error &error);
/**
* Checks if the output devices have drained their music pipe, and
* returns the consumed music chunks to the #music_buffer.
*
* @return the number of chunks to play left in the #MusicPipe
*/
unsigned Check();
/**
* Checks if the size of the #MusicPipe is below the #threshold. If
* not, it attempts to synchronize with all output threads, and waits
* until another #music_chunk is finished.
*
* @param threshold the maximum number of chunks in the pipe
* @return true if there are less than #threshold chunks in the pipe
*/
bool Wait(PlayerControl &pc, unsigned threshold);
/**
* Puts all audio outputs into pause mode. Most implementations will
* simply close it then.
*/
void Pause();
/**
* Drain all audio outputs.
*/
void Drain();
/**
* Try to cancel data which may still be in the device's buffers.
*/
void Cancel();
/**
* Indicate that a new song will begin now.
*/
void SongBorder();
/**
* Returns the "elapsed_time" stamp of the most recently finished
* chunk. A negative value is returned when no chunk has been
* finished yet.
*/
gcc_pure
float GetElapsedTime() const {
return elapsed_time;
}
/**
* Returns the average volume of all available mixers (range
* 0..100). Returns -1 if no mixer can be queried.
*/
gcc_pure
int GetVolume() const;
/**
* Sets the volume on all available mixers.
*
* @param volume the volume (range 0..100)
* @return true on success, false on failure
*/
bool SetVolume(unsigned volume);
/**
* Similar to GetVolume(), but gets the volume only for
* software mixers. See #software_mixer_plugin. This
* function fails if no software mixer is configured.
*/
gcc_pure
int GetSoftwareVolume() const;
/**
* Similar to SetVolume(), but sets the volume only for
* software mixers. See #software_mixer_plugin. This
* function cannot fail, because the underlying software
* mixers cannot fail either.
*/
void SetSoftwareVolume(unsigned volume);
private:
/**
* Determine if all (active) outputs have finished the current
* command.
*/
gcc_pure
bool AllFinished() const;
void WaitAll();
/**
* Signals all audio outputs which are open.
*/
void AllowPlay();
/**
* Resets the "reopen" flag on all audio devices. MPD should
* immediately retry to open the device instead of waiting for
* the timeout when the user wants to start playback.
*/
void ResetReopen();
/**
* Opens all output devices which are enabled, but closed.
*
* @return true if there is at least open output device which
* is open
*/
bool Update();
/**
* Has this chunk been consumed by all audio outputs?
*/
bool IsChunkConsumed(const music_chunk *chunk) const;
/**
* There's only one chunk left in the pipe (#pipe), and all
* audio outputs have consumed it already. Clear the
* reference.
*/
void ClearTailChunk(const struct music_chunk *chunk, bool *locked);
};
#endif
-589
View File
@@ -1,589 +0,0 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "OutputAll.hxx"
#include "PlayerControl.hxx"
#include "OutputInternal.hxx"
#include "OutputControl.hxx"
#include "OutputError.hxx"
#include "MusicBuffer.hxx"
#include "MusicPipe.hxx"
#include "MusicChunk.hxx"
#include "system/FatalError.hxx"
#include "util/Error.hxx"
#include "config/ConfigData.hxx"
#include "config/ConfigGlobal.hxx"
#include "config/ConfigOption.hxx"
#include "notify.hxx"
#include <assert.h>
#include <string.h>
static AudioFormat input_audio_format;
static struct audio_output **audio_outputs;
static unsigned int num_audio_outputs;
/**
* The #MusicBuffer object where consumed chunks are returned.
*/
static MusicBuffer *g_music_buffer;
/**
* The #MusicPipe object which feeds all audio outputs. It is filled
* by audio_output_all_play().
*/
static MusicPipe *g_mp;
/**
* The "elapsed_time" stamp of the most recently finished chunk.
*/
static float audio_output_all_elapsed_time = -1.0;
unsigned int audio_output_count(void)
{
return num_audio_outputs;
}
struct audio_output *
audio_output_get(unsigned i)
{
assert(i < num_audio_outputs);
assert(audio_outputs[i] != nullptr);
return audio_outputs[i];
}
struct audio_output *
audio_output_find(const char *name)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
struct audio_output *ao = audio_output_get(i);
if (strcmp(ao->name, name) == 0)
return ao;
}
/* name not found */
return nullptr;
}
gcc_const
static unsigned
audio_output_config_count(void)
{
unsigned int nr = 0;
const struct config_param *param = nullptr;
while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
nr++;
if (!nr)
nr = 1; /* we'll always have at least one device */
return nr;
}
void
audio_output_all_init(PlayerControl &pc)
{
const struct config_param *param = nullptr;
unsigned int i;
Error error;
num_audio_outputs = audio_output_config_count();
audio_outputs = new audio_output *[num_audio_outputs];
const config_param empty;
for (i = 0; i < num_audio_outputs; i++)
{
unsigned int j;
param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
if (param == nullptr) {
/* only allow param to be nullptr if there
just one audio output */
assert(i == 0);
assert(num_audio_outputs == 1);
param = &empty;
}
audio_output *output = audio_output_new(*param, pc, error);
if (output == nullptr) {
if (param != nullptr)
FormatFatalError("line %i: %s",
param->line,
error.GetMessage());
else
FatalError(error);
}
audio_outputs[i] = output;
/* require output names to be unique: */
for (j = 0; j < i; j++) {
if (!strcmp(output->name, audio_outputs[j]->name)) {
FormatFatalError("output devices with identical "
"names: %s", output->name);
}
}
}
}
void
audio_output_all_finish(void)
{
unsigned int i;
for (i = 0; i < num_audio_outputs; i++) {
audio_output_disable(audio_outputs[i]);
audio_output_finish(audio_outputs[i]);
}
delete[] audio_outputs;
audio_outputs = nullptr;
num_audio_outputs = 0;
}
void
audio_output_all_enable_disable(void)
{
for (unsigned i = 0; i < num_audio_outputs; i++) {
struct audio_output *ao = audio_outputs[i];
bool enabled;
ao->mutex.lock();
enabled = ao->really_enabled;
ao->mutex.unlock();
if (ao->enabled != enabled) {
if (ao->enabled)
audio_output_enable(ao);
else
audio_output_disable(ao);
}
}
}
/**
* Determine if all (active) outputs have finished the current
* command.
*/
static bool
audio_output_all_finished(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
struct audio_output *ao = audio_outputs[i];
const ScopeLock protect(ao->mutex);
if (audio_output_is_open(ao) &&
!audio_output_command_is_finished(ao))
return false;
}
return true;
}
static void audio_output_wait_all(void)
{
while (!audio_output_all_finished())
audio_output_client_notify.Wait();
}
/**
* Signals all audio outputs which are open.
*/
static void
audio_output_allow_play_all(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
audio_output_allow_play(audio_outputs[i]);
}
static void
audio_output_reset_reopen(struct audio_output *ao)
{
const ScopeLock protect(ao->mutex);
ao->fail_timer.Reset();
}
/**
* Resets the "reopen" flag on all audio devices. MPD should
* immediately retry to open the device instead of waiting for the
* timeout when the user wants to start playback.
*/
static void
audio_output_all_reset_reopen(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
struct audio_output *ao = audio_outputs[i];
audio_output_reset_reopen(ao);
}
}
/**
* Opens all output devices which are enabled, but closed.
*
* @return true if there is at least open output device which is open
*/
static bool
audio_output_all_update(void)
{
unsigned int i;
bool ret = false;
if (!input_audio_format.IsDefined())
return false;
for (i = 0; i < num_audio_outputs; ++i)
ret = audio_output_update(audio_outputs[i],
input_audio_format, *g_mp) || ret;
return ret;
}
void
audio_output_all_set_replay_gain_mode(ReplayGainMode mode)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
audio_output_set_replay_gain_mode(audio_outputs[i], mode);
}
bool
audio_output_all_play(struct music_chunk *chunk, Error &error)
{
bool ret;
unsigned int i;
assert(g_music_buffer != nullptr);
assert(g_mp != nullptr);
assert(chunk != nullptr);
assert(chunk->CheckFormat(input_audio_format));
ret = audio_output_all_update();
if (!ret) {
/* TODO: obtain real error */
error.Set(output_domain, "Failed to open audio output");
return false;
}
g_mp->Push(chunk);
for (i = 0; i < num_audio_outputs; ++i)
audio_output_play(audio_outputs[i]);
return true;
}
bool
audio_output_all_open(const AudioFormat audio_format,
MusicBuffer &buffer,
Error &error)
{
bool ret = false, enabled = false;
unsigned int i;
assert(g_music_buffer == nullptr || g_music_buffer == &buffer);
assert((g_mp == nullptr) == (g_music_buffer == nullptr));
g_music_buffer = &buffer;
/* the audio format must be the same as existing chunks in the
pipe */
assert(g_mp == nullptr || g_mp->CheckFormat(audio_format));
if (g_mp == nullptr)
g_mp = new MusicPipe();
else
/* if the pipe hasn't been cleared, the the audio
format must not have changed */
assert(g_mp->IsEmpty() || audio_format == input_audio_format);
input_audio_format = audio_format;
audio_output_all_reset_reopen();
audio_output_all_enable_disable();
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i) {
if (audio_outputs[i]->enabled)
enabled = true;
if (audio_outputs[i]->open)
ret = true;
}
if (!enabled)
error.Set(output_domain, "All audio outputs are disabled");
else if (!ret)
/* TODO: obtain real error */
error.Set(output_domain, "Failed to open audio output");
if (!ret)
/* close all devices if there was an error */
audio_output_all_close();
return ret;
}
/**
* Has the specified audio output already consumed this chunk?
*/
static bool
chunk_is_consumed_in(const struct audio_output *ao,
const struct music_chunk *chunk)
{
if (!ao->open)
return true;
if (ao->chunk == nullptr)
return false;
assert(chunk == ao->chunk || g_mp->Contains(ao->chunk));
if (chunk != ao->chunk) {
assert(chunk->next != nullptr);
return true;
}
return ao->chunk_finished && chunk->next == nullptr;
}
/**
* Has this chunk been consumed by all audio outputs?
*/
static bool
chunk_is_consumed(const struct music_chunk *chunk)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
struct audio_output *ao = audio_outputs[i];
const ScopeLock protect(ao->mutex);
if (!chunk_is_consumed_in(ao, chunk))
return false;
}
return true;
}
/**
* There's only one chunk left in the pipe (#g_mp), and all audio
* outputs have consumed it already. Clear the reference.
*/
static void
clear_tail_chunk(gcc_unused const struct music_chunk *chunk, bool *locked)
{
assert(chunk->next == nullptr);
assert(g_mp->Contains(chunk));
for (unsigned i = 0; i < num_audio_outputs; ++i) {
struct audio_output *ao = audio_outputs[i];
/* this mutex will be unlocked by the caller when it's
ready */
ao->mutex.lock();
locked[i] = ao->open;
if (!locked[i]) {
ao->mutex.unlock();
continue;
}
assert(ao->chunk == chunk);
assert(ao->chunk_finished);
ao->chunk = nullptr;
}
}
unsigned
audio_output_all_check(void)
{
const struct music_chunk *chunk;
bool is_tail;
struct music_chunk *shifted;
bool locked[num_audio_outputs];
assert(g_music_buffer != nullptr);
assert(g_mp != nullptr);
while ((chunk = g_mp->Peek()) != nullptr) {
assert(!g_mp->IsEmpty());
if (!chunk_is_consumed(chunk))
/* at least one output is not finished playing
this chunk */
return g_mp->GetSize();
if (chunk->length > 0 && chunk->times >= 0.0)
/* only update elapsed_time if the chunk
provides a defined value */
audio_output_all_elapsed_time = chunk->times;
is_tail = chunk->next == nullptr;
if (is_tail)
/* this is the tail of the pipe - clear the
chunk reference in all outputs */
clear_tail_chunk(chunk, locked);
/* remove the chunk from the pipe */
shifted = g_mp->Shift();
assert(shifted == chunk);
if (is_tail)
/* unlock all audio outputs which were locked
by clear_tail_chunk() */
for (unsigned i = 0; i < num_audio_outputs; ++i)
if (locked[i])
audio_outputs[i]->mutex.unlock();
/* return the chunk to the buffer */
g_music_buffer->Return(shifted);
}
return 0;
}
bool
audio_output_all_wait(PlayerControl &pc, unsigned threshold)
{
pc.Lock();
if (audio_output_all_check() < threshold) {
pc.Unlock();
return true;
}
pc.Wait();
pc.Unlock();
return audio_output_all_check() < threshold;
}
void
audio_output_all_pause(void)
{
unsigned int i;
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
audio_output_pause(audio_outputs[i]);
audio_output_wait_all();
}
void
audio_output_all_drain(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
audio_output_drain_async(audio_outputs[i]);
audio_output_wait_all();
}
void
audio_output_all_cancel(void)
{
unsigned int i;
/* send the cancel() command to all audio outputs */
for (i = 0; i < num_audio_outputs; ++i)
audio_output_cancel(audio_outputs[i]);
audio_output_wait_all();
/* clear the music pipe and return all chunks to the buffer */
if (g_mp != nullptr)
g_mp->Clear(*g_music_buffer);
/* the audio outputs are now waiting for a signal, to
synchronize the cleared music pipe */
audio_output_allow_play_all();
/* invalidate elapsed_time */
audio_output_all_elapsed_time = -1.0;
}
void
audio_output_all_close(void)
{
unsigned int i;
for (i = 0; i < num_audio_outputs; ++i)
audio_output_close(audio_outputs[i]);
if (g_mp != nullptr) {
assert(g_music_buffer != nullptr);
g_mp->Clear(*g_music_buffer);
delete g_mp;
g_mp = nullptr;
}
g_music_buffer = nullptr;
input_audio_format.Clear();
audio_output_all_elapsed_time = -1.0;
}
void
audio_output_all_release(void)
{
unsigned int i;
for (i = 0; i < num_audio_outputs; ++i)
audio_output_release(audio_outputs[i]);
if (g_mp != nullptr) {
assert(g_music_buffer != nullptr);
g_mp->Clear(*g_music_buffer);
delete g_mp;
g_mp = nullptr;
}
g_music_buffer = nullptr;
input_audio_format.Clear();
audio_output_all_elapsed_time = -1.0;
}
void
audio_output_all_song_border(void)
{
/* clear the elapsed_time pointer at the beginning of a new
song */
audio_output_all_elapsed_time = 0.0;
}
float
audio_output_all_get_elapsed_time(void)
{
return audio_output_all_elapsed_time;
}
-174
View File
@@ -1,174 +0,0 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Functions for dealing with all configured (enabled) audion outputs
* at once.
*
*/
#ifndef OUTPUT_ALL_H
#define OUTPUT_ALL_H
#include "ReplayGainInfo.hxx"
#include "Compiler.h"
struct AudioFormat;
class MusicBuffer;
struct music_chunk;
struct PlayerControl;
class Error;
/**
* Global initialization: load audio outputs from the configuration
* file and initialize them.
*/
void
audio_output_all_init(PlayerControl &pc);
/**
* Global finalization: free memory occupied by audio outputs. All
*/
void
audio_output_all_finish(void);
/**
* Returns the total number of audio output devices, including those
* who are disabled right now.
*/
gcc_const
unsigned int audio_output_count(void);
/**
* Returns the "i"th audio output device.
*/
gcc_const
struct audio_output *
audio_output_get(unsigned i);
/**
* Returns the audio output device with the specified name. Returns
* NULL if the name does not exist.
*/
gcc_pure
struct audio_output *
audio_output_find(const char *name);
/**
* Checks the "enabled" flag of all audio outputs, and if one has
* changed, commit the change.
*/
void
audio_output_all_enable_disable(void);
/**
* Opens all audio outputs which are not disabled.
*
* @param audio_format the preferred audio format
* @param buffer the #music_buffer where consumed #music_chunk objects
* should be returned
* @return true on success, false on failure
*/
bool
audio_output_all_open(AudioFormat audio_format,
MusicBuffer &buffer,
Error &error);
/**
* Closes all audio outputs.
*/
void
audio_output_all_close(void);
/**
* Closes all audio outputs. Outputs with the "always_on" flag are
* put into pause mode.
*/
void
audio_output_all_release(void);
void
audio_output_all_set_replay_gain_mode(ReplayGainMode mode);
/**
* Enqueue a #music_chunk object for playing, i.e. pushes it to a
* #MusicPipe.
*
* @param chunk the #music_chunk object to be played
* @return true on success, false if no audio output was able to play
* (all closed then)
*/
bool
audio_output_all_play(music_chunk *chunk, Error &error);
/**
* Checks if the output devices have drained their music pipe, and
* returns the consumed music chunks to the #music_buffer.
*
* @return the number of chunks to play left in the #MusicPipe
*/
unsigned
audio_output_all_check(void);
/**
* Checks if the size of the #MusicPipe is below the #threshold. If
* not, it attempts to synchronize with all output threads, and waits
* until another #music_chunk is finished.
*
* @param threshold the maximum number of chunks in the pipe
* @return true if there are less than #threshold chunks in the pipe
*/
bool
audio_output_all_wait(PlayerControl &pc, unsigned threshold);
/**
* Puts all audio outputs into pause mode. Most implementations will
* simply close it then.
*/
void
audio_output_all_pause(void);
/**
* Drain all audio outputs.
*/
void
audio_output_all_drain(void);
/**
* Try to cancel data which may still be in the device's buffers.
*/
void
audio_output_all_cancel(void);
/**
* Indicate that a new song will begin now.
*/
void
audio_output_all_song_border(void);
/**
* Returns the "elapsed_time" stamp of the most recently finished
* chunk. A negative value is returned when no chunk has been
* finished yet.
*/
gcc_pure
float
audio_output_all_get_elapsed_time(void);
#endif
+20 -26
View File
@@ -26,7 +26,7 @@
#include "config.h"
#include "OutputCommand.hxx"
#include "OutputAll.hxx"
#include "MultipleOutputs.hxx"
#include "OutputInternal.hxx"
#include "PlayerControl.hxx"
#include "mixer/MixerControl.hxx"
@@ -35,21 +35,19 @@
extern unsigned audio_output_state_version;
bool
audio_output_enable_index(unsigned idx)
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
if (idx >= outputs.Size())
return false;
ao = audio_output_get(idx);
if (ao->enabled)
audio_output &ao = outputs.Get(idx);
if (ao.enabled)
return true;
ao->enabled = true;
ao.enabled = true;
idle_add(IDLE_OUTPUT);
ao->player_control->UpdateAudio();
ao.player_control->UpdateAudio();
++audio_output_state_version;
@@ -57,27 +55,25 @@ audio_output_enable_index(unsigned idx)
}
bool
audio_output_disable_index(unsigned idx)
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
if (idx >= outputs.Size())
return false;
ao = audio_output_get(idx);
if (!ao->enabled)
audio_output &ao = outputs.Get(idx);
if (!ao.enabled)
return true;
ao->enabled = false;
ao.enabled = false;
idle_add(IDLE_OUTPUT);
Mixer *mixer = ao->mixer;
Mixer *mixer = ao.mixer;
if (mixer != nullptr) {
mixer_close(mixer);
idle_add(IDLE_MIXER);
}
ao->player_control->UpdateAudio();
ao.player_control->UpdateAudio();
++audio_output_state_version;
@@ -85,26 +81,24 @@ audio_output_disable_index(unsigned idx)
}
bool
audio_output_toggle_index(unsigned idx)
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
if (idx >= outputs.Size())
return false;
ao = audio_output_get(idx);
const bool enabled = ao->enabled = !ao->enabled;
audio_output &ao = outputs.Get(idx);
const bool enabled = ao.enabled = !ao.enabled;
idle_add(IDLE_OUTPUT);
if (!enabled) {
Mixer *mixer = ao->mixer;
Mixer *mixer = ao.mixer;
if (mixer != nullptr) {
mixer_close(mixer);
idle_add(IDLE_MIXER);
}
}
ao->player_control->UpdateAudio();
ao.player_control->UpdateAudio();
++audio_output_state_version;
+5 -3
View File
@@ -27,25 +27,27 @@
#ifndef MPD_OUTPUT_COMMAND_HXX
#define MPD_OUTPUT_COMMAND_HXX
class MultipleOutputs;
/**
* Enables an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_enable_index(unsigned idx);
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx);
/**
* Disables an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_disable_index(unsigned idx);
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx);
/**
* Toggles an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_toggle_index(unsigned idx);
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
#endif
+5 -7
View File
@@ -24,22 +24,20 @@
#include "config.h"
#include "OutputPrint.hxx"
#include "OutputAll.hxx"
#include "MultipleOutputs.hxx"
#include "OutputInternal.hxx"
#include "client/Client.hxx"
void
printAudioDevices(Client &client)
printAudioDevices(Client &client, const MultipleOutputs &outputs)
{
const unsigned n = audio_output_count();
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
const audio_output &ao = outputs.Get(i);
client_printf(client,
"outputid: %i\n"
"outputname: %s\n"
"outputenabled: %i\n",
i, ao->name, ao->enabled);
i, ao.name, ao.enabled);
}
}
+2 -2
View File
@@ -1,4 +1,3 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
@@ -27,8 +26,9 @@
#define MPD_OUTPUT_PRINT_HXX
class Client;
class MultipleOutputs;
void
printAudioDevices(Client &client);
printAudioDevices(Client &client, const MultipleOutputs &outputs);
#endif
+7 -12
View File
@@ -24,7 +24,7 @@
#include "config.h"
#include "OutputState.hxx"
#include "OutputAll.hxx"
#include "MultipleOutputs.hxx"
#include "OutputInternal.hxx"
#include "OutputError.hxx"
#include "Log.hxx"
@@ -38,27 +38,22 @@
unsigned audio_output_state_version;
void
audio_output_state_save(FILE *fp)
audio_output_state_save(FILE *fp, const MultipleOutputs &outputs)
{
unsigned n = audio_output_count();
assert(n > 0);
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
const audio_output &ao = outputs.Get(i);
fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
ao->enabled, ao->name);
ao.enabled, ao.name);
}
}
bool
audio_output_state_read(const char *line)
audio_output_state_read(const char *line, MultipleOutputs &outputs)
{
long value;
char *endptr;
const char *name;
struct audio_output *ao;
if (!StringStartsWith(line, AUDIO_DEVICE_STATE))
return false;
@@ -74,7 +69,7 @@ audio_output_state_read(const char *line)
return true;
name = endptr + 1;
ao = audio_output_find(name);
audio_output *ao = outputs.FindByName(name);
if (ao == NULL) {
FormatDebug(output_domain,
"Ignoring device state for '%s'", name);
+4 -2
View File
@@ -27,11 +27,13 @@
#include <stdio.h>
class MultipleOutputs;
bool
audio_output_state_read(const char *line);
audio_output_state_read(const char *line, MultipleOutputs &outputs);
void
audio_output_state_save(FILE *fp);
audio_output_state_save(FILE *fp, const MultipleOutputs &outputs);
/**
* Generates a version number for the current state of the audio