output/*: move to output/plugins/

This commit is contained in:
Max Kellermann
2014-01-23 23:49:50 +01:00
parent f1f19841bd
commit ea5b901bcc
71 changed files with 103 additions and 91 deletions

33
src/output/OutputAPI.hxx Normal file
View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
#ifndef MPD_OUTPUT_API_HXX
#define MPD_OUTPUT_API_HXX
// IWYU pragma: begin_exports
#include "OutputPlugin.hxx"
#include "OutputInternal.hxx"
#include "AudioFormat.hxx"
#include "tag/Tag.hxx"
#include "ConfigData.hxx"
// IWYU pragma: end_exports
#endif

589
src/output/OutputAll.cxx Normal file
View File

@@ -0,0 +1,589 @@
/*
* 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 "ConfigData.hxx"
#include "ConfigGlobal.hxx"
#include "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
src/output/OutputAll.hxx Normal file
View File

@@ -0,0 +1,174 @@
/*
* 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

View File

@@ -0,0 +1,112 @@
/*
* 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.
*/
/*
* Glue functions for controlling the audio outputs over the MPD
* protocol. These functions perform extra validation on all
* parameters, because they might be from an untrusted source.
*
*/
#include "config.h"
#include "OutputCommand.hxx"
#include "OutputAll.hxx"
#include "OutputInternal.hxx"
#include "PlayerControl.hxx"
#include "MixerControl.hxx"
#include "Idle.hxx"
extern unsigned audio_output_state_version;
bool
audio_output_enable_index(unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
return false;
ao = audio_output_get(idx);
if (ao->enabled)
return true;
ao->enabled = true;
idle_add(IDLE_OUTPUT);
ao->player_control->UpdateAudio();
++audio_output_state_version;
return true;
}
bool
audio_output_disable_index(unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
return false;
ao = audio_output_get(idx);
if (!ao->enabled)
return true;
ao->enabled = false;
idle_add(IDLE_OUTPUT);
Mixer *mixer = ao->mixer;
if (mixer != nullptr) {
mixer_close(mixer);
idle_add(IDLE_MIXER);
}
ao->player_control->UpdateAudio();
++audio_output_state_version;
return true;
}
bool
audio_output_toggle_index(unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
return false;
ao = audio_output_get(idx);
const bool enabled = ao->enabled = !ao->enabled;
idle_add(IDLE_OUTPUT);
if (!enabled) {
Mixer *mixer = ao->mixer;
if (mixer != nullptr) {
mixer_close(mixer);
idle_add(IDLE_MIXER);
}
}
ao->player_control->UpdateAudio();
++audio_output_state_version;
return true;
}

View File

@@ -0,0 +1,51 @@
/*
* 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.
*/
/*
* Glue functions for controlling the audio outputs over the MPD
* protocol. These functions perform extra validation on all
* parameters, because they might be from an untrusted source.
*
*/
#ifndef MPD_OUTPUT_COMMAND_HXX
#define MPD_OUTPUT_COMMAND_HXX
/**
* Enables an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_enable_index(unsigned idx);
/**
* Disables an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_disable_index(unsigned idx);
/**
* Toggles an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_toggle_index(unsigned idx);
#endif

View File

@@ -0,0 +1,325 @@
/*
* 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 "OutputControl.hxx"
#include "OutputThread.hxx"
#include "OutputInternal.hxx"
#include "OutputPlugin.hxx"
#include "OutputError.hxx"
#include "MixerControl.hxx"
#include "notify.hxx"
#include "filter/ReplayGainFilterPlugin.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include <assert.h>
/** after a failure, wait this number of seconds before
automatically reopening the device */
static constexpr unsigned REOPEN_AFTER = 10;
struct notify audio_output_client_notify;
/**
* Waits for command completion.
*
* @param ao the #audio_output instance; must be locked
*/
static void ao_command_wait(struct audio_output *ao)
{
while (ao->command != AO_COMMAND_NONE) {
ao->mutex.unlock();
audio_output_client_notify.Wait();
ao->mutex.lock();
}
}
/**
* Sends a command to the #audio_output object, but does not wait for
* completion.
*
* @param ao the #audio_output instance; must be locked
*/
static void ao_command_async(struct audio_output *ao,
enum audio_output_command cmd)
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
ao->cond.signal();
}
/**
* Sends a command to the #audio_output object and waits for
* completion.
*
* @param ao the #audio_output instance; must be locked
*/
static void
ao_command(struct audio_output *ao, enum audio_output_command cmd)
{
ao_command_async(ao, cmd);
ao_command_wait(ao);
}
/**
* Lock the #audio_output object and execute the command
* synchronously.
*/
static void
ao_lock_command(struct audio_output *ao, enum audio_output_command cmd)
{
const ScopeLock protect(ao->mutex);
ao_command(ao, cmd);
}
void
audio_output_set_replay_gain_mode(struct audio_output *ao,
ReplayGainMode mode)
{
if (ao->replay_gain_filter != nullptr)
replay_gain_filter_set_mode(ao->replay_gain_filter, mode);
if (ao->other_replay_gain_filter != nullptr)
replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode);
}
void
audio_output_enable(struct audio_output *ao)
{
if (!ao->thread.IsDefined()) {
if (ao->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 */
ao->really_enabled = true;
return;
}
audio_output_thread_start(ao);
}
ao_lock_command(ao, AO_COMMAND_ENABLE);
}
void
audio_output_disable(struct audio_output *ao)
{
if (!ao->thread.IsDefined()) {
if (ao->plugin->disable == nullptr)
ao->really_enabled = false;
else
/* if there's no thread yet, the device cannot
be enabled */
assert(!ao->really_enabled);
return;
}
ao_lock_command(ao, AO_COMMAND_DISABLE);
}
/**
* Object must be locked (and unlocked) by the caller.
*/
static bool
audio_output_open(struct audio_output *ao,
const AudioFormat audio_format,
const MusicPipe &mp)
{
bool open;
assert(ao != nullptr);
assert(ao->allow_play);
assert(audio_format.IsValid());
ao->fail_timer.Reset();
if (ao->open && audio_format == ao->in_audio_format) {
assert(ao->pipe == &mp ||
(ao->always_on && ao->pause));
if (ao->pause) {
ao->chunk = nullptr;
ao->pipe = &mp;
/* unpause with the CANCEL command; this is a
hack, but suits well for forcing the thread
to leave the ao_pause() thread, and we need
to flush the device buffer anyway */
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
ao_command(ao, AO_COMMAND_CANCEL);
}
return true;
}
ao->in_audio_format = audio_format;
ao->chunk = nullptr;
ao->pipe = &mp;
if (!ao->thread.IsDefined())
audio_output_thread_start(ao);
ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
open = ao->open;
if (open && ao->mixer != nullptr) {
Error error;
if (!mixer_open(ao->mixer, error))
FormatWarning(output_domain,
"Failed to open mixer for '%s'",
ao->name);
}
return open;
}
/**
* Same as audio_output_close(), but expects the lock to be held by
* the caller.
*/
static void
audio_output_close_locked(struct audio_output *ao)
{
assert(ao != nullptr);
assert(ao->allow_play);
if (ao->mixer != nullptr)
mixer_auto_close(ao->mixer);
assert(!ao->open || !ao->fail_timer.IsDefined());
if (ao->open)
ao_command(ao, AO_COMMAND_CLOSE);
else
ao->fail_timer.Reset();
}
bool
audio_output_update(struct audio_output *ao,
const AudioFormat audio_format,
const MusicPipe &mp)
{
const ScopeLock protect(ao->mutex);
if (ao->enabled && ao->really_enabled) {
if (ao->fail_timer.Check(REOPEN_AFTER * 1000)) {
return audio_output_open(ao, audio_format, mp);
}
} else if (audio_output_is_open(ao))
audio_output_close_locked(ao);
return false;
}
void
audio_output_play(struct audio_output *ao)
{
const ScopeLock protect(ao->mutex);
assert(ao->allow_play);
if (audio_output_is_open(ao) && !ao->in_playback_loop &&
!ao->woken_for_play) {
ao->woken_for_play = true;
ao->cond.signal();
}
}
void audio_output_pause(struct audio_output *ao)
{
if (ao->mixer != nullptr && ao->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(ao->mixer);
const ScopeLock protect(ao->mutex);
assert(ao->allow_play);
if (audio_output_is_open(ao))
ao_command_async(ao, AO_COMMAND_PAUSE);
}
void
audio_output_drain_async(struct audio_output *ao)
{
const ScopeLock protect(ao->mutex);
assert(ao->allow_play);
if (audio_output_is_open(ao))
ao_command_async(ao, AO_COMMAND_DRAIN);
}
void audio_output_cancel(struct audio_output *ao)
{
const ScopeLock protect(ao->mutex);
if (audio_output_is_open(ao)) {
ao->allow_play = false;
ao_command_async(ao, AO_COMMAND_CANCEL);
}
}
void
audio_output_allow_play(struct audio_output *ao)
{
const ScopeLock protect(ao->mutex);
ao->allow_play = true;
if (audio_output_is_open(ao))
ao->cond.signal();
}
void
audio_output_release(struct audio_output *ao)
{
if (ao->always_on)
audio_output_pause(ao);
else
audio_output_close(ao);
}
void audio_output_close(struct audio_output *ao)
{
assert(ao != nullptr);
assert(!ao->open || !ao->fail_timer.IsDefined());
const ScopeLock protect(ao->mutex);
audio_output_close_locked(ao);
}
void audio_output_finish(struct audio_output *ao)
{
audio_output_close(ao);
assert(!ao->fail_timer.IsDefined());
if (ao->thread.IsDefined()) {
assert(ao->allow_play);
ao_lock_command(ao, AO_COMMAND_KILL);
ao->thread.Join();
}
audio_output_free(ao);
}

View File

@@ -0,0 +1,94 @@
/*
* 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.
*/
#ifndef MPD_OUTPUT_CONTROL_HXX
#define MPD_OUTPUT_CONTROL_HXX
#include "ReplayGainInfo.hxx"
#include <stddef.h>
struct audio_output;
struct AudioFormat;
struct config_param;
class MusicPipe;
void
audio_output_set_replay_gain_mode(audio_output *ao,
ReplayGainMode mode);
/**
* Enables the device.
*/
void
audio_output_enable(audio_output *ao);
/**
* Disables the device.
*/
void
audio_output_disable(audio_output *ao);
/**
* Opens or closes the device, depending on the "enabled" flag.
*
* @return true if the device is open
*/
bool
audio_output_update(audio_output *ao,
AudioFormat audio_format,
const MusicPipe &mp);
void
audio_output_play(audio_output *ao);
void
audio_output_pause(audio_output *ao);
void
audio_output_drain_async(audio_output *ao);
/**
* Clear the "allow_play" flag and send the "CANCEL" command
* asynchronously. To finish the operation, the caller has to call
* audio_output_allow_play().
*/
void
audio_output_cancel(audio_output *ao);
/**
* Set the "allow_play" and signal the thread.
*/
void
audio_output_allow_play(audio_output *ao);
void
audio_output_close(audio_output *ao);
/**
* Closes the audio output, but if the "always_on" flag is set, put it
* into pause mode instead.
*/
void
audio_output_release(audio_output *ao);
void
audio_output_finish(audio_output *ao);
#endif

View File

@@ -0,0 +1,23 @@
/*
* 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 "OutputError.hxx"
#include "util/Domain.hxx"
const Domain output_domain("output");

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
#ifndef MPD_OUTPUT_ERROR_HXX
#define MPD_OUTPUT_ERROR_HXX
extern const class Domain output_domain;
#endif

View File

@@ -0,0 +1,51 @@
/*
* 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 "OutputInternal.hxx"
#include "OutputPlugin.hxx"
#include "MixerControl.hxx"
#include "FilterInternal.hxx"
#include <assert.h>
void
ao_base_finish(struct audio_output *ao)
{
assert(!ao->open);
assert(!ao->fail_timer.IsDefined());
assert(!ao->thread.IsDefined());
if (ao->mixer != nullptr)
mixer_free(ao->mixer);
delete ao->replay_gain_filter;
delete ao->other_replay_gain_filter;
delete ao->filter;
}
void
audio_output_free(struct audio_output *ao)
{
assert(!ao->open);
assert(!ao->fail_timer.IsDefined());
assert(!ao->thread.IsDefined());
ao_plugin_finish(ao);
}

329
src/output/OutputInit.cxx Normal file
View File

@@ -0,0 +1,329 @@
/*
* 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 "OutputInternal.hxx"
#include "OutputList.hxx"
#include "OutputError.hxx"
#include "OutputAPI.hxx"
#include "FilterConfig.hxx"
#include "AudioParser.hxx"
#include "MixerList.hxx"
#include "MixerType.hxx"
#include "MixerControl.hxx"
#include "mixer/SoftwareMixerPlugin.hxx"
#include "FilterPlugin.hxx"
#include "FilterRegistry.hxx"
#include "filter/AutoConvertFilterPlugin.hxx"
#include "filter/ReplayGainFilterPlugin.hxx"
#include "filter/ChainFilterPlugin.hxx"
#include "ConfigError.hxx"
#include "ConfigGlobal.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include <assert.h>
#include <string.h>
#define AUDIO_OUTPUT_TYPE "type"
#define AUDIO_OUTPUT_NAME "name"
#define AUDIO_OUTPUT_FORMAT "format"
#define AUDIO_FILTERS "filters"
static const struct audio_output_plugin *
audio_output_detect(Error &error)
{
LogDefault(output_domain, "Attempt to detect audio output device");
audio_output_plugins_for_each(plugin) {
if (plugin->test_default_device == nullptr)
continue;
FormatDefault(output_domain,
"Attempting to detect a %s audio device",
plugin->name);
if (ao_plugin_test_default_device(plugin))
return plugin;
}
error.Set(output_domain, "Unable to detect an audio device");
return nullptr;
}
/**
* Determines the mixer type which should be used for the specified
* configuration block.
*
* This handles the deprecated options mixer_type (global) and
* mixer_enabled, if the mixer_type setting is not configured.
*/
gcc_pure
static enum mixer_type
audio_output_mixer_type(const config_param &param)
{
/* read the local "mixer_type" setting */
const char *p = param.GetBlockValue("mixer_type");
if (p != nullptr)
return mixer_type_parse(p);
/* try the local "mixer_enabled" setting next (deprecated) */
if (!param.GetBlockValue("mixer_enabled", true))
return MIXER_TYPE_NONE;
/* fall back to the global "mixer_type" setting (also
deprecated) */
return mixer_type_parse(config_get_string(CONF_MIXER_TYPE,
"hardware"));
}
static Mixer *
audio_output_load_mixer(struct audio_output *ao,
const config_param &param,
const struct mixer_plugin *plugin,
Filter &filter_chain,
Error &error)
{
Mixer *mixer;
switch (audio_output_mixer_type(param)) {
case MIXER_TYPE_NONE:
case MIXER_TYPE_UNKNOWN:
return nullptr;
case MIXER_TYPE_HARDWARE:
if (plugin == nullptr)
return nullptr;
return mixer_new(plugin, ao, param, error);
case MIXER_TYPE_SOFTWARE:
mixer = mixer_new(&software_mixer_plugin, nullptr,
config_param(),
IgnoreError());
assert(mixer != nullptr);
filter_chain_append(filter_chain, "software_mixer",
software_mixer_get_filter(mixer));
return mixer;
}
assert(false);
gcc_unreachable();
}
bool
ao_base_init(struct audio_output *ao,
const struct audio_output_plugin *plugin,
const config_param &param, Error &error)
{
assert(ao != nullptr);
assert(plugin != nullptr);
assert(plugin->finish != nullptr);
assert(plugin->open != nullptr);
assert(plugin->close != nullptr);
assert(plugin->play != nullptr);
if (!param.IsNull()) {
ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
if (ao->name == nullptr) {
error.Set(config_domain,
"Missing \"name\" configuration");
return false;
}
const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT);
if (p != nullptr) {
bool success =
audio_format_parse(ao->config_audio_format,
p, true, error);
if (!success)
return false;
} else
ao->config_audio_format.Clear();
} else {
ao->name = "default detected output";
ao->config_audio_format.Clear();
}
ao->plugin = plugin;
ao->tags = param.GetBlockValue("tags", true);
ao->always_on = param.GetBlockValue("always_on", false);
ao->enabled = param.GetBlockValue("enabled", true);
ao->really_enabled = false;
ao->open = false;
ao->pause = false;
ao->allow_play = true;
ao->in_playback_loop = false;
ao->woken_for_play = false;
/* set up the filter chain */
ao->filter = filter_chain_new();
assert(ao->filter != nullptr);
/* create the normalization filter (if configured) */
if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
Filter *normalize_filter =
filter_new(&normalize_filter_plugin, config_param(),
IgnoreError());
assert(normalize_filter != nullptr);
filter_chain_append(*ao->filter, "normalize",
autoconvert_filter_new(normalize_filter));
}
Error filter_error;
filter_chain_parse(*ao->filter,
param.GetBlockValue(AUDIO_FILTERS, ""),
filter_error);
// It's not really fatal - Part of the filter chain has been set up already
// and even an empty one will work (if only with unexpected behaviour)
if (filter_error.IsDefined())
FormatError(filter_error,
"Failed to initialize filter chain for '%s'",
ao->name);
ao->command = AO_COMMAND_NONE;
ao->mixer = nullptr;
ao->replay_gain_filter = nullptr;
ao->other_replay_gain_filter = nullptr;
/* done */
return true;
}
static bool
audio_output_setup(struct audio_output *ao, const config_param &param,
Error &error)
{
/* create the replay_gain filter */
const char *replay_gain_handler =
param.GetBlockValue("replay_gain_handler", "software");
if (strcmp(replay_gain_handler, "none") != 0) {
ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
param, IgnoreError());
assert(ao->replay_gain_filter != nullptr);
ao->replay_gain_serial = 0;
ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
param,
IgnoreError());
assert(ao->other_replay_gain_filter != nullptr);
ao->other_replay_gain_serial = 0;
} else {
ao->replay_gain_filter = nullptr;
ao->other_replay_gain_filter = nullptr;
}
/* set up the mixer */
Error mixer_error;
ao->mixer = audio_output_load_mixer(ao, param,
ao->plugin->mixer_plugin,
*ao->filter, mixer_error);
if (ao->mixer == nullptr && mixer_error.IsDefined())
FormatError(mixer_error,
"Failed to initialize hardware mixer for '%s'",
ao->name);
/* use the hardware mixer for replay gain? */
if (strcmp(replay_gain_handler, "mixer") == 0) {
if (ao->mixer != nullptr)
replay_gain_filter_set_mixer(ao->replay_gain_filter,
ao->mixer, 100);
else
FormatError(output_domain,
"No such mixer for output '%s'", ao->name);
} else if (strcmp(replay_gain_handler, "software") != 0 &&
ao->replay_gain_filter != nullptr) {
error.Set(config_domain,
"Invalid \"replay_gain_handler\" value");
return false;
}
/* the "convert" filter must be the last one in the chain */
ao->convert_filter = filter_new(&convert_filter_plugin, config_param(),
IgnoreError());
assert(ao->convert_filter != nullptr);
filter_chain_append(*ao->filter, "convert", ao->convert_filter);
return true;
}
struct audio_output *
audio_output_new(const config_param &param,
PlayerControl &pc,
Error &error)
{
const struct audio_output_plugin *plugin;
if (!param.IsNull()) {
const char *p;
p = param.GetBlockValue(AUDIO_OUTPUT_TYPE);
if (p == nullptr) {
error.Set(config_domain,
"Missing \"type\" configuration");
return nullptr;
}
plugin = audio_output_plugin_get(p);
if (plugin == nullptr) {
error.Format(config_domain,
"No such audio output plugin: %s", p);
return nullptr;
}
} else {
LogWarning(output_domain,
"No 'audio_output' defined in config file");
plugin = audio_output_detect(error);
if (plugin == nullptr)
return nullptr;
FormatDefault(output_domain,
"Successfully detected a %s audio device",
plugin->name);
}
struct audio_output *ao = ao_plugin_init(plugin, param, error);
if (ao == nullptr)
return nullptr;
if (!audio_output_setup(ao, param, error)) {
ao_plugin_finish(ao);
return nullptr;
}
ao->player_control = &pc;
return ao;
}

View File

@@ -0,0 +1,301 @@
/*
* 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.
*/
#ifndef MPD_OUTPUT_INTERNAL_HXX
#define MPD_OUTPUT_INTERNAL_HXX
#include "AudioFormat.hxx"
#include "pcm/PcmBuffer.hxx"
#include "pcm/PcmDither.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "thread/Thread.hxx"
#include "system/PeriodClock.hxx"
class Error;
class Filter;
class MusicPipe;
struct config_param;
struct PlayerControl;
enum audio_output_command {
AO_COMMAND_NONE = 0,
AO_COMMAND_ENABLE,
AO_COMMAND_DISABLE,
AO_COMMAND_OPEN,
/**
* This command is invoked when the input audio format
* changes.
*/
AO_COMMAND_REOPEN,
AO_COMMAND_CLOSE,
AO_COMMAND_PAUSE,
/**
* Drains the internal (hardware) buffers of the device. This
* operation may take a while to complete.
*/
AO_COMMAND_DRAIN,
AO_COMMAND_CANCEL,
AO_COMMAND_KILL
};
struct audio_output {
/**
* The device's configured display name.
*/
const char *name;
/**
* The plugin which implements this output device.
*/
const struct audio_output_plugin *plugin;
/**
* The #mixer object associated with this audio output device.
* May be nullptr if none is available, or if software volume is
* configured.
*/
class Mixer *mixer;
/**
* Will this output receive tags from the decoder? The
* default is true, but it may be configured to false to
* suppress sending tags to the output.
*/
bool tags;
/**
* Shall this output always play something (i.e. silence),
* even when playback is stopped?
*/
bool always_on;
/**
* Has the user enabled this device?
*/
bool enabled;
/**
* Is this device actually enabled, i.e. the "enable" method
* has succeeded?
*/
bool really_enabled;
/**
* Is the device (already) open and functional?
*
* This attribute may only be modified by the output thread.
* It is protected with #mutex: write accesses inside the
* output thread and read accesses outside of it may only be
* performed while the lock is held.
*/
bool open;
/**
* Is the device paused? i.e. the output thread is in the
* ao_pause() loop.
*/
bool pause;
/**
* 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 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;
/**
* 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;
/**
* 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.
*/
AudioFormat config_audio_format;
/**
* The audio_format in which audio data is received from the
* player thread (which in turn receives it from the decoder).
*/
AudioFormat in_audio_format;
/**
* The audio_format which is really sent to the device. This
* is basically config_audio_format (if configured) or
* in_audio_format, but may have been modified by
* plugin->open().
*/
AudioFormat out_audio_format;
/**
* The buffer used to allocate the cross-fading result.
*/
PcmBuffer cross_fade_buffer;
/**
* The dithering state for cross-fading two streams.
*/
PcmDither cross_fade_dither;
/**
* The filter object of this audio output. This is an
* instance of chain_filter_plugin.
*/
Filter *filter;
/**
* The replay_gain_filter_plugin instance of this audio
* output.
*/
Filter *replay_gain_filter;
/**
* The serial number of the last replay gain info. 0 means no
* replay gain info was available.
*/
unsigned replay_gain_serial;
/**
* The replay_gain_filter_plugin instance of this audio
* output, to be applied to the second chunk during
* cross-fading.
*/
Filter *other_replay_gain_filter;
/**
* The serial number of the last replay gain info by the
* "other" chunk during cross-fading.
*/
unsigned other_replay_gain_serial;
/**
* The convert_filter_plugin instance of this audio output.
* It is the last item in the filter chain, and is responsible
* for converting the input data into the appropriate format
* for this audio output.
*/
Filter *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.
*/
enum audio_output_command command;
/**
* The music pipe which provides music chunks to be played.
*/
const MusicPipe *pipe;
/**
* This mutex protects #open, #fail_timer, #chunk and
* #chunk_finished.
*/
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.
*/
PlayerControl *player_control;
/**
* The #music_chunk which is currently being played. All
* chunks before this one may be returned to the
* #music_buffer, because they are not going to be used by
* this output anymore.
*/
const struct music_chunk *chunk;
/**
* Has the output finished playing #chunk?
*/
bool chunk_finished;
};
/**
* Notify object used by the thread's client, i.e. we will send a
* notify signal to this object, expecting the caller to wait on it.
*/
extern struct notify audio_output_client_notify;
static inline bool
audio_output_is_open(const struct audio_output *ao)
{
return ao->open;
}
static inline bool
audio_output_command_is_finished(const struct audio_output *ao)
{
return ao->command == AO_COMMAND_NONE;
}
struct audio_output *
audio_output_new(const config_param &param,
PlayerControl &pc,
Error &error);
bool
ao_base_init(struct audio_output *ao,
const struct audio_output_plugin *plugin,
const config_param &param, Error &error);
void
ao_base_finish(struct audio_output *ao);
void
audio_output_free(struct audio_output *ao);
#endif

100
src/output/OutputList.cxx Normal file
View File

@@ -0,0 +1,100 @@
/*
* 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 "OutputList.hxx"
#include "OutputAPI.hxx"
#include "plugins/AlsaOutputPlugin.hxx"
#include "plugins/AoOutputPlugin.hxx"
#include "plugins/FifoOutputPlugin.hxx"
#include "plugins/HttpdOutputPlugin.hxx"
#include "plugins/JackOutputPlugin.hxx"
#include "plugins/NullOutputPlugin.hxx"
#include "plugins/OpenALOutputPlugin.hxx"
#include "plugins/OssOutputPlugin.hxx"
#include "plugins/OSXOutputPlugin.hxx"
#include "plugins/PipeOutputPlugin.hxx"
#include "plugins/PulseOutputPlugin.hxx"
#include "plugins/RecorderOutputPlugin.hxx"
#include "plugins/RoarOutputPlugin.hxx"
#include "plugins/ShoutOutputPlugin.hxx"
#include "plugins/SolarisOutputPlugin.hxx"
#include "plugins/WinmmOutputPlugin.hxx"
#include <string.h>
const struct audio_output_plugin *const audio_output_plugins[] = {
#ifdef HAVE_SHOUT
&shout_output_plugin,
#endif
&null_output_plugin,
#ifdef HAVE_FIFO
&fifo_output_plugin,
#endif
#ifdef ENABLE_PIPE_OUTPUT
&pipe_output_plugin,
#endif
#ifdef HAVE_ALSA
&alsa_output_plugin,
#endif
#ifdef HAVE_ROAR
&roar_output_plugin,
#endif
#ifdef HAVE_AO
&ao_output_plugin,
#endif
#ifdef HAVE_OSS
&oss_output_plugin,
#endif
#ifdef HAVE_OPENAL
&openal_output_plugin,
#endif
#ifdef HAVE_OSX
&osx_output_plugin,
#endif
#ifdef ENABLE_SOLARIS_OUTPUT
&solaris_output_plugin,
#endif
#ifdef HAVE_PULSE
&pulse_output_plugin,
#endif
#ifdef HAVE_JACK
&jack_output_plugin,
#endif
#ifdef ENABLE_HTTPD_OUTPUT
&httpd_output_plugin,
#endif
#ifdef ENABLE_RECORDER_OUTPUT
&recorder_output_plugin,
#endif
#ifdef ENABLE_WINMM_OUTPUT
&winmm_output_plugin,
#endif
nullptr
};
const struct audio_output_plugin *
audio_output_plugin_get(const char *name)
{
audio_output_plugins_for_each(plugin)
if (strcmp(plugin->name, name) == 0)
return plugin;
return nullptr;
}

33
src/output/OutputList.hxx Normal file
View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
#ifndef MPD_OUTPUT_LIST_HXX
#define MPD_OUTPUT_LIST_HXX
extern const struct audio_output_plugin *const audio_output_plugins[];
const struct audio_output_plugin *
audio_output_plugin_get(const char *name);
#define audio_output_plugins_for_each(plugin) \
for (const struct audio_output_plugin *plugin, \
*const*output_plugin_iterator = &audio_output_plugins[0]; \
(plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator)
#endif

109
src/output/OutputPlugin.cxx Normal file
View File

@@ -0,0 +1,109 @@
/*
* 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 "OutputPlugin.hxx"
#include "OutputInternal.hxx"
struct audio_output *
ao_plugin_init(const struct audio_output_plugin *plugin,
const config_param &param,
Error &error)
{
assert(plugin != nullptr);
assert(plugin->init != nullptr);
return plugin->init(param, error);
}
void
ao_plugin_finish(struct audio_output *ao)
{
ao->plugin->finish(ao);
}
bool
ao_plugin_enable(struct audio_output *ao, Error &error_r)
{
return ao->plugin->enable != nullptr
? ao->plugin->enable(ao, error_r)
: true;
}
void
ao_plugin_disable(struct audio_output *ao)
{
if (ao->plugin->disable != nullptr)
ao->plugin->disable(ao);
}
bool
ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
Error &error)
{
return ao->plugin->open(ao, audio_format, error);
}
void
ao_plugin_close(struct audio_output *ao)
{
ao->plugin->close(ao);
}
unsigned
ao_plugin_delay(struct audio_output *ao)
{
return ao->plugin->delay != nullptr
? ao->plugin->delay(ao)
: 0;
}
void
ao_plugin_send_tag(struct audio_output *ao, const Tag *tag)
{
if (ao->plugin->send_tag != nullptr)
ao->plugin->send_tag(ao, tag);
}
size_t
ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
Error &error)
{
return ao->plugin->play(ao, chunk, size, error);
}
void
ao_plugin_drain(struct audio_output *ao)
{
if (ao->plugin->drain != nullptr)
ao->plugin->drain(ao);
}
void
ao_plugin_cancel(struct audio_output *ao)
{
if (ao->plugin->cancel != nullptr)
ao->plugin->cancel(ao);
}
bool
ao_plugin_pause(struct audio_output *ao)
{
return ao->plugin->pause != nullptr && ao->plugin->pause(ao);
}

202
src/output/OutputPlugin.hxx Normal file
View File

@@ -0,0 +1,202 @@
/*
* 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.
*/
#ifndef MPD_OUTPUT_PLUGIN_HXX
#define MPD_OUTPUT_PLUGIN_HXX
#include "Compiler.h"
#include <stddef.h>
struct config_param;
struct AudioFormat;
struct Tag;
class Error;
/**
* A plugin which controls an audio output device.
*/
struct audio_output_plugin {
/**
* the plugin's name
*/
const char *name;
/**
* Test if this plugin can provide a default output, in case
* none has been configured. This method is optional.
*/
bool (*test_default_device)(void);
/**
* Configure and initialize the device, but do not open it
* yet.
*
* @param param the configuration section, or nullptr if there is
* no configuration
* @return nullptr on error, or an opaque pointer to the plugin's
* data
*/
struct audio_output *(*init)(const config_param &param,
Error &error);
/**
* Free resources allocated by this device.
*/
void (*finish)(struct audio_output *data);
/**
* Enable the device. This may allocate resources, preparing
* for the device to be opened. Enabling a device cannot
* fail: if an error occurs during that, it should be reported
* by the open() method.
*
* @return true on success, false on error
*/
bool (*enable)(struct audio_output *data, Error &error);
/**
* Disables the device. It is closed before this method is
* called.
*/
void (*disable)(struct audio_output *data);
/**
* Really open the device.
*
* @param audio_format the audio format in which data is going
* to be delivered; may be modified by the plugin
*/
bool (*open)(struct audio_output *data, AudioFormat &audio_format,
Error &error);
/**
* Close the device.
*/
void (*close)(struct audio_output *data);
/**
* Returns a positive number if the output thread shall delay
* the next call to play() or pause(). This should be
* implemented instead of doing a sleep inside the plugin,
* because this allows MPD to listen to commands meanwhile.
*
* @return the number of milliseconds to wait
*/
unsigned (*delay)(struct audio_output *data);
/**
* Display metadata for the next chunk. Optional method,
* because not all devices can display metadata.
*/
void (*send_tag)(struct audio_output *data, const Tag *tag);
/**
* Play a chunk of audio data.
*
* @return the number of bytes played, or 0 on error
*/
size_t (*play)(struct audio_output *data,
const void *chunk, size_t size,
Error &error);
/**
* Wait until the device has finished playing.
*/
void (*drain)(struct audio_output *data);
/**
* Try to cancel data which may still be in the device's
* buffers.
*/
void (*cancel)(struct audio_output *data);
/**
* Pause the device. If supported, it may perform a special
* action, which keeps the device open, but does not play
* anything. Output plugins like "shout" might want to play
* silence during pause, so their clients won't be
* disconnected. Plugins which do not support pausing will
* simply be closed, and have to be reopened when unpaused.
*
* @return false on error (output will be closed then), true
* for continue to pause
*/
bool (*pause)(struct audio_output *data);
/**
* The mixer plugin associated with this output plugin. This
* may be nullptr if no mixer plugin is implemented. When
* created, this mixer plugin gets the same #config_param as
* this audio output device.
*/
const struct mixer_plugin *mixer_plugin;
};
static inline bool
ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
{
return plugin->test_default_device != nullptr
? plugin->test_default_device()
: false;
}
gcc_malloc
struct audio_output *
ao_plugin_init(const struct audio_output_plugin *plugin,
const config_param &param,
Error &error);
void
ao_plugin_finish(struct audio_output *ao);
bool
ao_plugin_enable(struct audio_output *ao, Error &error);
void
ao_plugin_disable(struct audio_output *ao);
bool
ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
Error &error);
void
ao_plugin_close(struct audio_output *ao);
gcc_pure
unsigned
ao_plugin_delay(struct audio_output *ao);
void
ao_plugin_send_tag(struct audio_output *ao, const Tag *tag);
size_t
ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
Error &error);
void
ao_plugin_drain(struct audio_output *ao);
void
ao_plugin_cancel(struct audio_output *ao);
bool
ao_plugin_pause(struct audio_output *ao);
#endif

View File

@@ -0,0 +1,45 @@
/*
* 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.
*/
/*
* Protocol specific code for the audio output library.
*
*/
#include "config.h"
#include "OutputPrint.hxx"
#include "OutputAll.hxx"
#include "OutputInternal.hxx"
#include "Client.hxx"
void
printAudioDevices(Client &client)
{
const unsigned n = audio_output_count();
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
client_printf(client,
"outputid: %i\n"
"outputname: %s\n"
"outputenabled: %i\n",
i, ao->name, ao->enabled);
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.
*/
/*
* Protocol specific code for the audio output library.
*
*/
#ifndef MPD_OUTPUT_PRINT_HXX
#define MPD_OUTPUT_PRINT_HXX
class Client;
void
printAudioDevices(Client &client);
#endif

View File

@@ -0,0 +1,92 @@
/*
* 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.
*/
/*
* Saving and loading the audio output states to/from the state file.
*
*/
#include "config.h"
#include "OutputState.hxx"
#include "OutputAll.hxx"
#include "OutputInternal.hxx"
#include "OutputError.hxx"
#include "Log.hxx"
#include "util/StringUtil.hxx"
#include <assert.h>
#include <stdlib.h>
#define AUDIO_DEVICE_STATE "audio_device_state:"
unsigned audio_output_state_version;
void
audio_output_state_save(FILE *fp)
{
unsigned n = audio_output_count();
assert(n > 0);
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
ao->enabled, ao->name);
}
}
bool
audio_output_state_read(const char *line)
{
long value;
char *endptr;
const char *name;
struct audio_output *ao;
if (!StringStartsWith(line, AUDIO_DEVICE_STATE))
return false;
line += sizeof(AUDIO_DEVICE_STATE) - 1;
value = strtol(line, &endptr, 10);
if (*endptr != ':' || (value != 0 && value != 1))
return false;
if (value != 0)
/* state is "enabled": no-op */
return true;
name = endptr + 1;
ao = audio_output_find(name);
if (ao == NULL) {
FormatDebug(output_domain,
"Ignoring device state for '%s'", name);
return true;
}
ao->enabled = false;
return true;
}
unsigned
audio_output_state_get_version(void)
{
return audio_output_state_version;
}

View File

@@ -0,0 +1,44 @@
/*
* 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.
*/
/*
* Saving and loading the audio output states to/from the state file.
*
*/
#ifndef MPD_OUTPUT_STATE_HXX
#define MPD_OUTPUT_STATE_HXX
#include <stdio.h>
bool
audio_output_state_read(const char *line);
void
audio_output_state_save(FILE *fp);
/**
* Generates a version number for the current state of the audio
* outputs. This is used by timer_save_state_file() to determine
* whether the state has changed and the state file should be saved.
*/
unsigned
audio_output_state_get_version(void);
#endif

690
src/output/OutputThread.cxx Normal file
View File

@@ -0,0 +1,690 @@
/*
* 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 "OutputThread.hxx"
#include "OutputInternal.hxx"
#include "OutputAPI.hxx"
#include "OutputError.hxx"
#include "pcm/PcmMix.hxx"
#include "notify.hxx"
#include "FilterInternal.hxx"
#include "filter/ConvertFilterPlugin.hxx"
#include "filter/ReplayGainFilterPlugin.hxx"
#include "PlayerControl.hxx"
#include "MusicPipe.hxx"
#include "MusicChunk.hxx"
#include "thread/Util.hxx"
#include "thread/Name.hxx"
#include "system/FatalError.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include "Compiler.h"
#include <assert.h>
#include <string.h>
static void ao_command_finished(struct audio_output *ao)
{
assert(ao->command != AO_COMMAND_NONE);
ao->command = AO_COMMAND_NONE;
ao->mutex.unlock();
audio_output_client_notify.Signal();
ao->mutex.lock();
}
static bool
ao_enable(struct audio_output *ao)
{
Error error;
bool success;
if (ao->really_enabled)
return true;
ao->mutex.unlock();
success = ao_plugin_enable(ao, error);
ao->mutex.lock();
if (!success) {
FormatError(error,
"Failed to enable \"%s\" [%s]",
ao->name, ao->plugin->name);
return false;
}
ao->really_enabled = true;
return true;
}
static void
ao_close(struct audio_output *ao, bool drain);
static void
ao_disable(struct audio_output *ao)
{
if (ao->open)
ao_close(ao, false);
if (ao->really_enabled) {
ao->really_enabled = false;
ao->mutex.unlock();
ao_plugin_disable(ao);
ao->mutex.lock();
}
}
static AudioFormat
ao_filter_open(struct audio_output *ao, AudioFormat &format,
Error &error_r)
{
assert(format.IsValid());
/* the replay_gain filter cannot fail here */
if (ao->replay_gain_filter != nullptr &&
!ao->replay_gain_filter->Open(format, error_r).IsDefined())
return AudioFormat::Undefined();
if (ao->other_replay_gain_filter != nullptr &&
!ao->other_replay_gain_filter->Open(format, error_r).IsDefined()) {
if (ao->replay_gain_filter != nullptr)
ao->replay_gain_filter->Close();
return AudioFormat::Undefined();
}
const AudioFormat af = ao->filter->Open(format, error_r);
if (!af.IsDefined()) {
if (ao->replay_gain_filter != nullptr)
ao->replay_gain_filter->Close();
if (ao->other_replay_gain_filter != nullptr)
ao->other_replay_gain_filter->Close();
}
return af;
}
static void
ao_filter_close(struct audio_output *ao)
{
if (ao->replay_gain_filter != nullptr)
ao->replay_gain_filter->Close();
if (ao->other_replay_gain_filter != nullptr)
ao->other_replay_gain_filter->Close();
ao->filter->Close();
}
static void
ao_open(struct audio_output *ao)
{
bool success;
Error error;
struct audio_format_string af_string;
assert(!ao->open);
assert(ao->pipe != nullptr);
assert(ao->chunk == nullptr);
assert(ao->in_audio_format.IsValid());
ao->fail_timer.Reset();
/* enable the device (just in case the last enable has failed) */
if (!ao_enable(ao))
/* still no luck */
return;
/* open the filter */
const AudioFormat filter_audio_format =
ao_filter_open(ao, ao->in_audio_format, error);
if (!filter_audio_format.IsDefined()) {
FormatError(error, "Failed to open filter for \"%s\" [%s]",
ao->name, ao->plugin->name);
ao->fail_timer.Update();
return;
}
assert(filter_audio_format.IsValid());
ao->out_audio_format = filter_audio_format;
ao->out_audio_format.ApplyMask(ao->config_audio_format);
ao->mutex.unlock();
success = ao_plugin_open(ao, ao->out_audio_format, error);
ao->mutex.lock();
assert(!ao->open);
if (!success) {
FormatError(error, "Failed to open \"%s\" [%s]",
ao->name, ao->plugin->name);
ao_filter_close(ao);
ao->fail_timer.Update();
return;
}
if (!convert_filter_set(ao->convert_filter, ao->out_audio_format,
error)) {
FormatError(error, "Failed to convert for \"%s\" [%s]",
ao->name, ao->plugin->name);
ao_filter_close(ao);
ao->fail_timer.Update();
return;
}
ao->open = true;
FormatDebug(output_domain,
"opened plugin=%s name=\"%s\" audio_format=%s",
ao->plugin->name, ao->name,
audio_format_to_string(ao->out_audio_format, &af_string));
if (ao->in_audio_format != ao->out_audio_format)
FormatDebug(output_domain, "converting from %s",
audio_format_to_string(ao->in_audio_format,
&af_string));
}
static void
ao_close(struct audio_output *ao, bool drain)
{
assert(ao->open);
ao->pipe = nullptr;
ao->chunk = nullptr;
ao->open = false;
ao->mutex.unlock();
if (drain)
ao_plugin_drain(ao);
else
ao_plugin_cancel(ao);
ao_plugin_close(ao);
ao_filter_close(ao);
ao->mutex.lock();
FormatDebug(output_domain, "closed plugin=%s name=\"%s\"",
ao->plugin->name, ao->name);
}
static void
ao_reopen_filter(struct audio_output *ao)
{
Error error;
ao_filter_close(ao);
const AudioFormat filter_audio_format =
ao_filter_open(ao, ao->in_audio_format, error);
if (!filter_audio_format.IsDefined() ||
!convert_filter_set(ao->convert_filter, ao->out_audio_format,
error)) {
FormatError(error,
"Failed to open filter for \"%s\" [%s]",
ao->name, ao->plugin->name);
/* this is a little code duplication fro ao_close(),
but we cannot call this function because we must
not call filter_close(ao->filter) again */
ao->pipe = nullptr;
ao->chunk = nullptr;
ao->open = false;
ao->fail_timer.Update();
ao->mutex.unlock();
ao_plugin_close(ao);
ao->mutex.lock();
return;
}
}
static void
ao_reopen(struct audio_output *ao)
{
if (!ao->config_audio_format.IsFullyDefined()) {
if (ao->open) {
const MusicPipe *mp = ao->pipe;
ao_close(ao, true);
ao->pipe = mp;
}
/* no audio format is configured: copy in->out, let
the output's open() method determine the effective
out_audio_format */
ao->out_audio_format = ao->in_audio_format;
ao->out_audio_format.ApplyMask(ao->config_audio_format);
}
if (ao->open)
/* the audio format has changed, and all filters have
to be reconfigured */
ao_reopen_filter(ao);
else
ao_open(ao);
}
/**
* Wait until the output's delay reaches zero.
*
* @return true if playback should be continued, false if a command
* was issued
*/
static bool
ao_wait(struct audio_output *ao)
{
while (true) {
unsigned delay = ao_plugin_delay(ao);
if (delay == 0)
return true;
(void)ao->cond.timed_wait(ao->mutex, delay);
if (ao->command != AO_COMMAND_NONE)
return false;
}
}
static const void *
ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
Filter *replay_gain_filter,
unsigned *replay_gain_serial_p,
size_t *length_r)
{
assert(chunk != nullptr);
assert(!chunk->IsEmpty());
assert(chunk->CheckFormat(ao->in_audio_format));
const void *data = chunk->data;
size_t length = chunk->length;
(void)ao;
assert(length % ao->in_audio_format.GetFrameSize() == 0);
if (length > 0 && replay_gain_filter != nullptr) {
if (chunk->replay_gain_serial != *replay_gain_serial_p) {
replay_gain_filter_set_info(replay_gain_filter,
chunk->replay_gain_serial != 0
? &chunk->replay_gain_info
: nullptr);
*replay_gain_serial_p = chunk->replay_gain_serial;
}
Error error;
data = replay_gain_filter->FilterPCM(data, length,
&length, error);
if (data == nullptr) {
FormatError(error, "\"%s\" [%s] failed to filter",
ao->name, ao->plugin->name);
return nullptr;
}
}
*length_r = length;
return data;
}
static const void *
ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
size_t *length_r)
{
size_t length;
const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
&ao->replay_gain_serial, &length);
if (data == nullptr)
return nullptr;
if (length == 0) {
/* empty chunk, nothing to do */
*length_r = 0;
return data;
}
/* cross-fade */
if (chunk->other != nullptr) {
size_t other_length;
const void *other_data =
ao_chunk_data(ao, chunk->other,
ao->other_replay_gain_filter,
&ao->other_replay_gain_serial,
&other_length);
if (other_data == nullptr)
return nullptr;
if (other_length == 0) {
*length_r = 0;
return data;
}
/* if the "other" chunk is longer, then that trailer
is used as-is, without mixing; it is part of the
"next" song being faded in, and if there's a rest,
it means cross-fading ends here */
if (length > other_length)
length = other_length;
void *dest = ao->cross_fade_buffer.Get(other_length);
memcpy(dest, other_data, other_length);
if (!pcm_mix(ao->cross_fade_dither, dest, data, length,
ao->in_audio_format.format,
1.0 - chunk->mix_ratio)) {
FormatError(output_domain,
"Cannot cross-fade format %s",
sample_format_to_string(ao->in_audio_format.format));
return nullptr;
}
data = dest;
length = other_length;
}
/* apply filter chain */
Error error;
data = ao->filter->FilterPCM(data, length, &length, error);
if (data == nullptr) {
FormatError(error, "\"%s\" [%s] failed to filter",
ao->name, ao->plugin->name);
return nullptr;
}
*length_r = length;
return data;
}
static bool
ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
{
assert(ao != nullptr);
assert(ao->filter != nullptr);
if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) {
ao->mutex.unlock();
ao_plugin_send_tag(ao, chunk->tag);
ao->mutex.lock();
}
size_t size;
#if GCC_CHECK_VERSION(4,7)
/* workaround -Wmaybe-uninitialized false positive */
size = 0;
#endif
const char *data = (const char *)ao_filter_chunk(ao, chunk, &size);
if (data == nullptr) {
ao_close(ao, false);
/* don't automatically reopen this device for 10
seconds */
ao->fail_timer.Update();
return false;
}
Error error;
while (size > 0 && ao->command == AO_COMMAND_NONE) {
size_t nbytes;
if (!ao_wait(ao))
break;
ao->mutex.unlock();
nbytes = ao_plugin_play(ao, data, size, error);
ao->mutex.lock();
if (nbytes == 0) {
/* play()==0 means failure */
FormatError(error, "\"%s\" [%s] failed to play",
ao->name, ao->plugin->name);
ao_close(ao, false);
/* don't automatically reopen this device for
10 seconds */
assert(!ao->fail_timer.IsDefined());
ao->fail_timer.Update();
return false;
}
assert(nbytes <= size);
assert(nbytes % ao->out_audio_format.GetFrameSize() == 0);
data += nbytes;
size -= nbytes;
}
return true;
}
static const struct music_chunk *
ao_next_chunk(struct audio_output *ao)
{
return ao->chunk != nullptr
/* continue the previous play() call */
? ao->chunk->next
/* get the first chunk from the pipe */
: ao->pipe->Peek();
}
/**
* 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
*/
static bool
ao_play(struct audio_output *ao)
{
bool success;
const struct music_chunk *chunk;
assert(ao->pipe != nullptr);
chunk = ao_next_chunk(ao);
if (chunk == nullptr)
/* no chunk available */
return false;
ao->chunk_finished = false;
assert(!ao->in_playback_loop);
ao->in_playback_loop = true;
while (chunk != nullptr && ao->command == AO_COMMAND_NONE) {
assert(!ao->chunk_finished);
ao->chunk = chunk;
success = ao_play_chunk(ao, chunk);
if (!success) {
assert(ao->chunk == nullptr);
break;
}
assert(ao->chunk == chunk);
chunk = chunk->next;
}
assert(ao->in_playback_loop);
ao->in_playback_loop = false;
ao->chunk_finished = true;
ao->mutex.unlock();
ao->player_control->LockSignal();
ao->mutex.lock();
return true;
}
static void ao_pause(struct audio_output *ao)
{
bool ret;
ao->mutex.unlock();
ao_plugin_cancel(ao);
ao->mutex.lock();
ao->pause = true;
ao_command_finished(ao);
do {
if (!ao_wait(ao))
break;
ao->mutex.unlock();
ret = ao_plugin_pause(ao);
ao->mutex.lock();
if (!ret) {
ao_close(ao, false);
break;
}
} while (ao->command == AO_COMMAND_NONE);
ao->pause = false;
}
static void
audio_output_task(void *arg)
{
struct audio_output *ao = (struct audio_output *)arg;
FormatThreadName("output:%s", ao->name);
SetThreadRealtime();
ao->mutex.lock();
while (1) {
switch (ao->command) {
case AO_COMMAND_NONE:
break;
case AO_COMMAND_ENABLE:
ao_enable(ao);
ao_command_finished(ao);
break;
case AO_COMMAND_DISABLE:
ao_disable(ao);
ao_command_finished(ao);
break;
case AO_COMMAND_OPEN:
ao_open(ao);
ao_command_finished(ao);
break;
case AO_COMMAND_REOPEN:
ao_reopen(ao);
ao_command_finished(ao);
break;
case AO_COMMAND_CLOSE:
assert(ao->open);
assert(ao->pipe != nullptr);
ao_close(ao, false);
ao_command_finished(ao);
break;
case AO_COMMAND_PAUSE:
if (!ao->open) {
/* the output has failed after
audio_output_all_pause() has
submitted the PAUSE command; bail
out */
ao_command_finished(ao);
break;
}
ao_pause(ao);
/* don't "break" here: this might cause
ao_play() to be called when command==CLOSE
ends the paused state - "continue" checks
the new command first */
continue;
case AO_COMMAND_DRAIN:
if (ao->open) {
assert(ao->chunk == nullptr);
assert(ao->pipe->Peek() == nullptr);
ao->mutex.unlock();
ao_plugin_drain(ao);
ao->mutex.lock();
}
ao_command_finished(ao);
continue;
case AO_COMMAND_CANCEL:
ao->chunk = nullptr;
if (ao->open) {
ao->mutex.unlock();
ao_plugin_cancel(ao);
ao->mutex.lock();
}
ao_command_finished(ao);
continue;
case AO_COMMAND_KILL:
ao->chunk = nullptr;
ao_command_finished(ao);
ao->mutex.unlock();
return;
}
if (ao->open && ao->allow_play && ao_play(ao))
/* don't wait for an event if there are more
chunks in the pipe */
continue;
if (ao->command == AO_COMMAND_NONE) {
ao->woken_for_play = false;
ao->cond.wait(ao->mutex);
}
}
}
void audio_output_thread_start(struct audio_output *ao)
{
assert(ao->command == AO_COMMAND_NONE);
Error error;
if (!ao->thread.Start(audio_output_task, ao, error))
FatalError(error);
}

View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
#ifndef MPD_OUTPUT_THREAD_HXX
#define MPD_OUTPUT_THREAD_HXX
struct audio_output;
void
audio_output_thread_start(audio_output *ao);
#endif

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "AlsaOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "pcm/PcmExport.hxx"
#include "util/Manual.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "AoOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"

View File

@@ -20,7 +20,7 @@
#include "config.h"
#include "FifoOutputPlugin.hxx"
#include "ConfigError.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "Timer.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"

View File

@@ -25,7 +25,7 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
#include "OutputInternal.hxx"
#include "../OutputInternal.hxx"
#include "Timer.hxx"
#include "thread/Mutex.hxx"
#include "event/ServerSocket.hxx"

View File

@@ -21,7 +21,7 @@
#include "HttpdOutputPlugin.hxx"
#include "HttpdInternal.hxx"
#include "HttpdClient.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "system/Resolver.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "JackOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "NullOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "Timer.hxx"
struct NullOutput {

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "OSXOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "OpenALOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "OssOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "system/fd_util.h"
#include "util/Error.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "PipeOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "PulseOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "mixer/PulseMixerPlugin.hxx"
#include "util/Error.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "RecorderOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "ConfigError.hxx"

View File

@@ -20,7 +20,7 @@
#include "config.h"
#include "RoarOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "thread/Mutex.hxx"
#include "util/Error.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "ShoutOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "ConfigError.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "SolarisOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "system/fd_util.h"
#include "util/Error.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "WinmmOutputPlugin.hxx"
#include "OutputAPI.hxx"
#include "../OutputAPI.hxx"
#include "pcm/PcmBuffer.hxx"
#include "MixerList.hxx"
#include "util/Error.hxx"