output/*: move to output/plugins/
This commit is contained in:
33
src/output/OutputAPI.hxx
Normal file
33
src/output/OutputAPI.hxx
Normal 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
589
src/output/OutputAll.cxx
Normal 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 = ∅
|
||||
}
|
||||
|
||||
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
174
src/output/OutputAll.hxx
Normal 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
|
||||
112
src/output/OutputCommand.cxx
Normal file
112
src/output/OutputCommand.cxx
Normal 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;
|
||||
}
|
||||
51
src/output/OutputCommand.hxx
Normal file
51
src/output/OutputCommand.hxx
Normal 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
|
||||
325
src/output/OutputControl.cxx
Normal file
325
src/output/OutputControl.cxx
Normal 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 = ∓
|
||||
|
||||
/* 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 = ∓
|
||||
|
||||
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);
|
||||
}
|
||||
94
src/output/OutputControl.hxx
Normal file
94
src/output/OutputControl.hxx
Normal 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
|
||||
23
src/output/OutputError.cxx
Normal file
23
src/output/OutputError.cxx
Normal 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");
|
||||
25
src/output/OutputError.hxx
Normal file
25
src/output/OutputError.hxx
Normal 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
|
||||
51
src/output/OutputFinish.cxx
Normal file
51
src/output/OutputFinish.cxx
Normal 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
329
src/output/OutputInit.cxx
Normal 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 ¶m)
|
||||
{
|
||||
/* 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 ¶m,
|
||||
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 ¶m, 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 ¶m,
|
||||
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 ¶m,
|
||||
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;
|
||||
}
|
||||
301
src/output/OutputInternal.hxx
Normal file
301
src/output/OutputInternal.hxx
Normal 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 ¶m,
|
||||
PlayerControl &pc,
|
||||
Error &error);
|
||||
|
||||
bool
|
||||
ao_base_init(struct audio_output *ao,
|
||||
const struct audio_output_plugin *plugin,
|
||||
const config_param ¶m, 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
100
src/output/OutputList.cxx
Normal 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
33
src/output/OutputList.hxx
Normal 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
109
src/output/OutputPlugin.cxx
Normal 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 ¶m,
|
||||
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
202
src/output/OutputPlugin.hxx
Normal 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 ¶m,
|
||||
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 ¶m,
|
||||
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
|
||||
45
src/output/OutputPrint.cxx
Normal file
45
src/output/OutputPrint.cxx
Normal 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);
|
||||
}
|
||||
}
|
||||
34
src/output/OutputPrint.hxx
Normal file
34
src/output/OutputPrint.hxx
Normal 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
|
||||
92
src/output/OutputState.cxx
Normal file
92
src/output/OutputState.cxx
Normal 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;
|
||||
}
|
||||
44
src/output/OutputState.hxx
Normal file
44
src/output/OutputState.hxx
Normal 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
690
src/output/OutputThread.cxx
Normal 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);
|
||||
}
|
||||
28
src/output/OutputThread.hxx
Normal file
28
src/output/OutputThread.hxx
Normal 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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "NullOutputPlugin.hxx"
|
||||
#include "OutputAPI.hxx"
|
||||
#include "../OutputAPI.hxx"
|
||||
#include "Timer.hxx"
|
||||
|
||||
struct NullOutput {
|
||||
@@ -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"
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "OpenALOutputPlugin.hxx"
|
||||
#include "OutputAPI.hxx"
|
||||
#include "../OutputAPI.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user