diff options
Diffstat (limited to 'src/output')
-rw-r--r-- | src/output/Domain.cxx | 23 | ||||
-rw-r--r-- | src/output/Domain.hxx | 25 | ||||
-rw-r--r-- | src/output/Finish.cxx | 50 | ||||
-rw-r--r-- | src/output/Init.cxx | 335 | ||||
-rw-r--r-- | src/output/Internal.hxx | 441 | ||||
-rw-r--r-- | src/output/MultipleOutputs.cxx | 482 | ||||
-rw-r--r-- | src/output/MultipleOutputs.hxx | 276 | ||||
-rw-r--r-- | src/output/OutputAPI.hxx | 33 | ||||
-rw-r--r-- | src/output/OutputCommand.cxx | 106 | ||||
-rw-r--r-- | src/output/OutputCommand.hxx | 53 | ||||
-rw-r--r-- | src/output/OutputControl.cxx | 295 | ||||
-rw-r--r-- | src/output/OutputControl.hxx | 25 | ||||
-rw-r--r-- | src/output/OutputPlugin.cxx | 109 | ||||
-rw-r--r-- | src/output/OutputPlugin.hxx | 204 | ||||
-rw-r--r-- | src/output/OutputPrint.cxx | 43 | ||||
-rw-r--r-- | src/output/OutputPrint.hxx | 34 | ||||
-rw-r--r-- | src/output/OutputState.cxx | 88 | ||||
-rw-r--r-- | src/output/OutputState.hxx | 46 | ||||
-rw-r--r-- | src/output/OutputThread.cxx | 704 | ||||
-rw-r--r-- | src/output/Registry.cxx | 104 | ||||
-rw-r--r-- | src/output/Registry.hxx | 35 | ||||
-rw-r--r-- | src/output/Timer.cxx | 67 | ||||
-rw-r--r-- | src/output/Timer.hxx | 47 | ||||
-rw-r--r-- | src/output/plugins/AlsaOutputPlugin.cxx (renamed from src/output/AlsaOutputPlugin.cxx) | 182 | ||||
-rw-r--r-- | src/output/plugins/AlsaOutputPlugin.hxx (renamed from src/output/AlsaOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/AoOutputPlugin.cxx (renamed from src/output/AoOutputPlugin.cxx) | 30 | ||||
-rw-r--r-- | src/output/plugins/AoOutputPlugin.hxx (renamed from src/output/AoOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/FifoOutputPlugin.cxx (renamed from src/output/FifoOutputPlugin.cxx) | 41 | ||||
-rw-r--r-- | src/output/plugins/FifoOutputPlugin.hxx (renamed from src/output/FifoOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/JackOutputPlugin.cxx (renamed from src/output/JackOutputPlugin.cxx) | 43 | ||||
-rw-r--r-- | src/output/plugins/JackOutputPlugin.hxx (renamed from src/output/JackOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/NullOutputPlugin.cxx (renamed from src/output/NullOutputPlugin.cxx) | 37 | ||||
-rw-r--r-- | src/output/plugins/NullOutputPlugin.hxx (renamed from src/output/NullOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/OSXOutputPlugin.cxx (renamed from src/output/OSXOutputPlugin.cxx) | 78 | ||||
-rw-r--r-- | src/output/plugins/OSXOutputPlugin.hxx (renamed from src/output/OSXOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/OpenALOutputPlugin.cxx (renamed from src/output/OpenALOutputPlugin.cxx) | 37 | ||||
-rw-r--r-- | src/output/plugins/OpenALOutputPlugin.hxx (renamed from src/output/OpenALOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/OssOutputPlugin.cxx (renamed from src/output/OssOutputPlugin.cxx) | 45 | ||||
-rw-r--r-- | src/output/plugins/OssOutputPlugin.hxx (renamed from src/output/OssOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/PipeOutputPlugin.cxx (renamed from src/output/PipeOutputPlugin.cxx) | 32 | ||||
-rw-r--r-- | src/output/plugins/PipeOutputPlugin.hxx (renamed from src/output/PipeOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/PulseOutputPlugin.cxx (renamed from src/output/PulseOutputPlugin.cxx) | 117 | ||||
-rw-r--r-- | src/output/plugins/PulseOutputPlugin.hxx (renamed from src/output/PulseOutputPlugin.hxx) | 18 | ||||
-rw-r--r-- | src/output/plugins/RecorderOutputPlugin.cxx (renamed from src/output/RecorderOutputPlugin.cxx) | 36 | ||||
-rw-r--r-- | src/output/plugins/RecorderOutputPlugin.hxx (renamed from src/output/RecorderOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/RoarOutputPlugin.cxx (renamed from src/output/RoarOutputPlugin.cxx) | 74 | ||||
-rw-r--r-- | src/output/plugins/RoarOutputPlugin.hxx (renamed from src/output/RoarOutputPlugin.hxx) | 8 | ||||
-rw-r--r-- | src/output/plugins/ShoutOutputPlugin.cxx (renamed from src/output/ShoutOutputPlugin.cxx) | 53 | ||||
-rw-r--r-- | src/output/plugins/ShoutOutputPlugin.hxx (renamed from src/output/ShoutOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/SolarisOutputPlugin.cxx (renamed from src/output/SolarisOutputPlugin.cxx) | 31 | ||||
-rw-r--r-- | src/output/plugins/SolarisOutputPlugin.hxx (renamed from src/output/SolarisOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/WinmmOutputPlugin.cxx (renamed from src/output/WinmmOutputPlugin.cxx) | 37 | ||||
-rw-r--r-- | src/output/plugins/WinmmOutputPlugin.hxx (renamed from src/output/WinmmOutputPlugin.hxx) | 6 | ||||
-rw-r--r-- | src/output/plugins/httpd/HttpdClient.cxx (renamed from src/output/HttpdClient.cxx) | 97 | ||||
-rw-r--r-- | src/output/plugins/httpd/HttpdClient.hxx (renamed from src/output/HttpdClient.hxx) | 27 | ||||
-rw-r--r-- | src/output/plugins/httpd/HttpdInternal.hxx (renamed from src/output/HttpdInternal.hxx) | 65 | ||||
-rw-r--r-- | src/output/plugins/httpd/HttpdOutputPlugin.cxx (renamed from src/output/HttpdOutputPlugin.cxx) | 246 | ||||
-rw-r--r-- | src/output/plugins/httpd/HttpdOutputPlugin.hxx (renamed from src/output/HttpdOutputPlugin.hxx) | 4 | ||||
-rw-r--r-- | src/output/plugins/httpd/IcyMetaDataServer.cxx | 134 | ||||
-rw-r--r-- | src/output/plugins/httpd/IcyMetaDataServer.hxx | 39 | ||||
-rw-r--r-- | src/output/plugins/httpd/Page.cxx | 70 | ||||
-rw-r--r-- | src/output/plugins/httpd/Page.hxx | 102 | ||||
-rw-r--r-- | src/output/plugins/sles/AndroidSimpleBufferQueue.hxx | 67 | ||||
-rw-r--r-- | src/output/plugins/sles/Engine.hxx | 68 | ||||
-rw-r--r-- | src/output/plugins/sles/Object.hxx | 64 | ||||
-rw-r--r-- | src/output/plugins/sles/Play.hxx | 52 | ||||
-rw-r--r-- | src/output/plugins/sles/SlesOutputPlugin.cxx | 539 | ||||
-rw-r--r-- | src/output/plugins/sles/SlesOutputPlugin.hxx | 25 |
68 files changed, 5524 insertions, 653 deletions
diff --git a/src/output/Domain.cxx b/src/output/Domain.cxx new file mode 100644 index 000000000..878e5f3c5 --- /dev/null +++ b/src/output/Domain.cxx @@ -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 "Domain.hxx" +#include "util/Domain.hxx" + +const Domain output_domain("output"); diff --git a/src/output/Domain.hxx b/src/output/Domain.hxx new file mode 100644 index 000000000..e3a20142f --- /dev/null +++ b/src/output/Domain.hxx @@ -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 diff --git a/src/output/Finish.cxx b/src/output/Finish.cxx new file mode 100644 index 000000000..be2ca463e --- /dev/null +++ b/src/output/Finish.cxx @@ -0,0 +1,50 @@ +/* + * 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 "Internal.hxx" +#include "OutputPlugin.hxx" +#include "mixer/MixerControl.hxx" +#include "filter/FilterInternal.hxx" + +#include <assert.h> + +AudioOutput::~AudioOutput() +{ + assert(!open); + assert(!fail_timer.IsDefined()); + assert(!thread.IsDefined()); + + if (mixer != nullptr) + mixer_free(mixer); + + delete replay_gain_filter; + delete other_replay_gain_filter; + delete filter; +} + +void +audio_output_free(AudioOutput *ao) +{ + assert(!ao->open); + assert(!ao->fail_timer.IsDefined()); + assert(!ao->thread.IsDefined()); + + ao_plugin_finish(ao); +} diff --git a/src/output/Init.cxx b/src/output/Init.cxx new file mode 100644 index 000000000..eafcec432 --- /dev/null +++ b/src/output/Init.cxx @@ -0,0 +1,335 @@ +/* + * 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 "Internal.hxx" +#include "Registry.hxx" +#include "Domain.hxx" +#include "OutputAPI.hxx" +#include "filter/FilterConfig.hxx" +#include "AudioParser.hxx" +#include "mixer/MixerList.hxx" +#include "mixer/MixerType.hxx" +#include "mixer/MixerControl.hxx" +#include "mixer/plugins/SoftwareMixerPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterRegistry.hxx" +#include "filter/plugins/AutoConvertFilterPlugin.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "filter/plugins/ChainFilterPlugin.hxx" +#include "config/ConfigError.hxx" +#include "config/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" + +AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin) + :plugin(_plugin), + enabled(true), really_enabled(false), + open(false), + pause(false), + allow_play(true), + in_playback_loop(false), + woken_for_play(false), + filter(nullptr), + replay_gain_filter(nullptr), + other_replay_gain_filter(nullptr), + command(AO_COMMAND_NONE) +{ + assert(plugin.finish != nullptr); + assert(plugin.open != nullptr); + assert(plugin.close != nullptr); + assert(plugin.play != nullptr); +} + +static const AudioOutputPlugin * +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(EventLoop &event_loop, AudioOutput &ao, + const config_param ¶m, + const MixerPlugin *plugin, + Filter &filter_chain, + MixerListener &listener, + 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(event_loop, *plugin, ao, listener, + param, error); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(event_loop, software_mixer_plugin, ao, + listener, + 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 +AudioOutput::Configure(const config_param ¶m, Error &error) +{ + if (!param.IsNull()) { + name = param.GetBlockValue(AUDIO_OUTPUT_NAME); + if (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(config_audio_format, + p, true, error); + if (!success) + return false; + } else + config_audio_format.Clear(); + } else { + name = "default detected output"; + + config_audio_format.Clear(); + } + + tags = param.GetBlockValue("tags", true); + always_on = param.GetBlockValue("always_on", false); + enabled = param.GetBlockValue("enabled", true); + + /* set up the filter chain */ + + filter = filter_chain_new(); + assert(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(*filter, "normalize", + autoconvert_filter_new(normalize_filter)); + } + + Error filter_error; + filter_chain_parse(*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'", + name); + + /* done */ + + return true; +} + +static bool +audio_output_setup(EventLoop &event_loop, AudioOutput &ao, + MixerListener &mixer_listener, + 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(event_loop, ao, param, + ao.plugin.mixer_plugin, + *ao.filter, + mixer_listener, + 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; +} + +AudioOutput * +audio_output_new(EventLoop &event_loop, const config_param ¶m, + MixerListener &mixer_listener, + PlayerControl &pc, + Error &error) +{ + const AudioOutputPlugin *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 = AudioOutputPlugin_get(p); + if (plugin == nullptr) { + error.Format(config_domain, + "No such audio output plugin: %s", p); + return nullptr; + } + } else { + LogWarning(output_domain, + "No 'AudioOutput' 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); + } + + AudioOutput *ao = ao_plugin_init(plugin, param, error); + if (ao == nullptr) + return nullptr; + + if (!audio_output_setup(event_loop, *ao, mixer_listener, + param, error)) { + ao_plugin_finish(ao); + return nullptr; + } + + ao->player_control = &pc; + return ao; +} diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx new file mode 100644 index 000000000..6e6ffb442 --- /dev/null +++ b/src/output/Internal.hxx @@ -0,0 +1,441 @@ +/* + * 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 "ReplayGainInfo.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "system/PeriodClock.hxx" + +class Error; +class Filter; +class MusicPipe; +class EventLoop; +class Mixer; +class MixerListener; +struct MusicChunk; +struct config_param; +struct PlayerControl; +struct AudioOutputPlugin; + +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 AudioOutput { + /** + * The device's configured display name. + */ + const char *name; + + /** + * The plugin which implements this output device. + */ + const AudioOutputPlugin &plugin; + + /** + * The #mixer object associated with this audio output device. + * May be nullptr if none is available, or if software volume is + * configured. + */ + 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, #current_chunk and + * #current_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 #MusicChunk 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 MusicChunk *current_chunk; + + /** + * Has the output finished playing #current_chunk? + */ + bool current_chunk_finished; + + AudioOutput(const AudioOutputPlugin &_plugin); + ~AudioOutput(); + + bool Configure(const config_param ¶m, Error &error); + + void StartThread(); + void StopThread(); + + void Finish(); + + bool IsOpen() const { + return open; + } + + bool IsCommandFinished() const { + return command == AO_COMMAND_NONE; + } + + /** + * Waits for command completion. + * + * Caller must lock the mutex. + */ + void WaitForCommand(); + + /** + * Sends a command, but does not wait for completion. + * + * Caller must lock the mutex. + */ + void CommandAsync(audio_output_command cmd); + + /** + * Sends a command to the #AudioOutput object and waits for + * completion. + * + * Caller must lock the mutex. + */ + void CommandWait(audio_output_command cmd); + + /** + * Lock the #AudioOutput object and execute the command + * synchronously. + */ + void LockCommandWait(audio_output_command cmd); + + /** + * Enables the device. + */ + void LockEnableWait(); + + /** + * Disables the device. + */ + void LockDisableWait(); + + void LockPauseAsync(); + + /** + * Same LockCloseWait(), but expects the lock to be + * held by the caller. + */ + void CloseWait(); + void LockCloseWait(); + + /** + * Closes the audio output, but if the "always_on" flag is set, put it + * into pause mode instead. + */ + void LockRelease(); + + void SetReplayGainMode(ReplayGainMode mode); + + /** + * Caller must lock the mutex. + */ + bool Open(const AudioFormat audio_format, const MusicPipe &mp); + + /** + * Opens or closes the device, depending on the "enabled" + * flag. + * + * @return true if the device is open + */ + bool LockUpdate(const AudioFormat audio_format, + const MusicPipe &mp); + + void LockPlay(); + + void LockDrainAsync(); + + /** + * Clear the "allow_play" flag and send the "CANCEL" command + * asynchronously. To finish the operation, the caller has to + * call LockAllowPlay(). + */ + void LockCancelAsync(); + + /** + * Set the "allow_play" and signal the thread. + */ + void LockAllowPlay(); + +private: + void CommandFinished(); + + bool Enable(); + void Disable(); + + void Open(); + void Close(bool drain); + void Reopen(); + + AudioFormat OpenFilter(AudioFormat &format, Error &error_r); + + /** + * Mutex must not be locked. + */ + void CloseFilter(); + + void ReopenFilter(); + + /** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a + * command was issued + */ + bool WaitForDelay(); + + gcc_pure + const MusicChunk *GetNextChunk() const; + + bool PlayChunk(const MusicChunk *chunk); + + /** + * Plays all remaining chunks, until the tail of the pipe has + * been reached (and no more chunks are queued), or until a + * command is received. + * + * @return true if at least one chunk has been available, + * false if the tail of the pipe was already reached + */ + bool Play(); + + void Pause(); + + /** + * The OutputThread. + */ + void Task(); + static void Task(void *arg); +}; + +/** + * 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; + +AudioOutput * +audio_output_new(EventLoop &event_loop, const config_param ¶m, + MixerListener &mixer_listener, + PlayerControl &pc, + Error &error); + +void +audio_output_free(AudioOutput *ao); + +#endif diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx new file mode 100644 index 000000000..33ab57894 --- /dev/null +++ b/src/output/MultipleOutputs.cxx @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MultipleOutputs.hxx" +#include "PlayerControl.hxx" +#include "Internal.hxx" +#include "Domain.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "notify.hxx" + +#include <assert.h> +#include <string.h> + +MultipleOutputs::MultipleOutputs(MixerListener &_mixer_listener) + :mixer_listener(_mixer_listener), + input_audio_format(AudioFormat::Undefined()), + buffer(nullptr), pipe(nullptr), + elapsed_time(SignedSongTime::Negative()) +{ +} + +MultipleOutputs::~MultipleOutputs() +{ + for (auto i : outputs) { + i->LockDisableWait(); + i->Finish(); + } +} + +static AudioOutput * +LoadOutput(EventLoop &event_loop, MixerListener &mixer_listener, + PlayerControl &pc, const config_param ¶m) +{ + Error error; + AudioOutput *output = audio_output_new(event_loop, param, + mixer_listener, + pc, error); + if (output == nullptr) { + if (param.line > 0) + FormatFatalError("line %i: %s", + param.line, + error.GetMessage()); + else + FatalError(error); + } + + return output; +} + +void +MultipleOutputs::Configure(EventLoop &event_loop, PlayerControl &pc) +{ + for (const config_param *param = config_get_param(CONF_AUDIO_OUTPUT); + param != nullptr; param = param->next) { + auto output = LoadOutput(event_loop, mixer_listener, + pc, *param); + if (FindByName(output->name) != nullptr) + FormatFatalError("output devices with identical " + "names: %s", output->name); + + outputs.push_back(output); + } + + if (outputs.empty()) { + /* auto-detect device */ + const config_param empty; + auto output = LoadOutput(event_loop, mixer_listener, + pc, empty); + outputs.push_back(output); + } +} + +AudioOutput * +MultipleOutputs::FindByName(const char *name) const +{ + for (auto i : outputs) + if (strcmp(i->name, name) == 0) + return i; + + return nullptr; +} + +void +MultipleOutputs::EnableDisable() +{ + for (auto ao : outputs) { + bool enabled; + + ao->mutex.lock(); + enabled = ao->really_enabled; + ao->mutex.unlock(); + + if (ao->enabled != enabled) { + if (ao->enabled) + ao->LockEnableWait(); + else + ao->LockDisableWait(); + } + } +} + +bool +MultipleOutputs::AllFinished() const +{ + for (auto ao : outputs) { + const ScopeLock protect(ao->mutex); + if (ao->IsOpen() && !ao->IsCommandFinished()) + return false; + } + + return true; +} + +void +MultipleOutputs::WaitAll() +{ + while (!AllFinished()) + audio_output_client_notify.Wait(); +} + +void +MultipleOutputs::AllowPlay() +{ + for (auto ao : outputs) + ao->LockAllowPlay(); +} + +static void +audio_output_reset_reopen(AudioOutput *ao) +{ + const ScopeLock protect(ao->mutex); + + ao->fail_timer.Reset(); +} + +void +MultipleOutputs::ResetReopen() +{ + for (auto ao : outputs) + audio_output_reset_reopen(ao); +} + +bool +MultipleOutputs::Update() +{ + bool ret = false; + + if (!input_audio_format.IsDefined()) + return false; + + for (auto ao : outputs) + ret = ao->LockUpdate(input_audio_format, *pipe) + || ret; + + return ret; +} + +void +MultipleOutputs::SetReplayGainMode(ReplayGainMode mode) +{ + for (auto ao : outputs) + ao->SetReplayGainMode(mode); +} + +bool +MultipleOutputs::Play(MusicChunk *chunk, Error &error) +{ + assert(buffer != nullptr); + assert(pipe != nullptr); + assert(chunk != nullptr); + assert(chunk->CheckFormat(input_audio_format)); + + if (!Update()) { + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + return false; + } + + pipe->Push(chunk); + + for (auto ao : outputs) + ao->LockPlay(); + + return true; +} + +bool +MultipleOutputs::Open(const AudioFormat audio_format, + MusicBuffer &_buffer, + Error &error) +{ + bool ret = false, enabled = false; + + assert(buffer == nullptr || buffer == &_buffer); + assert((pipe == nullptr) == (buffer == nullptr)); + + buffer = &_buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(pipe == nullptr || pipe->CheckFormat(audio_format)); + + if (pipe == nullptr) + pipe = new MusicPipe(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(pipe->IsEmpty() || audio_format == input_audio_format); + + input_audio_format = audio_format; + + ResetReopen(); + EnableDisable(); + Update(); + + for (auto ao : outputs) { + if (ao->enabled) + enabled = true; + + if (ao->open) + ret = true; + } + + if (!enabled) + error.Set(output_domain, "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + + if (!ret) + /* close all devices if there was an error */ + Close(); + + return ret; +} + +/** + * Has the specified audio output already consumed this chunk? + */ +gcc_pure +static bool +chunk_is_consumed_in(const AudioOutput *ao, + gcc_unused const MusicPipe *pipe, + const MusicChunk *chunk) +{ + if (!ao->open) + return true; + + if (ao->current_chunk == nullptr) + return false; + + assert(chunk == ao->current_chunk || + pipe->Contains(ao->current_chunk)); + + if (chunk != ao->current_chunk) { + assert(chunk->next != nullptr); + return true; + } + + return ao->current_chunk_finished && chunk->next == nullptr; +} + +bool +MultipleOutputs::IsChunkConsumed(const MusicChunk *chunk) const +{ + for (auto ao : outputs) { + const ScopeLock protect(ao->mutex); + if (!chunk_is_consumed_in(ao, pipe, chunk)) + return false; + } + + return true; +} + +inline void +MultipleOutputs::ClearTailChunk(gcc_unused const MusicChunk *chunk, + bool *locked) +{ + assert(chunk->next == nullptr); + assert(pipe->Contains(chunk)); + + for (unsigned i = 0, n = outputs.size(); i != n; ++i) { + AudioOutput *ao = outputs[i]; + + /* this mutex will be unlocked by the caller when it's + ready */ + ao->mutex.lock(); + locked[i] = ao->open; + + if (!locked[i]) { + ao->mutex.unlock(); + continue; + } + + assert(ao->current_chunk == chunk); + assert(ao->current_chunk_finished); + ao->current_chunk = nullptr; + } +} + +unsigned +MultipleOutputs::Check() +{ + const MusicChunk *chunk; + bool is_tail; + MusicChunk *shifted; + bool locked[outputs.size()]; + + assert(buffer != nullptr); + assert(pipe != nullptr); + + while ((chunk = pipe->Peek()) != nullptr) { + assert(!pipe->IsEmpty()); + + if (!IsChunkConsumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return pipe->GetSize(); + + if (chunk->length > 0 && !chunk->time.IsNegative()) + /* only update elapsed_time if the chunk + provides a defined value */ + elapsed_time = chunk->time; + + is_tail = chunk->next == nullptr; + if (is_tail) + /* this is the tail of the pipe - clear the + chunk reference in all outputs */ + ClearTailChunk(chunk, locked); + + /* remove the chunk from the pipe */ + shifted = pipe->Shift(); + assert(shifted == chunk); + + if (is_tail) + /* unlock all audio outputs which were locked + by clear_tail_chunk() */ + for (unsigned i = 0, n = outputs.size(); i != n; ++i) + if (locked[i]) + outputs[i]->mutex.unlock(); + + /* return the chunk to the buffer */ + buffer->Return(shifted); + } + + return 0; +} + +bool +MultipleOutputs::Wait(PlayerControl &pc, unsigned threshold) +{ + pc.Lock(); + + if (Check() < threshold) { + pc.Unlock(); + return true; + } + + pc.Wait(); + pc.Unlock(); + + return Check() < threshold; +} + +void +MultipleOutputs::Pause() +{ + Update(); + + for (auto ao : outputs) + ao->LockPauseAsync(); + + WaitAll(); +} + +void +MultipleOutputs::Drain() +{ + for (auto ao : outputs) + ao->LockDrainAsync(); + + WaitAll(); +} + +void +MultipleOutputs::Cancel() +{ + /* send the cancel() command to all audio outputs */ + + for (auto ao : outputs) + ao->LockCancelAsync(); + + WaitAll(); + + /* clear the music pipe and return all chunks to the buffer */ + + if (pipe != nullptr) + pipe->Clear(*buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + AllowPlay(); + + /* invalidate elapsed_time */ + + elapsed_time = SignedSongTime::Negative(); +} + +void +MultipleOutputs::Close() +{ + for (auto ao : outputs) + ao->LockCloseWait(); + + if (pipe != nullptr) { + assert(buffer != nullptr); + + pipe->Clear(*buffer); + delete pipe; + pipe = nullptr; + } + + buffer = nullptr; + + input_audio_format.Clear(); + + elapsed_time = SignedSongTime::Negative(); +} + +void +MultipleOutputs::Release() +{ + for (auto ao : outputs) + ao->LockRelease(); + + if (pipe != nullptr) { + assert(buffer != nullptr); + + pipe->Clear(*buffer); + delete pipe; + pipe = nullptr; + } + + buffer = nullptr; + + input_audio_format.Clear(); + + elapsed_time = SignedSongTime::Negative(); +} + +void +MultipleOutputs::SongBorder() +{ + /* clear the elapsed_time pointer at the beginning of a new + song */ + elapsed_time = SignedSongTime::zero(); +} diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx new file mode 100644 index 000000000..2c6536e2a --- /dev/null +++ b/src/output/MultipleOutputs.hxx @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for dealing with all configured (enabled) audion outputs + * at once. + * + */ + +#ifndef OUTPUT_ALL_H +#define OUTPUT_ALL_H + +#include "AudioFormat.hxx" +#include "ReplayGainInfo.hxx" +#include "Chrono.hxx" +#include "Compiler.h" + +#include <vector> + +#include <assert.h> + +struct AudioFormat; +class MusicBuffer; +class MusicPipe; +class EventLoop; +class MixerListener; +struct MusicChunk; +struct PlayerControl; +struct AudioOutput; +class Error; + +class MultipleOutputs { + MixerListener &mixer_listener; + + std::vector<AudioOutput *> outputs; + + AudioFormat input_audio_format; + + /** + * The #MusicBuffer object where consumed chunks are returned. + */ + MusicBuffer *buffer; + + /** + * The #MusicPipe object which feeds all audio outputs. It is + * filled by audio_output_all_play(). + */ + MusicPipe *pipe; + + /** + * The "elapsed_time" stamp of the most recently finished + * chunk. + */ + SignedSongTime elapsed_time; + +public: + /** + * Load audio outputs from the configuration file and + * initialize them. + */ + MultipleOutputs(MixerListener &_mixer_listener); + ~MultipleOutputs(); + + void Configure(EventLoop &event_loop, PlayerControl &pc); + + /** + * Returns the total number of audio output devices, including + * those which are disabled right now. + */ + gcc_pure + unsigned Size() const { + return outputs.size(); + } + + /** + * Returns the "i"th audio output device. + */ + const AudioOutput &Get(unsigned i) const { + assert(i < Size()); + + return *outputs[i]; + } + + AudioOutput &Get(unsigned i) { + assert(i < Size()); + + return *outputs[i]; + } + + /** + * Returns the audio output device with the specified name. + * Returns nullptr if the name does not exist. + */ + gcc_pure + AudioOutput *FindByName(const char *name) const; + + /** + * Checks the "enabled" flag of all audio outputs, and if one has + * changed, commit the change. + */ + void EnableDisable(); + + /** + * Opens all audio outputs which are not disabled. + * + * @param audio_format the preferred audio format + * @param buffer the #music_buffer where consumed #MusicChunk objects + * should be returned + * @return true on success, false on failure + */ + bool Open(const AudioFormat audio_format, MusicBuffer &_buffer, + Error &error); + + /** + * Closes all audio outputs. + */ + void Close(); + + /** + * Closes all audio outputs. Outputs with the "always_on" + * flag are put into pause mode. + */ + void Release(); + + void SetReplayGainMode(ReplayGainMode mode); + + /** + * Enqueue a #MusicChunk object for playing, i.e. pushes it to a + * #MusicPipe. + * + * @param chunk the #MusicChunk object to be played + * @return true on success, false if no audio output was able to play + * (all closed then) + */ + bool Play(MusicChunk *chunk, Error &error); + + /** + * Checks if the output devices have drained their music pipe, and + * returns the consumed music chunks to the #music_buffer. + * + * @return the number of chunks to play left in the #MusicPipe + */ + unsigned Check(); + + /** + * Checks if the size of the #MusicPipe is below the #threshold. If + * not, it attempts to synchronize with all output threads, and waits + * until another #MusicChunk is finished. + * + * @param threshold the maximum number of chunks in the pipe + * @return true if there are less than #threshold chunks in the pipe + */ + bool Wait(PlayerControl &pc, unsigned threshold); + + /** + * Puts all audio outputs into pause mode. Most implementations will + * simply close it then. + */ + void Pause(); + + /** + * Drain all audio outputs. + */ + void Drain(); + + /** + * Try to cancel data which may still be in the device's buffers. + */ + void Cancel(); + + /** + * Indicate that a new song will begin now. + */ + void SongBorder(); + + /** + * Returns the "elapsed_time" stamp of the most recently finished + * chunk. A negative value is returned when no chunk has been + * finished yet. + */ + gcc_pure + SignedSongTime GetElapsedTime() const { + return elapsed_time; + } + + /** + * Returns the average volume of all available mixers (range + * 0..100). Returns -1 if no mixer can be queried. + */ + gcc_pure + int GetVolume() const; + + /** + * Sets the volume on all available mixers. + * + * @param volume the volume (range 0..100) + * @return true on success, false on failure + */ + bool SetVolume(unsigned volume); + + /** + * Similar to GetVolume(), but gets the volume only for + * software mixers. See #software_mixer_plugin. This + * function fails if no software mixer is configured. + */ + gcc_pure + int GetSoftwareVolume() const; + + /** + * Similar to SetVolume(), but sets the volume only for + * software mixers. See #software_mixer_plugin. This + * function cannot fail, because the underlying software + * mixers cannot fail either. + */ + void SetSoftwareVolume(unsigned volume); + +private: + /** + * Determine if all (active) outputs have finished the current + * command. + */ + gcc_pure + bool AllFinished() const; + + void WaitAll(); + + /** + * Signals all audio outputs which are open. + */ + void AllowPlay(); + + /** + * Resets the "reopen" flag on all audio devices. MPD should + * immediately retry to open the device instead of waiting for + * the timeout when the user wants to start playback. + */ + void ResetReopen(); + + /** + * Opens all output devices which are enabled, but closed. + * + * @return true if there is at least open output device which + * is open + */ + bool Update(); + + /** + * Has this chunk been consumed by all audio outputs? + */ + bool IsChunkConsumed(const MusicChunk *chunk) const; + + /** + * There's only one chunk left in the pipe (#pipe), and all + * audio outputs have consumed it already. Clear the + * reference. + */ + void ClearTailChunk(const MusicChunk *chunk, bool *locked); +}; + +#endif diff --git a/src/output/OutputAPI.hxx b/src/output/OutputAPI.hxx new file mode 100644 index 000000000..e0fd6eec8 --- /dev/null +++ b/src/output/OutputAPI.hxx @@ -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 "Internal.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "config/ConfigData.hxx" + +// IWYU pragma: end_exports + +#endif diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx new file mode 100644 index 000000000..6afb70cf1 --- /dev/null +++ b/src/output/OutputCommand.cxx @@ -0,0 +1,106 @@ +/* + * 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 "MultipleOutputs.hxx" +#include "Internal.hxx" +#include "PlayerControl.hxx" +#include "mixer/MixerControl.hxx" +#include "Idle.hxx" + +extern unsigned audio_output_state_version; + +bool +audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) +{ + if (idx >= outputs.Size()) + return false; + + AudioOutput &ao = outputs.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(MultipleOutputs &outputs, unsigned idx) +{ + if (idx >= outputs.Size()) + return false; + + AudioOutput &ao = outputs.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(MultipleOutputs &outputs, unsigned idx) +{ + if (idx >= outputs.Size()) + return false; + + AudioOutput &ao = outputs.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; +} diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx new file mode 100644 index 000000000..53fc5c95e --- /dev/null +++ b/src/output/OutputCommand.hxx @@ -0,0 +1,53 @@ +/* + * 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 + +class MultipleOutputs; + +/** + * Enables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_enable_index(MultipleOutputs &outputs, unsigned idx); + +/** + * Disables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_disable_index(MultipleOutputs &outputs, unsigned idx); + +/** + * Toggles an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx); + +#endif diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx new file mode 100644 index 000000000..89428fa87 --- /dev/null +++ b/src/output/OutputControl.cxx @@ -0,0 +1,295 @@ +/* + * 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 "Internal.hxx" +#include "OutputPlugin.hxx" +#include "Domain.hxx" +#include "mixer/MixerControl.hxx" +#include "notify.hxx" +#include "filter/plugins/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; + +void +AudioOutput::WaitForCommand() +{ + while (!IsCommandFinished()) { + mutex.unlock(); + audio_output_client_notify.Wait(); + mutex.lock(); + } +} + +void +AudioOutput::CommandAsync(audio_output_command cmd) +{ + assert(IsCommandFinished()); + + command = cmd; + cond.signal(); +} + +void +AudioOutput::CommandWait(audio_output_command cmd) +{ + CommandAsync(cmd); + WaitForCommand(); +} + +void +AudioOutput::LockCommandWait(audio_output_command cmd) +{ + const ScopeLock protect(mutex); + CommandWait(cmd); +} + +void +AudioOutput::SetReplayGainMode(ReplayGainMode mode) +{ + if (replay_gain_filter != nullptr) + replay_gain_filter_set_mode(replay_gain_filter, mode); + if (other_replay_gain_filter != nullptr) + replay_gain_filter_set_mode(other_replay_gain_filter, mode); +} + +void +AudioOutput::LockEnableWait() +{ + if (!thread.IsDefined()) { + if (plugin.enable == nullptr) { + /* don't bother to start the thread now if the + device doesn't even have a enable() method; + just assign the variable and we're done */ + really_enabled = true; + return; + } + + StartThread(); + } + + LockCommandWait(AO_COMMAND_ENABLE); +} + +void +AudioOutput::LockDisableWait() +{ + if (!thread.IsDefined()) { + if (plugin.disable == nullptr) + really_enabled = false; + else + /* if there's no thread yet, the device cannot + be enabled */ + assert(!really_enabled); + + return; + } + + LockCommandWait(AO_COMMAND_DISABLE); +} + +inline bool +AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) +{ + assert(allow_play); + assert(audio_format.IsValid()); + + fail_timer.Reset(); + + if (open && audio_format == in_audio_format) { + assert(pipe == &mp || (always_on && pause)); + + if (pause) { + current_chunk = nullptr; + 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 */ + CommandWait(AO_COMMAND_CANCEL); + } + + return true; + } + + in_audio_format = audio_format; + current_chunk = nullptr; + + pipe = ∓ + + if (!thread.IsDefined()) + StartThread(); + + CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); + const bool open2 = open; + + if (open2 && mixer != nullptr) { + Error error; + if (!mixer_open(mixer, error)) + FormatWarning(output_domain, + "Failed to open mixer for '%s'", name); + } + + return open2; +} + +void +AudioOutput::CloseWait() +{ + assert(allow_play); + + if (mixer != nullptr) + mixer_auto_close(mixer); + + assert(!open || !fail_timer.IsDefined()); + + if (open) + CommandWait(AO_COMMAND_CLOSE); + else + fail_timer.Reset(); +} + +bool +AudioOutput::LockUpdate(const AudioFormat audio_format, + const MusicPipe &mp) +{ + const ScopeLock protect(mutex); + + if (enabled && really_enabled) { + if (fail_timer.Check(REOPEN_AFTER * 1000)) { + return Open(audio_format, mp); + } + } else if (IsOpen()) + CloseWait(); + + return false; +} + +void +AudioOutput::LockPlay() +{ + const ScopeLock protect(mutex); + + assert(allow_play); + + if (IsOpen() && !in_playback_loop && !woken_for_play) { + woken_for_play = true; + cond.signal(); + } +} + +void +AudioOutput::LockPauseAsync() +{ + if (mixer != nullptr && plugin.pause == nullptr) + /* the device has no pause mode: close the mixer, + unless its "global" flag is set (checked by + mixer_auto_close()) */ + mixer_auto_close(mixer); + + const ScopeLock protect(mutex); + + assert(allow_play); + if (IsOpen()) + CommandAsync(AO_COMMAND_PAUSE); +} + +void +AudioOutput::LockDrainAsync() +{ + const ScopeLock protect(mutex); + + assert(allow_play); + if (IsOpen()) + CommandAsync(AO_COMMAND_DRAIN); +} + +void +AudioOutput::LockCancelAsync() +{ + const ScopeLock protect(mutex); + + if (IsOpen()) { + allow_play = false; + CommandAsync(AO_COMMAND_CANCEL); + } +} + +void +AudioOutput::LockAllowPlay() +{ + const ScopeLock protect(mutex); + + allow_play = true; + if (IsOpen()) + cond.signal(); +} + +void +AudioOutput::LockRelease() +{ + if (always_on) + LockPauseAsync(); + else + LockCloseWait(); +} + +void +AudioOutput::LockCloseWait() +{ + assert(!open || !fail_timer.IsDefined()); + + const ScopeLock protect(mutex); + CloseWait(); +} + +void +AudioOutput::StopThread() +{ + assert(thread.IsDefined()); + assert(allow_play); + + LockCommandWait(AO_COMMAND_KILL); + thread.Join(); +} + +void +AudioOutput::Finish() +{ + LockCloseWait(); + + assert(!fail_timer.IsDefined()); + + if (thread.IsDefined()) + StopThread(); + + audio_output_free(this); +} diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx new file mode 100644 index 000000000..fff3fe406 --- /dev/null +++ b/src/output/OutputControl.hxx @@ -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_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX + +struct AudioOutput; + +#endif diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx new file mode 100644 index 000000000..33bb854d4 --- /dev/null +++ b/src/output/OutputPlugin.cxx @@ -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 "Internal.hxx" + +AudioOutput * +ao_plugin_init(const AudioOutputPlugin *plugin, + const config_param ¶m, + Error &error) +{ + assert(plugin != nullptr); + assert(plugin->init != nullptr); + + return plugin->init(param, error); +} + +void +ao_plugin_finish(AudioOutput *ao) +{ + ao->plugin.finish(ao); +} + +bool +ao_plugin_enable(AudioOutput *ao, Error &error_r) +{ + return ao->plugin.enable != nullptr + ? ao->plugin.enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(AudioOutput *ao) +{ + if (ao->plugin.disable != nullptr) + ao->plugin.disable(ao); +} + +bool +ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + return ao->plugin.open(ao, audio_format, error); +} + +void +ao_plugin_close(AudioOutput *ao) +{ + ao->plugin.close(ao); +} + +unsigned +ao_plugin_delay(AudioOutput *ao) +{ + return ao->plugin.delay != nullptr + ? ao->plugin.delay(ao) + : 0; +} + +void +ao_plugin_send_tag(AudioOutput *ao, const Tag *tag) +{ + if (ao->plugin.send_tag != nullptr) + ao->plugin.send_tag(ao, tag); +} + +size_t +ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + return ao->plugin.play(ao, chunk, size, error); +} + +void +ao_plugin_drain(AudioOutput *ao) +{ + if (ao->plugin.drain != nullptr) + ao->plugin.drain(ao); +} + +void +ao_plugin_cancel(AudioOutput *ao) +{ + if (ao->plugin.cancel != nullptr) + ao->plugin.cancel(ao); +} + +bool +ao_plugin_pause(AudioOutput *ao) +{ + return ao->plugin.pause != nullptr && ao->plugin.pause(ao); +} diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx new file mode 100644 index 000000000..00fa36bc0 --- /dev/null +++ b/src/output/OutputPlugin.hxx @@ -0,0 +1,204 @@ +/* + * 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; +struct AudioOutput; +struct MixerPlugin; +class Error; + +/** + * A plugin which controls an audio output device. + */ +struct AudioOutputPlugin { + /** + * 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 + */ + AudioOutput *(*init)(const config_param ¶m, + Error &error); + + /** + * Free resources allocated by this device. + */ + void (*finish)(AudioOutput *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)(AudioOutput *data, Error &error); + + /** + * Disables the device. It is closed before this method is + * called. + */ + void (*disable)(AudioOutput *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)(AudioOutput *data, AudioFormat &audio_format, + Error &error); + + /** + * Close the device. + */ + void (*close)(AudioOutput *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)(AudioOutput *data); + + /** + * Display metadata for the next chunk. Optional method, + * because not all devices can display metadata. + */ + void (*send_tag)(AudioOutput *data, const Tag *tag); + + /** + * Play a chunk of audio data. + * + * @return the number of bytes played, or 0 on error + */ + size_t (*play)(AudioOutput *data, + const void *chunk, size_t size, + Error &error); + + /** + * Wait until the device has finished playing. + */ + void (*drain)(AudioOutput *data); + + /** + * Try to cancel data which may still be in the device's + * buffers. + */ + void (*cancel)(AudioOutput *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)(AudioOutput *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 MixerPlugin *mixer_plugin; +}; + +static inline bool +ao_plugin_test_default_device(const AudioOutputPlugin *plugin) +{ + return plugin->test_default_device != nullptr + ? plugin->test_default_device() + : false; +} + +gcc_malloc +AudioOutput * +ao_plugin_init(const AudioOutputPlugin *plugin, + const config_param ¶m, + Error &error); + +void +ao_plugin_finish(AudioOutput *ao); + +bool +ao_plugin_enable(AudioOutput *ao, Error &error); + +void +ao_plugin_disable(AudioOutput *ao); + +bool +ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error); + +void +ao_plugin_close(AudioOutput *ao); + +gcc_pure +unsigned +ao_plugin_delay(AudioOutput *ao); + +void +ao_plugin_send_tag(AudioOutput *ao, const Tag *tag); + +size_t +ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error); + +void +ao_plugin_drain(AudioOutput *ao); + +void +ao_plugin_cancel(AudioOutput *ao); + +bool +ao_plugin_pause(AudioOutput *ao); + +#endif diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx new file mode 100644 index 000000000..414a86e32 --- /dev/null +++ b/src/output/OutputPrint.cxx @@ -0,0 +1,43 @@ +/* + * 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 "MultipleOutputs.hxx" +#include "Internal.hxx" +#include "client/Client.hxx" + +void +printAudioDevices(Client &client, const MultipleOutputs &outputs) +{ + for (unsigned i = 0, n = outputs.Size(); i != n; ++i) { + const AudioOutput &ao = outputs.Get(i); + + client_printf(client, + "outputid: %i\n" + "outputname: %s\n" + "outputenabled: %i\n", + i, ao.name, ao.enabled); + } +} diff --git a/src/output/OutputPrint.hxx b/src/output/OutputPrint.hxx new file mode 100644 index 000000000..29aa2b11c --- /dev/null +++ b/src/output/OutputPrint.hxx @@ -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; +class MultipleOutputs; + +void +printAudioDevices(Client &client, const MultipleOutputs &outputs); + +#endif diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx new file mode 100644 index 000000000..fb01b1c65 --- /dev/null +++ b/src/output/OutputState.cxx @@ -0,0 +1,88 @@ +/* + * 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 "MultipleOutputs.hxx" +#include "Internal.hxx" +#include "Domain.hxx" +#include "Log.hxx" +#include "fs/io/BufferedOutputStream.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(BufferedOutputStream &os, + const MultipleOutputs &outputs) +{ + for (unsigned i = 0, n = outputs.Size(); i != n; ++i) { + const AudioOutput &ao = outputs.Get(i); + + os.Format(AUDIO_DEVICE_STATE "%d:%s\n", ao.enabled, ao.name); + } +} + +bool +audio_output_state_read(const char *line, MultipleOutputs &outputs) +{ + long value; + char *endptr; + const char *name; + + 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; + AudioOutput *ao = outputs.FindByName(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; +} diff --git a/src/output/OutputState.hxx b/src/output/OutputState.hxx new file mode 100644 index 000000000..47f8429d5 --- /dev/null +++ b/src/output/OutputState.hxx @@ -0,0 +1,46 @@ +/* + * 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 + +class MultipleOutputs; +class BufferedOutputStream; + +bool +audio_output_state_read(const char *line, MultipleOutputs &outputs); + +void +audio_output_state_save(BufferedOutputStream &os, + const MultipleOutputs &outputs); + +/** + * 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 diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx new file mode 100644 index 000000000..2ec0670c1 --- /dev/null +++ b/src/output/OutputThread.cxx @@ -0,0 +1,704 @@ +/* + * 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 "Internal.hxx" +#include "OutputAPI.hxx" +#include "Domain.hxx" +#include "pcm/PcmMix.hxx" +#include "pcm/Domain.hxx" +#include "notify.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/plugins/ConvertFilterPlugin.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "thread/Util.hxx" +#include "thread/Slack.hxx" +#include "thread/Name.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "util/ConstBuffer.hxx" +#include "Log.hxx" +#include "Compiler.h" + +#include <assert.h> +#include <string.h> + +void +AudioOutput::CommandFinished() +{ + assert(command != AO_COMMAND_NONE); + command = AO_COMMAND_NONE; + + mutex.unlock(); + audio_output_client_notify.Signal(); + mutex.lock(); +} + +inline bool +AudioOutput::Enable() +{ + if (really_enabled) + return true; + + mutex.unlock(); + Error error; + bool success = ao_plugin_enable(this, error); + mutex.lock(); + if (!success) { + FormatError(error, + "Failed to enable \"%s\" [%s]", + name, plugin.name); + return false; + } + + really_enabled = true; + return true; +} + +inline void +AudioOutput::Disable() +{ + if (open) + Close(false); + + if (really_enabled) { + really_enabled = false; + + mutex.unlock(); + ao_plugin_disable(this); + mutex.lock(); + } +} + +inline AudioFormat +AudioOutput::OpenFilter(AudioFormat &format, Error &error_r) +{ + assert(format.IsValid()); + + /* the replay_gain filter cannot fail here */ + if (replay_gain_filter != nullptr && + !replay_gain_filter->Open(format, error_r).IsDefined()) + return AudioFormat::Undefined(); + + if (other_replay_gain_filter != nullptr && + !other_replay_gain_filter->Open(format, error_r).IsDefined()) { + if (replay_gain_filter != nullptr) + replay_gain_filter->Close(); + return AudioFormat::Undefined(); + } + + const AudioFormat af = filter->Open(format, error_r); + if (!af.IsDefined()) { + if (replay_gain_filter != nullptr) + replay_gain_filter->Close(); + if (other_replay_gain_filter != nullptr) + other_replay_gain_filter->Close(); + } + + return af; +} + +void +AudioOutput::CloseFilter() +{ + if (replay_gain_filter != nullptr) + replay_gain_filter->Close(); + if (other_replay_gain_filter != nullptr) + other_replay_gain_filter->Close(); + + filter->Close(); +} + +inline void +AudioOutput::Open() +{ + bool success; + Error error; + struct audio_format_string af_string; + + assert(!open); + assert(pipe != nullptr); + assert(current_chunk == nullptr); + assert(in_audio_format.IsValid()); + + fail_timer.Reset(); + + /* enable the device (just in case the last enable has failed) */ + + if (!Enable()) + /* still no luck */ + return; + + /* open the filter */ + + const AudioFormat filter_audio_format = + OpenFilter(in_audio_format, error); + if (!filter_audio_format.IsDefined()) { + FormatError(error, "Failed to open filter for \"%s\" [%s]", + name, plugin.name); + + fail_timer.Update(); + return; + } + + assert(filter_audio_format.IsValid()); + + out_audio_format = filter_audio_format; + out_audio_format.ApplyMask(config_audio_format); + + mutex.unlock(); + + const AudioFormat retry_audio_format = out_audio_format; + + retry_without_dsd: + success = ao_plugin_open(this, out_audio_format, error); + mutex.lock(); + + assert(!open); + + if (!success) { + FormatError(error, "Failed to open \"%s\" [%s]", + name, plugin.name); + + mutex.unlock(); + CloseFilter(); + mutex.lock(); + + fail_timer.Update(); + return; + } + + if (!convert_filter_set(convert_filter, out_audio_format, + error)) { + FormatError(error, "Failed to convert for \"%s\" [%s]", + name, plugin.name); + + mutex.unlock(); + ao_plugin_close(this); + + if (error.IsDomain(pcm_domain) && + out_audio_format.format == SampleFormat::DSD) { + /* if the audio output supports DSD, but not + the given sample rate, it asks MPD to + resample; resampling DSD however is not + implemented; our last resort is to give up + DSD and fall back to PCM */ + + // TODO: clean up this workaround + + FormatError(output_domain, "Retrying without DSD"); + + out_audio_format = retry_audio_format; + out_audio_format.format = SampleFormat::FLOAT; + + /* clear the Error to allow reusing it */ + error.Clear(); + + /* sorry for the "goto" - this is a workaround + for the stable branch that should be as + unintrusive as possible */ + goto retry_without_dsd; + } + + CloseFilter(); + mutex.lock(); + + fail_timer.Update(); + return; + } + + open = true; + + FormatDebug(output_domain, + "opened plugin=%s name=\"%s\" audio_format=%s", + plugin.name, name, + audio_format_to_string(out_audio_format, &af_string)); + + if (in_audio_format != out_audio_format) + FormatDebug(output_domain, "converting from %s", + audio_format_to_string(in_audio_format, + &af_string)); +} + +void +AudioOutput::Close(bool drain) +{ + assert(open); + + pipe = nullptr; + + current_chunk = nullptr; + open = false; + + mutex.unlock(); + + if (drain) + ao_plugin_drain(this); + else + ao_plugin_cancel(this); + + ao_plugin_close(this); + CloseFilter(); + + mutex.lock(); + + FormatDebug(output_domain, "closed plugin=%s name=\"%s\"", + plugin.name, name); +} + +void +AudioOutput::ReopenFilter() +{ + Error error; + + mutex.unlock(); + CloseFilter(); + mutex.lock(); + + const AudioFormat filter_audio_format = + OpenFilter(in_audio_format, error); + if (!filter_audio_format.IsDefined() || + !convert_filter_set(convert_filter, out_audio_format, + error)) { + FormatError(error, + "Failed to open filter for \"%s\" [%s]", + name, plugin.name); + + /* this is a little code duplication from Close(), + but we cannot call this function because we must + not call filter_close(filter) again */ + + pipe = nullptr; + + current_chunk = nullptr; + open = false; + fail_timer.Update(); + + mutex.unlock(); + ao_plugin_close(this); + mutex.lock(); + + return; + } +} + +void +AudioOutput::Reopen() +{ + if (!config_audio_format.IsFullyDefined()) { + if (open) { + const MusicPipe *mp = pipe; + Close(true); + pipe = mp; + } + + /* no audio format is configured: copy in->out, let + the output's open() method determine the effective + out_audio_format */ + out_audio_format = in_audio_format; + out_audio_format.ApplyMask(config_audio_format); + } + + if (open) + /* the audio format has changed, and all filters have + to be reconfigured */ + ReopenFilter(); + else + Open(); +} + +/** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a command + * was issued + */ +inline bool +AudioOutput::WaitForDelay() +{ + while (true) { + unsigned delay = ao_plugin_delay(this); + if (delay == 0) + return true; + + (void)cond.timed_wait(mutex, delay); + + if (command != AO_COMMAND_NONE) + return false; + } +} + +static ConstBuffer<void> +ao_chunk_data(AudioOutput *ao, const MusicChunk *chunk, + Filter *replay_gain_filter, + unsigned *replay_gain_serial_p) +{ + assert(chunk != nullptr); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); + + ConstBuffer<void> data(chunk->data, chunk->length); + + (void)ao; + + assert(data.size % ao->in_audio_format.GetFrameSize() == 0); + + if (!data.IsEmpty() && 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, error); + if (data.IsNull()) + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin.name); + } + + return data; +} + +static ConstBuffer<void> +ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk) +{ + ConstBuffer<void> data = + ao_chunk_data(ao, chunk, ao->replay_gain_filter, + &ao->replay_gain_serial); + if (data.IsEmpty()) + return data; + + /* cross-fade */ + + if (chunk->other != nullptr) { + ConstBuffer<void> other_data = + ao_chunk_data(ao, chunk->other, + ao->other_replay_gain_filter, + &ao->other_replay_gain_serial); + if (other_data.IsNull()) + return nullptr; + + if (other_data.IsEmpty()) + 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 (data.size > other_data.size) + data.size = other_data.size; + + float mix_ratio = chunk->mix_ratio; + if (mix_ratio >= 0) + /* reverse the mix ratio (because the + arguments to pcm_mix() are reversed), but + only if the mix ratio is non-negative; a + negative mix ratio is a MixRamp special + case */ + mix_ratio = 1.0 - mix_ratio; + + void *dest = ao->cross_fade_buffer.Get(other_data.size); + memcpy(dest, other_data.data, other_data.size); + if (!pcm_mix(ao->cross_fade_dither, dest, data.data, data.size, + ao->in_audio_format.format, + mix_ratio)) { + FormatError(output_domain, + "Cannot cross-fade format %s", + sample_format_to_string(ao->in_audio_format.format)); + return nullptr; + } + + data.data = dest; + data.size = other_data.size; + } + + /* apply filter chain */ + + Error error; + data = ao->filter->FilterPCM(data, error); + if (data.IsNull()) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin.name); + return nullptr; + } + + return data; +} + +inline bool +AudioOutput::PlayChunk(const MusicChunk *chunk) +{ + assert(filter != nullptr); + + if (tags && gcc_unlikely(chunk->tag != nullptr)) { + mutex.unlock(); + ao_plugin_send_tag(this, chunk->tag); + mutex.lock(); + } + + auto data = ConstBuffer<char>::FromVoid(ao_filter_chunk(this, chunk)); + if (data.IsNull()) { + Close(false); + + /* don't automatically reopen this device for 10 + seconds */ + fail_timer.Update(); + return false; + } + + Error error; + + while (!data.IsEmpty() && command == AO_COMMAND_NONE) { + if (!WaitForDelay()) + break; + + mutex.unlock(); + size_t nbytes = ao_plugin_play(this, data.data, data.size, + error); + mutex.lock(); + if (nbytes == 0) { + /* play()==0 means failure */ + FormatError(error, "\"%s\" [%s] failed to play", + name, plugin.name); + + Close(false); + + /* don't automatically reopen this device for + 10 seconds */ + assert(!fail_timer.IsDefined()); + fail_timer.Update(); + + return false; + } + + assert(nbytes <= data.size); + assert(nbytes % out_audio_format.GetFrameSize() == 0); + + data.data += nbytes; + data.size -= nbytes; + } + + return true; +} + +inline const MusicChunk * +AudioOutput::GetNextChunk() const +{ + return current_chunk != nullptr + /* continue the previous play() call */ + ? current_chunk->next + /* get the first chunk from the pipe */ + : pipe->Peek(); +} + +inline bool +AudioOutput::Play() +{ + assert(pipe != nullptr); + + const MusicChunk *chunk = GetNextChunk(); + if (chunk == nullptr) + /* no chunk available */ + return false; + + current_chunk_finished = false; + + assert(!in_playback_loop); + in_playback_loop = true; + + while (chunk != nullptr && command == AO_COMMAND_NONE) { + assert(!current_chunk_finished); + + current_chunk = chunk; + + if (!PlayChunk(chunk)) { + assert(current_chunk == nullptr); + break; + } + + assert(current_chunk == chunk); + chunk = chunk->next; + } + + assert(in_playback_loop); + in_playback_loop = false; + + current_chunk_finished = true; + + mutex.unlock(); + player_control->LockSignal(); + mutex.lock(); + + return true; +} + +inline void +AudioOutput::Pause() +{ + mutex.unlock(); + ao_plugin_cancel(this); + mutex.lock(); + + pause = true; + CommandFinished(); + + do { + if (!WaitForDelay()) + break; + + mutex.unlock(); + bool success = ao_plugin_pause(this); + mutex.lock(); + + if (!success) { + Close(false); + break; + } + } while (command == AO_COMMAND_NONE); + + pause = false; +} + +inline void +AudioOutput::Task() +{ + FormatThreadName("output:%s", name); + + SetThreadRealtime(); + SetThreadTimerSlackUS(100); + + mutex.lock(); + + while (1) { + switch (command) { + case AO_COMMAND_NONE: + break; + + case AO_COMMAND_ENABLE: + Enable(); + CommandFinished(); + break; + + case AO_COMMAND_DISABLE: + Disable(); + CommandFinished(); + break; + + case AO_COMMAND_OPEN: + Open(); + CommandFinished(); + break; + + case AO_COMMAND_REOPEN: + Reopen(); + CommandFinished(); + break; + + case AO_COMMAND_CLOSE: + assert(open); + assert(pipe != nullptr); + + Close(false); + CommandFinished(); + break; + + case AO_COMMAND_PAUSE: + if (!open) { + /* the output has failed after + audio_output_all_pause() has + submitted the PAUSE command; bail + out */ + CommandFinished(); + break; + } + + Pause(); + /* don't "break" here: this might cause + Play() to be called when command==CLOSE + ends the paused state - "continue" checks + the new command first */ + continue; + + case AO_COMMAND_DRAIN: + if (open) { + assert(current_chunk == nullptr); + assert(pipe->Peek() == nullptr); + + mutex.unlock(); + ao_plugin_drain(this); + mutex.lock(); + } + + CommandFinished(); + continue; + + case AO_COMMAND_CANCEL: + current_chunk = nullptr; + + if (open) { + mutex.unlock(); + ao_plugin_cancel(this); + mutex.lock(); + } + + CommandFinished(); + continue; + + case AO_COMMAND_KILL: + current_chunk = nullptr; + CommandFinished(); + mutex.unlock(); + return; + } + + if (open && allow_play && Play()) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; + + if (command == AO_COMMAND_NONE) { + woken_for_play = false; + cond.wait(mutex); + } + } +} + +void +AudioOutput::Task(void *arg) +{ + AudioOutput *ao = (AudioOutput *)arg; + ao->Task(); +} + +void +AudioOutput::StartThread() +{ + assert(command == AO_COMMAND_NONE); + + Error error; + if (!thread.Start(Task, this, error)) + FatalError(error); +} diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx new file mode 100644 index 000000000..566f6b6a8 --- /dev/null +++ b/src/output/Registry.cxx @@ -0,0 +1,104 @@ +/* + * 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 "Registry.hxx" +#include "OutputAPI.hxx" +#include "plugins/AlsaOutputPlugin.hxx" +#include "plugins/AoOutputPlugin.hxx" +#include "plugins/FifoOutputPlugin.hxx" +#include "plugins/httpd/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/sles/SlesOutputPlugin.hxx" +#include "plugins/SolarisOutputPlugin.hxx" +#include "plugins/WinmmOutputPlugin.hxx" + +#include <string.h> + +const AudioOutputPlugin *const audio_output_plugins[] = { +#ifdef HAVE_SHOUT + &shout_output_plugin, +#endif + &null_output_plugin, +#ifdef ANDROID + &sles_output_plugin, +#endif +#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 AudioOutputPlugin * +AudioOutputPlugin_get(const char *name) +{ + audio_output_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return nullptr; +} diff --git a/src/output/Registry.hxx b/src/output/Registry.hxx new file mode 100644 index 000000000..bc9c1ae2b --- /dev/null +++ b/src/output/Registry.hxx @@ -0,0 +1,35 @@ +/* + * 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 + +struct AudioOutputPlugin; + +extern const AudioOutputPlugin *const audio_output_plugins[]; + +const AudioOutputPlugin * +AudioOutputPlugin_get(const char *name); + +#define audio_output_plugins_for_each(plugin) \ + for (const AudioOutputPlugin *plugin, \ + *const*output_plugin_iterator = &audio_output_plugins[0]; \ + (plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator) + +#endif diff --git a/src/output/Timer.cxx b/src/output/Timer.cxx new file mode 100644 index 000000000..d3dcc714d --- /dev/null +++ b/src/output/Timer.cxx @@ -0,0 +1,67 @@ +/* + * 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 "Timer.hxx" +#include "AudioFormat.hxx" +#include "system/Clock.hxx" + +#include <limits> + +#include <assert.h> + +Timer::Timer(const AudioFormat af) + : time(0), + started(false), + rate(af.sample_rate * af.GetFrameSize()) +{ +} + +void Timer::Start() +{ + time = MonotonicClockUS(); + started = true; +} + +void Timer::Reset() +{ + time = 0; + started = false; +} + +void Timer::Add(int size) +{ + assert(started); + + // (size samples) / (rate samples per second) = duration seconds + // duration seconds * 1000000 = duration us + time += ((uint64_t)size * 1000000) / rate; +} + +unsigned Timer::GetDelay() const +{ + int64_t delay = (int64_t)(time - MonotonicClockUS()) / 1000; + if (delay < 0) + return 0; + + if (delay > std::numeric_limits<int>::max()) + delay = std::numeric_limits<int>::max(); + + return delay; +} diff --git a/src/output/Timer.hxx b/src/output/Timer.hxx new file mode 100644 index 000000000..3c935cfac --- /dev/null +++ b/src/output/Timer.hxx @@ -0,0 +1,47 @@ +/* + * 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_TIMER_HXX +#define MPD_TIMER_HXX + +#include <stdint.h> + +struct AudioFormat; + +class Timer { + uint64_t time; + bool started; + const int rate; +public: + explicit Timer(AudioFormat af); + + bool IsStarted() const { return started; } + + void Start(); + void Reset(); + + void Add(int size); + + /** + * Returns the number of milliseconds to sleep to get back to sync. + */ + unsigned GetDelay() const; +}; + +#endif diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index f8aae13a1..28c374a00 100644 --- a/src/output/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,33 +19,36 @@ #include "config.h" #include "AlsaOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" #include "pcm/PcmExport.hxx" +#include "config/ConfigError.hxx" #include "util/Manual.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "util/ConstBuffer.hxx" #include "Log.hxx" -#include <glib.h> #include <alsa/asoundlib.h> #include <string> -#define ALSA_PCM_NEW_HW_PARAMS_API -#define ALSA_PCM_NEW_SW_PARAMS_API +#if SND_LIB_VERSION >= 0x1001c +/* alsa-lib supports DSD since version 1.0.27.1 */ +#define HAVE_ALSA_DSD +#endif static const char default_device[] = "default"; static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000; -#define MPD_ALSA_RETRY_NR 5 +static constexpr unsigned MPD_ALSA_RETRY_NR = 5; typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, snd_pcm_uframes_t size); struct AlsaOutput { - struct audio_output base; + AudioOutput base; Manual<PcmExport> pcm_export; @@ -59,12 +62,11 @@ struct AlsaOutput { bool use_mmap; /** - * Enable DSD over USB according to the dCS suggested - * standard? + * Enable DSD over PCM according to the DoP standard standard? * - * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf + * @see http://dsd-guide.com/dop-open-standard */ - bool dsd_usb; + bool dop; /** libasound's buffer_time setting (in microseconds) */ unsigned int buffer_time; @@ -122,19 +124,14 @@ struct AlsaOutput { * It contains silence samples, enough to fill one period (see * #period_frames). */ - void *silence; - - AlsaOutput():mode(0), writei(snd_pcm_writei) { - } + uint8_t *silence; - bool Init(const config_param ¶m, Error &error) { - return ao_base_init(&base, &alsa_output_plugin, - param, error); + AlsaOutput() + :base(alsa_output_plugin), + mode(0), writei(snd_pcm_writei) { } - void Deinit() { - ao_base_finish(&base); - } + bool Configure(const config_param ¶m, Error &error); }; static constexpr Domain alsa_output_domain("alsa_output"); @@ -145,56 +142,60 @@ alsa_device(const AlsaOutput *ad) return ad->device.empty() ? default_device : ad->device.c_str(); } -static void -alsa_configure(AlsaOutput *ad, const config_param ¶m) +inline bool +AlsaOutput::Configure(const config_param ¶m, Error &error) { - ad->device = param.GetBlockValue("device", ""); + if (!base.Configure(param, error)) + return false; - ad->use_mmap = param.GetBlockValue("use_mmap", false); + device = param.GetBlockValue("device", ""); - ad->dsd_usb = param.GetBlockValue("dsd_usb", false); + use_mmap = param.GetBlockValue("use_mmap", false); - ad->buffer_time = param.GetBlockValue("buffer_time", + dop = param.GetBlockValue("dop", false) || + /* legacy name from MPD 0.18 and older: */ + param.GetBlockValue("dsd_usb", false); + + buffer_time = param.GetBlockValue("buffer_time", MPD_ALSA_BUFFER_TIME_US); - ad->period_time = param.GetBlockValue("period_time", 0u); + period_time = param.GetBlockValue("period_time", 0u); #ifdef SND_PCM_NO_AUTO_RESAMPLE if (!param.GetBlockValue("auto_resample", true)) - ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; + mode |= SND_PCM_NO_AUTO_RESAMPLE; #endif #ifdef SND_PCM_NO_AUTO_CHANNELS if (!param.GetBlockValue("auto_channels", true)) - ad->mode |= SND_PCM_NO_AUTO_CHANNELS; + mode |= SND_PCM_NO_AUTO_CHANNELS; #endif #ifdef SND_PCM_NO_AUTO_FORMAT if (!param.GetBlockValue("auto_format", true)) - ad->mode |= SND_PCM_NO_AUTO_FORMAT; + mode |= SND_PCM_NO_AUTO_FORMAT; #endif + + return true; } -static struct audio_output * +static AudioOutput * alsa_init(const config_param ¶m, Error &error) { AlsaOutput *ad = new AlsaOutput(); - if (!ad->Init(param, error)) { + if (!ad->Configure(param, error)) { delete ad; return nullptr; } - alsa_configure(ad, param); - return &ad->base; } static void -alsa_finish(struct audio_output *ao) +alsa_finish(AudioOutput *ao) { AlsaOutput *ad = (AlsaOutput *)ao; - ad->Deinit(); delete ad; /* free libasound's config cache */ @@ -202,7 +203,7 @@ alsa_finish(struct audio_output *ao) } static bool -alsa_output_enable(struct audio_output *ao, gcc_unused Error &error) +alsa_output_enable(AudioOutput *ao, gcc_unused Error &error) { AlsaOutput *ad = (AlsaOutput *)ao; @@ -211,7 +212,7 @@ alsa_output_enable(struct audio_output *ao, gcc_unused Error &error) } static void -alsa_output_disable(struct audio_output *ao) +alsa_output_disable(AudioOutput *ao) { AlsaOutput *ad = (AlsaOutput *)ao; @@ -219,12 +220,12 @@ alsa_output_disable(struct audio_output *ao) } static bool -alsa_test_default_device(void) +alsa_test_default_device() { snd_pcm_t *handle; int ret = snd_pcm_open(&handle, default_device, - SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (ret) { FormatError(alsa_output_domain, "Error opening default ALSA device: %s", @@ -236,13 +237,24 @@ alsa_test_default_device(void) return true; } +/** + * Convert MPD's #SampleFormat enum to libasound's snd_pcm_format_t + * enum. Returns SND_PCM_FORMAT_UNKNOWN if there is no according ALSA + * PCM format. + */ static snd_pcm_format_t get_bitformat(SampleFormat sample_format) { switch (sample_format) { case SampleFormat::UNDEFINED: + return SND_PCM_FORMAT_UNKNOWN; + case SampleFormat::DSD: +#ifdef HAVE_ALSA_DSD + return SND_PCM_FORMAT_DSD_U8; +#else return SND_PCM_FORMAT_UNKNOWN; +#endif case SampleFormat::S8: return SND_PCM_FORMAT_S8; @@ -264,10 +276,14 @@ get_bitformat(SampleFormat sample_format) gcc_unreachable(); } +/** + * Determine the byte-swapped PCM format. Returns + * SND_PCM_FORMAT_UNKNOWN if the format cannot be byte-swapped. + */ static snd_pcm_format_t byteswap_bitformat(snd_pcm_format_t fmt) { - switch(fmt) { + switch (fmt) { case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; @@ -285,6 +301,10 @@ byteswap_bitformat(snd_pcm_format_t fmt) } } +/** + * Check if there is a "packed" version of the give PCM format. + * Returns SND_PCM_FORMAT_UNKNOWN if not. + */ static snd_pcm_format_t alsa_to_packed_format(snd_pcm_format_t fmt) { @@ -300,6 +320,10 @@ alsa_to_packed_format(snd_pcm_format_t fmt) } } +/** + * Attempts to configure the specified sample format. On failure, + * fall back to the packed version. + */ static int alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, snd_pcm_format_t fmt, bool *packed_r) @@ -370,7 +394,7 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, /* if unsupported by the hardware, try other formats */ - static const SampleFormat probe_formats[] = { + static constexpr SampleFormat probe_formats[] = { SampleFormat::S24_P32, SampleFormat::S32, SampleFormat::S16, @@ -406,7 +430,7 @@ alsa_setup(AlsaOutput *ad, AudioFormat &audio_format, unsigned int channels = audio_format.channels; int err; const char *cmd = nullptr; - int retry = MPD_ALSA_RETRY_NR; + unsigned retry = MPD_ALSA_RETRY_NR; unsigned int period_time, period_time_ro; unsigned int buffer_time; @@ -597,8 +621,8 @@ configure_hw: ad->period_frames = alsa_period_size; ad->period_position = 0; - ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm, - alsa_period_size)); + ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm, + alsa_period_size)]; snd_pcm_format_set_silence(format, ad->silence, alsa_period_size * channels); @@ -612,40 +636,40 @@ error: } static bool -alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format, +alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format, bool *shift8_r, bool *packed_r, bool *reverse_endian_r, Error &error) { - assert(ad->dsd_usb); + assert(ad->dop); assert(audio_format.format == SampleFormat::DSD); /* pass 24 bit to alsa_setup() */ - AudioFormat usb_format = audio_format; - usb_format.format = SampleFormat::S24_P32; - usb_format.sample_rate /= 2; + AudioFormat dop_format = audio_format; + dop_format.format = SampleFormat::S24_P32; + dop_format.sample_rate /= 2; - const AudioFormat check = usb_format; + const AudioFormat check = dop_format; - if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error)) + if (!alsa_setup(ad, dop_format, packed_r, reverse_endian_r, error)) return false; - /* if the device allows only 32 bit, shift all DSD-over-USB + /* if the device allows only 32 bit, shift all DoP samples left by 8 bit and leave the lower 8 bit cleared; the DSD-over-USB documentation does not specify whether this is legal, but there is anecdotical evidence that this is possible (and the only option for some devices) */ - *shift8_r = usb_format.format == SampleFormat::S32; - if (usb_format.format == SampleFormat::S32) - usb_format.format = SampleFormat::S24_P32; + *shift8_r = dop_format.format == SampleFormat::S32; + if (dop_format.format == SampleFormat::S32) + dop_format.format = SampleFormat::S24_P32; - if (usb_format != check) { + if (dop_format != check) { /* no bit-perfect playback, which is required for DSD over USB */ error.Format(alsa_output_domain, - "Failed to configure DSD-over-USB on ALSA device \"%s\"", + "Failed to configure DSD-over-PCM on ALSA device \"%s\"", alsa_device(ad)); - g_free(ad->silence); + delete[] ad->silence; return false; } @@ -653,15 +677,15 @@ alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format, } static bool -alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format, +alsa_setup_or_dop(AlsaOutput *ad, AudioFormat &audio_format, Error &error) { bool shift8 = false, packed, reverse_endian; - const bool dsd_usb = ad->dsd_usb && + const bool dop = ad->dop && audio_format.format == SampleFormat::DSD; - const bool success = dsd_usb - ? alsa_setup_dsd(ad, audio_format, + const bool success = dop + ? alsa_setup_dop(ad, audio_format, &shift8, &packed, &reverse_endian, error) : alsa_setup(ad, audio_format, &packed, &reverse_endian, @@ -671,12 +695,12 @@ alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format, ad->pcm_export->Open(audio_format.format, audio_format.channels, - dsd_usb, shift8, packed, reverse_endian); + dop, shift8, packed, reverse_endian); return true; } static bool -alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) +alsa_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { AlsaOutput *ad = (AlsaOutput *)ao; @@ -693,7 +717,7 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) snd_pcm_name(ad->pcm), snd_pcm_type_name(snd_pcm_type(ad->pcm))); - if (!alsa_setup_or_dsd(ad, audio_format, error)) { + if (!alsa_setup_or_dop(ad, audio_format, error)) { snd_pcm_close(ad->pcm); return false; } @@ -756,7 +780,7 @@ alsa_recover(AlsaOutput *ad, int err) } static void -alsa_drain(struct audio_output *ao) +alsa_drain(AudioOutput *ao) { AlsaOutput *ad = (AlsaOutput *)ao; @@ -777,7 +801,7 @@ alsa_drain(struct audio_output *ao) } static void -alsa_cancel(struct audio_output *ao) +alsa_cancel(AudioOutput *ao) { AlsaOutput *ad = (AlsaOutput *)ao; @@ -788,16 +812,16 @@ alsa_cancel(struct audio_output *ao) } static void -alsa_close(struct audio_output *ao) +alsa_close(AudioOutput *ao) { AlsaOutput *ad = (AlsaOutput *)ao; snd_pcm_close(ad->pcm); - g_free(ad->silence); + delete[] ad->silence; } static size_t -alsa_play(struct audio_output *ao, const void *chunk, size_t size, +alsa_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { AlsaOutput *ad = (AlsaOutput *)ao; @@ -815,16 +839,18 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size, } } - const size_t original_size = size; - chunk = ad->pcm_export->Export(chunk, size, size); - if (size == 0) + const auto e = ad->pcm_export->Export({chunk, size}); + if (e.size == 0) /* the DoP (DSD over PCM) filter converts two frames at a time and ignores the last odd frame; if there was only one frame (e.g. the last frame in the file), the result is empty; to avoid an endless loop, bail out here, and pretend the one frame has been played */ - return original_size; + return size; + + chunk = e.data; + size = e.size; assert(size % ad->out_frame_size == 0); @@ -849,7 +875,7 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size, } } -const struct audio_output_plugin alsa_output_plugin = { +const struct AudioOutputPlugin alsa_output_plugin = { "alsa", alsa_test_default_device, alsa_init, diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx index dc7e639a8..f72116f91 100644 --- a/src/output/AlsaOutputPlugin.hxx +++ b/src/output/plugins/AlsaOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX #define MPD_ALSA_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin alsa_output_plugin; +extern const struct AudioOutputPlugin alsa_output_plugin; #endif diff --git a/src/output/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx index e66969e20..af8c88fa1 100644 --- a/src/output/AoOutputPlugin.cxx +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -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" @@ -35,20 +35,18 @@ static ao_sample_format OUR_AO_FORMAT_INITIALIZER; static unsigned ao_output_ref; struct AoOutput { - struct audio_output base; + AudioOutput base; size_t write_size; int driver; ao_option *options; ao_device *device; - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &ao_output_plugin, param, - error); - } + AoOutput() + :base(ao_output_plugin) {} - void Deinitialize() { - ao_base_finish(&base); + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); } bool Configure(const config_param ¶m, Error &error); @@ -152,7 +150,7 @@ AoOutput::Configure(const config_param ¶m, Error &error) return true; } -static struct audio_output * +static AudioOutput * ao_output_init(const config_param ¶m, Error &error) { AoOutput *ad = new AoOutput(); @@ -163,7 +161,6 @@ ao_output_init(const config_param ¶m, Error &error) } if (!ad->Configure(param, error)) { - ad->Deinitialize(); delete ad; return nullptr; } @@ -172,12 +169,11 @@ ao_output_init(const config_param ¶m, Error &error) } static void -ao_output_finish(struct audio_output *ao) +ao_output_finish(AudioOutput *ao) { AoOutput *ad = (AoOutput *)ao; ao_free_options(ad->options); - ad->Deinitialize(); delete ad; ao_output_ref--; @@ -187,7 +183,7 @@ ao_output_finish(struct audio_output *ao) } static void -ao_output_close(struct audio_output *ao) +ao_output_close(AudioOutput *ao) { AoOutput *ad = (AoOutput *)ao; @@ -195,7 +191,7 @@ ao_output_close(struct audio_output *ao) } static bool -ao_output_open(struct audio_output *ao, AudioFormat &audio_format, +ao_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; @@ -251,7 +247,7 @@ static int ao_play_deconst(ao_device *device, const void *output_samples, } static size_t -ao_output_play(struct audio_output *ao, const void *chunk, size_t size, +ao_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { AoOutput *ad = (AoOutput *)ao; @@ -267,7 +263,7 @@ ao_output_play(struct audio_output *ao, const void *chunk, size_t size, return size; } -const struct audio_output_plugin ao_output_plugin = { +const struct AudioOutputPlugin ao_output_plugin = { "ao", nullptr, ao_output_init, diff --git a/src/output/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx index a44885e56..07c2ba16b 100644 --- a/src/output/AoOutputPlugin.hxx +++ b/src/output/plugins/AoOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_AO_OUTPUT_PLUGIN_HXX #define MPD_AO_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin ao_output_plugin; +extern const struct AudioOutputPlugin ao_output_plugin; #endif diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx index aeb9a6a87..9df5a74dd 100644 --- a/src/output/FifoOutputPlugin.cxx +++ b/src/output/plugins/FifoOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,10 +19,9 @@ #include "config.h" #include "FifoOutputPlugin.hxx" -#include "ConfigError.hxx" -#include "OutputAPI.hxx" -#include "Timer.hxx" -#include "system/fd_util.h" +#include "config/ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "../Timer.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" #include "util/Error.hxx" @@ -30,16 +29,14 @@ #include "Log.hxx" #include "open.h" -#include <sys/types.h> #include <sys/stat.h> #include <errno.h> -#include <string.h> #include <unistd.h> #define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ struct FifoOutput { - struct audio_output base; + AudioOutput base; AllocatedPath path; std::string path_utf8; @@ -50,16 +47,12 @@ struct FifoOutput { Timer *timer; FifoOutput() - :path(AllocatedPath::Null()), input(-1), output(-1), + :base(fifo_output_plugin), + path(AllocatedPath::Null()), input(-1), output(-1), created(false) {} bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &fifo_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); + return base.Configure(param, error); } bool Create(Error &error); @@ -175,7 +168,7 @@ fifo_open(FifoOutput *fd, Error &error) return fd->Open(error); } -static struct audio_output * +static AudioOutput * fifo_output_init(const config_param ¶m, Error &error) { FifoOutput *fd = new FifoOutput(); @@ -198,7 +191,6 @@ fifo_output_init(const config_param ¶m, Error &error) } if (!fifo_open(fd, error)) { - fd->Deinitialize(); delete fd; return nullptr; } @@ -207,17 +199,16 @@ fifo_output_init(const config_param ¶m, Error &error) } static void -fifo_output_finish(struct audio_output *ao) +fifo_output_finish(AudioOutput *ao) { FifoOutput *fd = (FifoOutput *)ao; fd->Close(); - fd->Deinitialize(); delete fd; } static bool -fifo_output_open(struct audio_output *ao, AudioFormat &audio_format, +fifo_output_open(AudioOutput *ao, AudioFormat &audio_format, gcc_unused Error &error) { FifoOutput *fd = (FifoOutput *)ao; @@ -228,7 +219,7 @@ fifo_output_open(struct audio_output *ao, AudioFormat &audio_format, } static void -fifo_output_close(struct audio_output *ao) +fifo_output_close(AudioOutput *ao) { FifoOutput *fd = (FifoOutput *)ao; @@ -236,7 +227,7 @@ fifo_output_close(struct audio_output *ao) } static void -fifo_output_cancel(struct audio_output *ao) +fifo_output_cancel(AudioOutput *ao) { FifoOutput *fd = (FifoOutput *)ao; char buf[FIFO_BUFFER_SIZE]; @@ -255,7 +246,7 @@ fifo_output_cancel(struct audio_output *ao) } static unsigned -fifo_output_delay(struct audio_output *ao) +fifo_output_delay(AudioOutput *ao) { FifoOutput *fd = (FifoOutput *)ao; @@ -265,7 +256,7 @@ fifo_output_delay(struct audio_output *ao) } static size_t -fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, +fifo_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { FifoOutput *fd = (FifoOutput *)ao; @@ -297,7 +288,7 @@ fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, } } -const struct audio_output_plugin fifo_output_plugin = { +const struct AudioOutputPlugin fifo_output_plugin = { "fifo", nullptr, fifo_output_init, diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx index dca2886d8..f41ceded6 100644 --- a/src/output/FifoOutputPlugin.hxx +++ b/src/output/plugins/FifoOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX #define MPD_FIFO_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin fifo_output_plugin; +extern const struct AudioOutputPlugin fifo_output_plugin; #endif diff --git a/src/output/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index 7ed672f95..e1dad7893 100644 --- a/src/output/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,8 +19,8 @@ #include "config.h" #include "JackOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "config/ConfigError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -34,10 +34,6 @@ #include <stdlib.h> #include <string.h> -#include <stdio.h> -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> enum { MAX_PORTS = 16, @@ -46,7 +42,7 @@ enum { static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); struct JackOutput { - struct audio_output base; + AudioOutput base; /** * libjack options passed to jack_client_open(). @@ -83,13 +79,11 @@ struct JackOutput { */ bool pause; - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &jack_output_plugin, param, - error_r); - } + JackOutput() + :base(jack_output_plugin) {} - void Deinitialize() { - ao_base_finish(&base); + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); } }; @@ -314,7 +308,7 @@ parse_port_list(const char *source, char **dest, Error &error) return n; } -static struct audio_output * +static AudioOutput * mpd_jack_init(const config_param ¶m, Error &error) { JackOutput *jd = new JackOutput(); @@ -393,7 +387,7 @@ mpd_jack_init(const config_param ¶m, Error &error) } static void -mpd_jack_finish(struct audio_output *ao) +mpd_jack_finish(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; @@ -403,12 +397,11 @@ mpd_jack_finish(struct audio_output *ao) for (unsigned i = 0; i < jd->num_destination_ports; ++i) g_free(jd->destination_ports[i]); - jd->Deinitialize(); delete jd; } static bool -mpd_jack_enable(struct audio_output *ao, Error &error) +mpd_jack_enable(AudioOutput *ao, Error &error) { JackOutput *jd = (JackOutput *)ao; @@ -419,7 +412,7 @@ mpd_jack_enable(struct audio_output *ao, Error &error) } static void -mpd_jack_disable(struct audio_output *ao) +mpd_jack_disable(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; @@ -583,7 +576,7 @@ mpd_jack_start(JackOutput *jd, Error &error) } static bool -mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format, +mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { JackOutput *jd = (JackOutput *)ao; @@ -608,7 +601,7 @@ mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format, } static void -mpd_jack_close(gcc_unused struct audio_output *ao) +mpd_jack_close(gcc_unused AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; @@ -616,7 +609,7 @@ mpd_jack_close(gcc_unused struct audio_output *ao) } static unsigned -mpd_jack_delay(struct audio_output *ao) +mpd_jack_delay(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; @@ -693,7 +686,7 @@ mpd_jack_write_samples(JackOutput *jd, const void *src, } static size_t -mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, +mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { JackOutput *jd = (JackOutput *)ao; @@ -738,7 +731,7 @@ mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, } static bool -mpd_jack_pause(struct audio_output *ao) +mpd_jack_pause(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; @@ -750,7 +743,7 @@ mpd_jack_pause(struct audio_output *ao) return true; } -const struct audio_output_plugin jack_output_plugin = { +const struct AudioOutputPlugin jack_output_plugin = { "jack", mpd_jack_test_default_device, mpd_jack_init, diff --git a/src/output/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx index 908105ad2..6f1f7ecb9 100644 --- a/src/output/JackOutputPlugin.hxx +++ b/src/output/plugins/JackOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_JACK_OUTPUT_PLUGIN_HXX #define MPD_JACK_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin jack_output_plugin; +extern const struct AudioOutputPlugin jack_output_plugin; #endif diff --git a/src/output/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx index e2eec9dbc..098f58926 100644 --- a/src/output/NullOutputPlugin.cxx +++ b/src/output/plugins/NullOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,29 +19,25 @@ #include "config.h" #include "NullOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "Timer.hxx" - -#include <assert.h> +#include "../OutputAPI.hxx" +#include "../Timer.hxx" struct NullOutput { - struct audio_output base; + AudioOutput base; bool sync; Timer *timer; - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &null_output_plugin, param, - error); - } + NullOutput() + :base(null_output_plugin) {} - void Deinitialize() { - ao_base_finish(&base); + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); } }; -static struct audio_output * +static AudioOutput * null_init(const config_param ¶m, Error &error) { NullOutput *nd = new NullOutput(); @@ -57,16 +53,15 @@ null_init(const config_param ¶m, Error &error) } static void -null_finish(struct audio_output *ao) +null_finish(AudioOutput *ao) { NullOutput *nd = (NullOutput *)ao; - nd->Deinitialize(); delete nd; } static bool -null_open(struct audio_output *ao, AudioFormat &audio_format, +null_open(AudioOutput *ao, AudioFormat &audio_format, gcc_unused Error &error) { NullOutput *nd = (NullOutput *)ao; @@ -78,7 +73,7 @@ null_open(struct audio_output *ao, AudioFormat &audio_format, } static void -null_close(struct audio_output *ao) +null_close(AudioOutput *ao) { NullOutput *nd = (NullOutput *)ao; @@ -87,7 +82,7 @@ null_close(struct audio_output *ao) } static unsigned -null_delay(struct audio_output *ao) +null_delay(AudioOutput *ao) { NullOutput *nd = (NullOutput *)ao; @@ -97,7 +92,7 @@ null_delay(struct audio_output *ao) } static size_t -null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size, +null_play(AudioOutput *ao, gcc_unused const void *chunk, size_t size, gcc_unused Error &error) { NullOutput *nd = (NullOutput *)ao; @@ -114,7 +109,7 @@ null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size, } static void -null_cancel(struct audio_output *ao) +null_cancel(AudioOutput *ao) { NullOutput *nd = (NullOutput *)ao; @@ -124,7 +119,7 @@ null_cancel(struct audio_output *ao) nd->timer->Reset(); } -const struct audio_output_plugin null_output_plugin = { +const struct AudioOutputPlugin null_output_plugin = { "null", nullptr, null_init, diff --git a/src/output/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx index a58f1cb13..f25f5b9f3 100644 --- a/src/output/NullOutputPlugin.hxx +++ b/src/output/plugins/NullOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_NULL_OUTPUT_PLUGIN_HXX #define MPD_NULL_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin null_output_plugin; +extern const struct AudioOutputPlugin null_output_plugin; #endif diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx index 97ebae056..13ac7b35e 100644 --- a/src/output/OSXOutputPlugin.cxx +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,8 +19,8 @@ #include "config.h" #include "OSXOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "util/fifo_buffer.h" +#include "../OutputAPI.hxx" +#include "util/DynamicFifoBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "thread/Mutex.hxx" @@ -33,7 +33,7 @@ #include <CoreServices/CoreServices.h> struct OSXOutput { - struct audio_output base; + AudioOutput base; /* configuration settings */ OSType component_subtype; @@ -44,7 +44,10 @@ struct OSXOutput { Mutex mutex; Cond condition; - struct fifo_buffer *buffer; + DynamicFifoBuffer<uint8_t> *buffer; + + OSXOutput() + :base(osx_output_plugin) {} }; static constexpr Domain osx_output_domain("osx_output"); @@ -72,16 +75,16 @@ osx_output_configure(OSXOutput *oo, const config_param ¶m) } else { oo->component_subtype = kAudioUnitSubType_HALOutput; - /* XXX am I supposed to g_strdup() this? */ + /* XXX am I supposed to strdup() this? */ oo->device_name = device; } } -static struct audio_output * +static AudioOutput * osx_output_init(const config_param ¶m, Error &error) { OSXOutput *oo = new OSXOutput(); - if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) { + if (!oo->base.Configure(param, error)) { delete oo; return NULL; } @@ -92,7 +95,7 @@ osx_output_init(const config_param ¶m, Error &error) } static void -osx_output_finish(struct audio_output *ao) +osx_output_finish(AudioOutput *ao) { OSXOutput *oo = (OSXOutput *)ao; @@ -207,22 +210,19 @@ osx_render(void *vdata, od->mutex.lock(); - size_t nbytes; - const void *src = fifo_buffer_read(od->buffer, &nbytes); - - if (src != NULL) { - if (nbytes > buffer_size) - nbytes = buffer_size; + auto src = od->buffer->Read(); + if (!src.IsEmpty()) { + if (src.size > buffer_size) + src.size = buffer_size; - memcpy(buffer->mData, src, nbytes); - fifo_buffer_consume(od->buffer, nbytes); - } else - nbytes = 0; + memcpy(buffer->mData, src.data, src.size); + od->buffer->Consume(src.size); + } od->condition.signal(); od->mutex.unlock(); - buffer->mDataByteSize = nbytes; + buffer->mDataByteSize = src.size; unsigned i; for (i = 1; i < buffer_list->mNumberBuffers; ++i) { @@ -234,7 +234,7 @@ osx_render(void *vdata, } static bool -osx_output_enable(struct audio_output *ao, Error &error) +osx_output_enable(AudioOutput *ao, Error &error) { OSXOutput *oo = (OSXOutput *)ao; @@ -285,7 +285,7 @@ osx_output_enable(struct audio_output *ao, Error &error) } static void -osx_output_disable(struct audio_output *ao) +osx_output_disable(AudioOutput *ao) { OSXOutput *oo = (OSXOutput *)ao; @@ -293,27 +293,27 @@ osx_output_disable(struct audio_output *ao) } static void -osx_output_cancel(struct audio_output *ao) +osx_output_cancel(AudioOutput *ao) { OSXOutput *od = (OSXOutput *)ao; const ScopeLock protect(od->mutex); - fifo_buffer_clear(od->buffer); + od->buffer->Clear(); } static void -osx_output_close(struct audio_output *ao) +osx_output_close(AudioOutput *ao) { OSXOutput *od = (OSXOutput *)ao; AudioOutputUnitStop(od->au); AudioUnitUninitialize(od->au); - fifo_buffer_free(od->buffer); + delete od->buffer; } static bool -osx_output_open(struct audio_output *ao, AudioFormat &audio_format, +osx_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { OSXOutput *od = (OSXOutput *)ao; @@ -370,8 +370,8 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format, } /* create a buffer of 1s */ - od->buffer = fifo_buffer_new(audio_format.sample_rate * - audio_format.GetFrameSize()); + od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate * + audio_format.GetFrameSize()); status = AudioOutputUnitStart(od->au); if (status != 0) { @@ -386,35 +386,33 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format, } static size_t -osx_output_play(struct audio_output *ao, const void *chunk, size_t size, +osx_output_play(AudioOutput *ao, const void *chunk, size_t size, gcc_unused Error &error) { OSXOutput *od = (OSXOutput *)ao; const ScopeLock protect(od->mutex); - void *dest; - size_t max_length; - + DynamicFifoBuffer<uint8_t>::Range dest; while (true) { - dest = fifo_buffer_write(od->buffer, &max_length); - if (dest != NULL) + dest = od->buffer->Write(); + if (!dest.IsEmpty()) break; /* wait for some free space in the buffer */ od->condition.wait(od->mutex); } - if (size > max_length) - size = max_length; + if (size > dest.size) + size = dest.size; - memcpy(dest, chunk, size); - fifo_buffer_append(od->buffer, size); + memcpy(dest.data, chunk, size); + od->buffer->Append(size); return size; } -const struct audio_output_plugin osx_output_plugin = { +const struct AudioOutputPlugin osx_output_plugin = { "osx", osx_output_test_default_device, osx_output_init, diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx index 2a4172880..d7aed40b6 100644 --- a/src/output/OSXOutputPlugin.hxx +++ b/src/output/plugins/OSXOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_OSX_OUTPUT_PLUGIN_HXX #define MPD_OSX_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin osx_output_plugin; +extern const struct AudioOutputPlugin osx_output_plugin; #endif diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx index 268cf17cc..2f095c0a4 100644 --- a/src/output/OpenALOutputPlugin.cxx +++ b/src/output/plugins/OpenALOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,11 +19,11 @@ #include "config.h" #include "OpenALOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include <glib.h> +#include <unistd.h> #ifndef __APPLE__ #include <AL/al.h> @@ -37,7 +37,7 @@ #define NUM_BUFFERS 16 struct OpenALOutput { - struct audio_output base; + AudioOutput base; const char *device_name; ALCdevice *device; @@ -48,13 +48,11 @@ struct OpenALOutput { ALenum format; ALuint frequency; - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &openal_output_plugin, param, - error_r); - } + OpenALOutput() + :base(openal_output_plugin) {} - void Deinitialize() { - ao_base_finish(&base); + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); } }; @@ -133,7 +131,7 @@ openal_setup_context(OpenALOutput *od, Error &error) return true; } -static struct audio_output * +static AudioOutput * openal_init(const config_param ¶m, Error &error) { const char *device_name = param.GetBlockValue("device"); @@ -153,16 +151,15 @@ openal_init(const config_param ¶m, Error &error) } static void -openal_finish(struct audio_output *ao) +openal_finish(AudioOutput *ao) { OpenALOutput *od = (OpenALOutput *)ao; - od->Deinitialize(); delete od; } static bool -openal_open(struct audio_output *ao, AudioFormat &audio_format, +openal_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { OpenALOutput *od = (OpenALOutput *)ao; @@ -196,7 +193,7 @@ openal_open(struct audio_output *ao, AudioFormat &audio_format, } static void -openal_close(struct audio_output *ao) +openal_close(AudioOutput *ao) { OpenALOutput *od = (OpenALOutput *)ao; @@ -208,7 +205,7 @@ openal_close(struct audio_output *ao) } static unsigned -openal_delay(struct audio_output *ao) +openal_delay(AudioOutput *ao) { OpenALOutput *od = (OpenALOutput *)ao; @@ -221,7 +218,7 @@ openal_delay(struct audio_output *ao) } static size_t -openal_play(struct audio_output *ao, const void *chunk, size_t size, +openal_play(AudioOutput *ao, const void *chunk, size_t size, gcc_unused Error &error) { OpenALOutput *od = (OpenALOutput *)ao; @@ -238,7 +235,7 @@ openal_play(struct audio_output *ao, const void *chunk, size_t size, } else { /* wait for processed buffer */ while (!openal_has_processed(od)) - g_usleep(10); + usleep(10); alSourceUnqueueBuffers(od->source, 1, &buffer); } @@ -253,7 +250,7 @@ openal_play(struct audio_output *ao, const void *chunk, size_t size, } static void -openal_cancel(struct audio_output *ao) +openal_cancel(AudioOutput *ao) { OpenALOutput *od = (OpenALOutput *)ao; @@ -266,7 +263,7 @@ openal_cancel(struct audio_output *ao) od->filled = 0; } -const struct audio_output_plugin openal_output_plugin = { +const struct AudioOutputPlugin openal_output_plugin = { "openal", nullptr, openal_init, diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx index e1ebf3d4f..a27e6b53c 100644 --- a/src/output/OpenALOutputPlugin.hxx +++ b/src/output/plugins/OpenALOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX #define MPD_OPENAL_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin openal_output_plugin; +extern const struct AudioOutputPlugin openal_output_plugin; #endif diff --git a/src/output/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx index cdde6d562..39d87fc35 100644 --- a/src/output/OssOutputPlugin.cxx +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,9 +19,10 @@ #include "config.h" #include "OssOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" #include "system/fd_util.h" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "util/Macros.hxx" @@ -57,7 +58,7 @@ #endif struct OssOutput { - struct audio_output base; + AudioOutput base; #ifdef AFMT_S24_PACKED Manual<PcmExport> pcm_export; @@ -78,15 +79,12 @@ struct OssOutput { */ int oss_format; - OssOutput():fd(-1), device(nullptr) {} + OssOutput() + :base(oss_output_plugin), + fd(-1), device(nullptr) {} bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &oss_output_plugin, param, - error_r); - } - - void Deinitialize() { - ao_base_finish(&base); + return base.Configure(param, error_r); } }; @@ -149,7 +147,7 @@ oss_output_test_default_device(void) return false; } -static struct audio_output * +static AudioOutput * oss_open_default(Error &error) { int err[ARRAY_SIZE(default_devices)]; @@ -199,7 +197,7 @@ oss_open_default(Error &error) return NULL; } -static struct audio_output * +static AudioOutput * oss_output_init(const config_param ¶m, Error &error) { const char *device = param.GetBlockValue("device"); @@ -218,18 +216,17 @@ oss_output_init(const config_param ¶m, Error &error) } static void -oss_output_finish(struct audio_output *ao) +oss_output_finish(AudioOutput *ao) { OssOutput *od = (OssOutput *)ao; - ao_base_finish(&od->base); delete od; } #ifdef AFMT_S24_PACKED static bool -oss_output_enable(struct audio_output *ao, gcc_unused Error &error) +oss_output_enable(AudioOutput *ao, gcc_unused Error &error) { OssOutput *od = (OssOutput *)ao; @@ -238,7 +235,7 @@ oss_output_enable(struct audio_output *ao, gcc_unused Error &error) } static void -oss_output_disable(struct audio_output *ao) +oss_output_disable(AudioOutput *ao) { OssOutput *od = (OssOutput *)ao; @@ -680,7 +677,7 @@ oss_reopen(OssOutput *od, Error &error) } static bool -oss_output_open(struct audio_output *ao, AudioFormat &audio_format, +oss_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { OssOutput *od = (OssOutput *)ao; @@ -702,7 +699,7 @@ oss_output_open(struct audio_output *ao, AudioFormat &audio_format, } static void -oss_output_close(struct audio_output *ao) +oss_output_close(AudioOutput *ao) { OssOutput *od = (OssOutput *)ao; @@ -710,7 +707,7 @@ oss_output_close(struct audio_output *ao) } static void -oss_output_cancel(struct audio_output *ao) +oss_output_cancel(AudioOutput *ao) { OssOutput *od = (OssOutput *)ao; @@ -721,7 +718,7 @@ oss_output_cancel(struct audio_output *ao) } static size_t -oss_output_play(struct audio_output *ao, const void *chunk, size_t size, +oss_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { OssOutput *od = (OssOutput *)ao; @@ -734,7 +731,9 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size, return 0; #ifdef AFMT_S24_PACKED - chunk = od->pcm_export->Export(chunk, size, size); + const auto e = od->pcm_export->Export({chunk, size}); + chunk = e.data; + size = e.size; #endif assert(size > 0); @@ -755,7 +754,7 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size, } } -const struct audio_output_plugin oss_output_plugin = { +const struct AudioOutputPlugin oss_output_plugin = { "oss", oss_output_test_default_device, oss_output_init, diff --git a/src/output/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx index 6c5c9530b..f9970c8f0 100644 --- a/src/output/OssOutputPlugin.hxx +++ b/src/output/plugins/OssOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_OSS_OUTPUT_PLUGIN_HXX #define MPD_OSS_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin oss_output_plugin; +extern const struct AudioOutputPlugin oss_output_plugin; #endif diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx index 34d615284..7a1f32258 100644 --- a/src/output/PipeOutputPlugin.cxx +++ b/src/output/plugins/PipeOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,8 +19,8 @@ #include "config.h" #include "PipeOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "config/ConfigError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -29,18 +29,16 @@ #include <stdio.h> struct PipeOutput { - struct audio_output base; + AudioOutput base; std::string cmd; FILE *fh; - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &pipe_output_plugin, param, - error); - } + PipeOutput() + :base(pipe_output_plugin) {} - void Deinitialize() { - ao_base_finish(&base); + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); } bool Configure(const config_param ¶m, Error &error); @@ -61,7 +59,7 @@ PipeOutput::Configure(const config_param ¶m, Error &error) return true; } -static struct audio_output * +static AudioOutput * pipe_output_init(const config_param ¶m, Error &error) { PipeOutput *pd = new PipeOutput(); @@ -72,7 +70,6 @@ pipe_output_init(const config_param ¶m, Error &error) } if (!pd->Configure(param, error)) { - pd->Deinitialize(); delete pd; return nullptr; } @@ -81,16 +78,15 @@ pipe_output_init(const config_param ¶m, Error &error) } static void -pipe_output_finish(struct audio_output *ao) +pipe_output_finish(AudioOutput *ao) { PipeOutput *pd = (PipeOutput *)ao; - pd->Deinitialize(); delete pd; } static bool -pipe_output_open(struct audio_output *ao, +pipe_output_open(AudioOutput *ao, gcc_unused AudioFormat &audio_format, Error &error) { @@ -107,7 +103,7 @@ pipe_output_open(struct audio_output *ao, } static void -pipe_output_close(struct audio_output *ao) +pipe_output_close(AudioOutput *ao) { PipeOutput *pd = (PipeOutput *)ao; @@ -115,7 +111,7 @@ pipe_output_close(struct audio_output *ao) } static size_t -pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, +pipe_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { PipeOutput *pd = (PipeOutput *)ao; @@ -128,7 +124,7 @@ pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, return ret; } -const struct audio_output_plugin pipe_output_plugin = { +const struct AudioOutputPlugin pipe_output_plugin = { "pipe", nullptr, pipe_output_init, diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx index f0c29706b..bdaf2ecfd 100644 --- a/src/output/PipeOutputPlugin.hxx +++ b/src/output/plugins/PipeOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX #define MPD_PIPE_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin pipe_output_plugin; +extern const struct AudioOutputPlugin pipe_output_plugin; #endif diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx index 1eece448a..120bad090 100644 --- a/src/output/PulseOutputPlugin.cxx +++ b/src/output/plugins/PulseOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,15 +19,13 @@ #include "config.h" #include "PulseOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" -#include "mixer/PulseMixerPlugin.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" +#include "mixer/plugins/PulseMixerPlugin.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <pulse/thread-mainloop.h> #include <pulse/context.h> #include <pulse/stream.h> @@ -38,11 +36,12 @@ #include <assert.h> #include <stddef.h> +#include <stdlib.h> #define MPD_PULSE_NAME "Music Player Daemon" struct PulseOutput { - struct audio_output base; + AudioOutput base; const char *name; const char *server; @@ -55,6 +54,9 @@ struct PulseOutput { struct pa_stream *stream; size_t writable; + + PulseOutput() + :base(pulse_output_plugin) {} }; static constexpr Domain pulse_output_domain("pulse_output"); @@ -67,70 +69,66 @@ SetError(Error &error, pa_context *context, const char *msg) } void -pulse_output_lock(PulseOutput *po) +pulse_output_lock(PulseOutput &po) { - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(po.mainloop); } void -pulse_output_unlock(PulseOutput *po) +pulse_output_unlock(PulseOutput &po) { - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(po.mainloop); } void -pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm) +pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm) { - assert(po != nullptr); - assert(po->mixer == nullptr); - assert(pm != nullptr); + assert(po.mixer == nullptr); - po->mixer = pm; + po.mixer = ± - if (po->mainloop == nullptr) + if (po.mainloop == nullptr) return; - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(po.mainloop); - if (po->context != nullptr && - pa_context_get_state(po->context) == PA_CONTEXT_READY) { - pulse_mixer_on_connect(pm, po->context); + if (po.context != nullptr && + pa_context_get_state(po.context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(pm, po.context); - if (po->stream != nullptr && - pa_stream_get_state(po->stream) == PA_STREAM_READY) - pulse_mixer_on_change(pm, po->context, po->stream); + if (po.stream != nullptr && + pa_stream_get_state(po.stream) == PA_STREAM_READY) + pulse_mixer_on_change(pm, po.context, po.stream); } - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(po.mainloop); } void -pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm) +pulse_output_clear_mixer(PulseOutput &po, gcc_unused PulseMixer &pm) { - assert(po != nullptr); - assert(pm != nullptr); - assert(po->mixer == pm); + assert(po.mixer == &pm); - po->mixer = nullptr; + po.mixer = nullptr; } bool -pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume, +pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, Error &error) { pa_operation *o; - if (po->context == nullptr || po->stream == nullptr || - pa_stream_get_state(po->stream) != PA_STREAM_READY) { + if (po.context == nullptr || po.stream == nullptr || + pa_stream_get_state(po.stream) != PA_STREAM_READY) { error.Set(pulse_output_domain, "disconnected"); return false; } - o = pa_context_set_sink_input_volume(po->context, - pa_stream_get_index(po->stream), + o = pa_context_set_sink_input_volume(po.context, + pa_stream_get_index(po.stream), volume, nullptr, nullptr); if (o == nullptr) { - SetError(error, po->context, + SetError(error, po.context, "failed to set PulseAudio volume"); return false; } @@ -150,16 +148,13 @@ static bool pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, struct pa_operation *operation) { - pa_operation_state_t state; - assert(mainloop != nullptr); assert(operation != nullptr); - state = pa_operation_get_state(operation); - while (state == PA_OPERATION_RUNNING) { + pa_operation_state_t state; + while ((state = pa_operation_get_state(operation)) + == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(mainloop); - state = pa_operation_get_state(operation); - } pa_operation_unref(operation); @@ -187,7 +182,7 @@ pulse_output_context_state_cb(struct pa_context *context, void *userdata) switch (pa_context_get_state(context)) { case PA_CONTEXT_READY: if (po->mixer != nullptr) - pulse_mixer_on_connect(po->mixer, context); + pulse_mixer_on_connect(*po->mixer, context); pa_threaded_mainloop_signal(po->mainloop, 0); break; @@ -195,7 +190,7 @@ pulse_output_context_state_cb(struct pa_context *context, void *userdata) case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: if (po->mixer != nullptr) - pulse_mixer_on_disconnect(po->mixer); + pulse_mixer_on_disconnect(*po->mixer); /* the caller thread might be waiting for these states */ @@ -228,7 +223,7 @@ pulse_output_subscribe_cb(pa_context *context, idx == pa_stream_get_index(po->stream) && (type == PA_SUBSCRIPTION_EVENT_NEW || type == PA_SUBSCRIPTION_EVENT_CHANGE)) - pulse_mixer_on_change(po->mixer, context, po->stream); + pulse_mixer_on_change(*po->mixer, context, po->stream); } /** @@ -323,15 +318,16 @@ pulse_output_setup_context(PulseOutput *po, Error &error) return true; } -static struct audio_output * +static AudioOutput * pulse_output_init(const config_param ¶m, Error &error) { PulseOutput *po; - g_setenv("PULSE_PROP_media.role", "music", true); + setenv("PULSE_PROP_media.role", "music", true); + setenv("PULSE_PROP_application.icon_name", "mpd", true); po = new PulseOutput(); - if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) { + if (!po->base.Configure(param, error)) { delete po; return nullptr; } @@ -349,16 +345,15 @@ pulse_output_init(const config_param ¶m, Error &error) } static void -pulse_output_finish(struct audio_output *ao) +pulse_output_finish(AudioOutput *ao) { PulseOutput *po = (PulseOutput *)ao; - ao_base_finish(&po->base); delete po; } static bool -pulse_output_enable(struct audio_output *ao, Error &error) +pulse_output_enable(AudioOutput *ao, Error &error) { PulseOutput *po = (PulseOutput *)ao; @@ -402,7 +397,7 @@ pulse_output_enable(struct audio_output *ao, Error &error) } static void -pulse_output_disable(struct audio_output *ao) +pulse_output_disable(AudioOutput *ao) { PulseOutput *po = (PulseOutput *)ao; @@ -483,7 +478,7 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata) switch (pa_stream_get_state(stream)) { case PA_STREAM_READY: if (po->mixer != nullptr) - pulse_mixer_on_change(po->mixer, po->context, stream); + pulse_mixer_on_change(*po->mixer, po->context, stream); pa_threaded_mainloop_signal(po->mainloop, 0); break; @@ -491,7 +486,7 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata) case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: if (po->mixer != nullptr) - pulse_mixer_on_disconnect(po->mixer); + pulse_mixer_on_disconnect(*po->mixer); pa_threaded_mainloop_signal(po->mainloop, 0); break; @@ -546,7 +541,7 @@ pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, } static bool -pulse_output_open(struct audio_output *ao, AudioFormat &audio_format, +pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { PulseOutput *po = (PulseOutput *)ao; @@ -614,7 +609,7 @@ pulse_output_open(struct audio_output *ao, AudioFormat &audio_format, } static void -pulse_output_close(struct audio_output *ao) +pulse_output_close(AudioOutput *ao) { PulseOutput *po = (PulseOutput *)ao; pa_operation *o; @@ -700,7 +695,7 @@ pulse_output_stream_pause(PulseOutput *po, bool pause, } static unsigned -pulse_output_delay(struct audio_output *ao) +pulse_output_delay(AudioOutput *ao) { PulseOutput *po = (PulseOutput *)ao; unsigned result = 0; @@ -718,7 +713,7 @@ pulse_output_delay(struct audio_output *ao) } static size_t -pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, +pulse_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { PulseOutput *po = (PulseOutput *)ao; @@ -783,7 +778,7 @@ pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, } static void -pulse_output_cancel(struct audio_output *ao) +pulse_output_cancel(AudioOutput *ao) { PulseOutput *po = (PulseOutput *)ao; pa_operation *o; @@ -816,7 +811,7 @@ pulse_output_cancel(struct audio_output *ao) } static bool -pulse_output_pause(struct audio_output *ao) +pulse_output_pause(AudioOutput *ao) { PulseOutput *po = (PulseOutput *)ao; @@ -867,7 +862,7 @@ pulse_output_test_default_device(void) return success; } -const struct audio_output_plugin pulse_output_plugin = { +const struct AudioOutputPlugin pulse_output_plugin = { "pulse", pulse_output_test_default_device, pulse_output_init, diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx index 0ed8404bc..9219780a5 100644 --- a/src/output/PulseOutputPlugin.hxx +++ b/src/output/plugins/PulseOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -21,26 +21,26 @@ #define MPD_PULSE_OUTPUT_PLUGIN_HXX struct PulseOutput; -struct PulseMixer; +class PulseMixer; struct pa_cvolume; class Error; -extern const struct audio_output_plugin pulse_output_plugin; +extern const struct AudioOutputPlugin pulse_output_plugin; void -pulse_output_lock(PulseOutput *po); +pulse_output_lock(PulseOutput &po); void -pulse_output_unlock(PulseOutput *po); +pulse_output_unlock(PulseOutput &po); void -pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm); +pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm); void -pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm); +pulse_output_clear_mixer(PulseOutput &po, PulseMixer &pm); bool -pulse_output_set_volume(PulseOutput *po, - const struct pa_cvolume *volume, Error &error); +pulse_output_set_volume(PulseOutput &po, + const pa_cvolume *volume, Error &error); #endif diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx index 9a7eba01f..87e23f55a 100644 --- a/src/output/RecorderOutputPlugin.cxx +++ b/src/output/plugins/RecorderOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,10 +19,10 @@ #include "config.h" #include "RecorderOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "EncoderPlugin.hxx" -#include "EncoderList.hxx" -#include "ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "config/ConfigError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "system/fd_util.h" @@ -35,7 +35,7 @@ #include <errno.h> struct RecorderOutput { - struct audio_output base; + AudioOutput base; /** * The configured encoder plugin. @@ -57,13 +57,11 @@ struct RecorderOutput { */ char buffer[32768]; - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &recorder_output_plugin, param, - error_r); - } + RecorderOutput() + :base(recorder_output_plugin) {} - void Deinitialize() { - ao_base_finish(&base); + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); } bool Configure(const config_param ¶m, Error &error); @@ -107,7 +105,7 @@ RecorderOutput::Configure(const config_param ¶m, Error &error) return true; } -static audio_output * +static AudioOutput * recorder_output_init(const config_param ¶m, Error &error) { RecorderOutput *recorder = new RecorderOutput(); @@ -118,7 +116,6 @@ recorder_output_init(const config_param ¶m, Error &error) } if (!recorder->Configure(param, error)) { - recorder->Deinitialize(); delete recorder; return nullptr; } @@ -127,12 +124,11 @@ recorder_output_init(const config_param ¶m, Error &error) } static void -recorder_output_finish(struct audio_output *ao) +recorder_output_finish(AudioOutput *ao) { RecorderOutput *recorder = (RecorderOutput *)ao; encoder_finish(recorder->encoder); - recorder->Deinitialize(); delete recorder; } @@ -181,7 +177,7 @@ RecorderOutput::EncoderToFile(Error &error) } static bool -recorder_output_open(struct audio_output *ao, +recorder_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { @@ -216,7 +212,7 @@ recorder_output_open(struct audio_output *ao, } static void -recorder_output_close(struct audio_output *ao) +recorder_output_close(AudioOutput *ao) { RecorderOutput *recorder = (RecorderOutput *)ao; @@ -233,7 +229,7 @@ recorder_output_close(struct audio_output *ao) } static size_t -recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, +recorder_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { RecorderOutput *recorder = (RecorderOutput *)ao; @@ -243,7 +239,7 @@ recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, ? size : 0; } -const struct audio_output_plugin recorder_output_plugin = { +const struct AudioOutputPlugin recorder_output_plugin = { "recorder", nullptr, recorder_output_init, diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx index a27f51e23..ea1044e0f 100644 --- a/src/output/RecorderOutputPlugin.hxx +++ b/src/output/plugins/RecorderOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX #define MPD_RECORDER_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin recorder_output_plugin; +extern const struct AudioOutputPlugin recorder_output_plugin; #endif diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx index 20d69f3f9..aa37c91b7 100644 --- a/src/output/RoarOutputPlugin.cxx +++ b/src/output/plugins/RoarOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen * @@ -20,8 +20,8 @@ #include "config.h" #include "RoarOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" #include "thread/Mutex.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -36,7 +36,7 @@ #undef new class RoarOutput { - struct audio_output base; + AudioOutput base; std::string host, name; @@ -50,19 +50,15 @@ class RoarOutput { public: RoarOutput() - :err(ROAR_ERROR_NONE) {} + :base(roar_output_plugin), + err(ROAR_ERROR_NONE) {} - operator audio_output *() { + operator AudioOutput *() { return &base; } bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &roar_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); + return base.Configure(param, error); } void Configure(const config_param ¶m); @@ -97,9 +93,9 @@ RoarOutput::GetVolume() const } int -roar_output_get_volume(RoarOutput *roar) +roar_output_get_volume(RoarOutput &roar) { - return roar->GetVolume(); + return roar.GetVolume(); } bool @@ -119,9 +115,9 @@ RoarOutput::SetVolume(unsigned volume) } bool -roar_output_set_volume(RoarOutput *roar, unsigned volume) +roar_output_set_volume(RoarOutput &roar, unsigned volume) { - return roar->SetVolume(volume); + return roar.SetVolume(volume); } inline void @@ -136,7 +132,7 @@ RoarOutput::Configure(const config_param ¶m) : ROAR_ROLE_MUSIC; } -static struct audio_output * +static AudioOutput * roar_init(const config_param ¶m, Error &error) { RoarOutput *self = new RoarOutput(); @@ -151,11 +147,10 @@ roar_init(const config_param ¶m, Error &error) } static void -roar_finish(struct audio_output *ao) +roar_finish(AudioOutput *ao) { RoarOutput *self = (RoarOutput *)ao; - self->Deinitialize(); delete self; } @@ -227,7 +222,7 @@ RoarOutput::Open(AudioFormat &audio_format, Error &error) } static bool -roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) +roar_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { RoarOutput *self = (RoarOutput *)ao; @@ -248,7 +243,7 @@ RoarOutput::Close() } static void -roar_close(struct audio_output *ao) +roar_close(AudioOutput *ao) { RoarOutput *self = (RoarOutput *)ao; self->Close(); @@ -283,7 +278,7 @@ RoarOutput::Cancel() } static void -roar_cancel(struct audio_output *ao) +roar_cancel(AudioOutput *ao) { RoarOutput *self = (RoarOutput *)ao; @@ -308,7 +303,7 @@ RoarOutput::Play(const void *chunk, size_t size, Error &error) } static size_t -roar_play(struct audio_output *ao, const void *chunk, size_t size, +roar_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { RoarOutput *self = (RoarOutput *)ao; @@ -354,6 +349,9 @@ roar_tag_convert(TagType type, bool *is_uuid) case TAG_MUSICBRAINZ_TRACKID: *is_uuid = true; return "HASH"; + case TAG_MUSICBRAINZ_RELEASETRACKID: + *is_uuid = true; + return "HASH"; default: return nullptr; @@ -368,31 +366,37 @@ RoarOutput::SendTag(const Tag &tag) const ScopeLock protect(mutex); - size_t cnt = 1; + size_t cnt = 0; struct roar_keyval vals[32]; char uuid_buf[32][64]; char timebuf[16]; - snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", - tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60); + if (!tag.duration.IsNegative()) { + const unsigned seconds = tag.duration.ToS(); + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + seconds / 3600, (seconds % 3600) / 60, seconds % 60); + + vals[cnt].key = const_cast<char *>("LENGTH"); + vals[cnt].value = timebuf; + ++cnt; + } - vals[0].key = const_cast<char *>("LENGTH"); - vals[0].value = timebuf; + for (const auto &item : tag) { + if (cnt >= 32) + break; - for (unsigned i = 0; i < tag.num_items && cnt < 32; i++) - { bool is_uuid = false; - const char *key = roar_tag_convert(tag.items[i]->type, + const char *key = roar_tag_convert(item.type, &is_uuid); if (key != nullptr) { vals[cnt].key = const_cast<char *>(key); if (is_uuid) { snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", - tag.items[i]->value); + item.value); vals[cnt].value = uuid_buf[cnt]; } else { - vals[cnt].value = tag.items[i]->value; + vals[cnt].value = const_cast<char *>(item.value); } cnt++; @@ -403,13 +407,13 @@ RoarOutput::SendTag(const Tag &tag) } static void -roar_send_tag(struct audio_output *ao, const Tag *meta) +roar_send_tag(AudioOutput *ao, const Tag *meta) { RoarOutput *self = (RoarOutput *)ao; self->SendTag(*meta); } -const struct audio_output_plugin roar_output_plugin = { +const struct AudioOutputPlugin roar_output_plugin = { "roar", nullptr, roar_init, diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx index 04949e421..5f5a9246e 100644 --- a/src/output/RoarOutputPlugin.hxx +++ b/src/output/plugins/RoarOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -22,12 +22,12 @@ class RoarOutput; -extern const struct audio_output_plugin roar_output_plugin; +extern const struct AudioOutputPlugin roar_output_plugin; int -roar_output_get_volume(RoarOutput *roar); +roar_output_get_volume(RoarOutput &roar); bool -roar_output_set_volume(RoarOutput *roar, unsigned volume); +roar_output_set_volume(RoarOutput &roar, unsigned volume); #endif diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx index 19f2b61cd..0341e1cf7 100644 --- a/src/output/ShoutOutputPlugin.cxx +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,17 +19,16 @@ #include "config.h" #include "ShoutOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "EncoderPlugin.hxx" -#include "EncoderList.hxx" -#include "ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "config/ConfigError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "system/FatalError.hxx" #include "Log.hxx" #include <shout/shout.h> -#include <glib.h> #include <assert.h> #include <stdlib.h> @@ -39,7 +38,7 @@ static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2; struct ShoutOutput final { - struct audio_output base; + AudioOutput base; shout_t *shout_conn; shout_metadata_t *shout_meta; @@ -54,7 +53,8 @@ struct ShoutOutput final { uint8_t buffer[32768]; ShoutOutput() - :shout_conn(shout_new()), + :base(shout_output_plugin), + shout_conn(shout_new()), shout_meta(shout_metadata_new()), quality(-2.0), bitrate(-1), @@ -68,12 +68,7 @@ struct ShoutOutput final { } bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &shout_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); + return base.Configure(param, error); } bool Configure(const config_param ¶m, Error &error); @@ -275,7 +270,7 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) return true; } -static struct audio_output * +static AudioOutput * my_shout_init_driver(const config_param ¶m, Error &error) { ShoutOutput *sd = new ShoutOutput(); @@ -285,7 +280,6 @@ my_shout_init_driver(const config_param ¶m, Error &error) } if (!sd->Configure(param, error)) { - sd->Deinitialize(); delete sd; return nullptr; } @@ -363,13 +357,12 @@ static void close_shout_conn(ShoutOutput * sd) } static void -my_shout_finish_driver(struct audio_output *ao) +my_shout_finish_driver(AudioOutput *ao) { ShoutOutput *sd = (ShoutOutput *)ao; encoder_finish(sd->encoder); - sd->Deinitialize(); delete sd; shout_init_count--; @@ -379,7 +372,7 @@ my_shout_finish_driver(struct audio_output *ao) } static void -my_shout_drop_buffered_audio(struct audio_output *ao) +my_shout_drop_buffered_audio(AudioOutput *ao) { gcc_unused ShoutOutput *sd = (ShoutOutput *)ao; @@ -388,7 +381,7 @@ my_shout_drop_buffered_audio(struct audio_output *ao) } static void -my_shout_close_device(struct audio_output *ao) +my_shout_close_device(AudioOutput *ao) { ShoutOutput *sd = (ShoutOutput *)ao; @@ -414,7 +407,7 @@ shout_connect(ShoutOutput *sd, Error &error) } static bool -my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format, +my_shout_open_device(AudioOutput *ao, AudioFormat &audio_format, Error &error) { ShoutOutput *sd = (ShoutOutput *)ao; @@ -437,7 +430,7 @@ my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format, } static unsigned -my_shout_delay(struct audio_output *ao) +my_shout_delay(AudioOutput *ao) { ShoutOutput *sd = (ShoutOutput *)ao; @@ -449,7 +442,7 @@ my_shout_delay(struct audio_output *ao) } static size_t -my_shout_play(struct audio_output *ao, const void *chunk, size_t size, +my_shout_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { ShoutOutput *sd = (ShoutOutput *)ao; @@ -461,7 +454,7 @@ my_shout_play(struct audio_output *ao, const void *chunk, size_t size, } static bool -my_shout_pause(struct audio_output *ao) +my_shout_pause(AudioOutput *ao) { static char silence[1020]; @@ -477,13 +470,13 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) artist[0] = 0; title[0] = 0; - for (unsigned i = 0; i < tag->num_items; i++) { - switch (tag->items[i]->type) { + for (const auto &item : *tag) { + switch (item.type) { case TAG_ARTIST: - strncpy(artist, tag->items[i]->value, size); + strncpy(artist, item.value, size); break; case TAG_TITLE: - strncpy(title, tag->items[i]->value, size); + strncpy(title, item.value, size); break; default: @@ -494,7 +487,7 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) snprintf(dest, size, "%s - %s", artist, title); } -static void my_shout_set_tag(struct audio_output *ao, +static void my_shout_set_tag(AudioOutput *ao, const Tag *tag) { ShoutOutput *sd = (ShoutOutput *)ao; @@ -525,7 +518,7 @@ static void my_shout_set_tag(struct audio_output *ao, write_page(sd, IgnoreError()); } -const struct audio_output_plugin shout_output_plugin = { +const struct AudioOutputPlugin shout_output_plugin = { "shout", nullptr, my_shout_init_driver, diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx index 496b77975..9f706fc3b 100644 --- a/src/output/ShoutOutputPlugin.hxx +++ b/src/output/plugins/ShoutOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX #define MPD_SHOUT_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin shout_output_plugin; +extern const struct AudioOutputPlugin shout_output_plugin; #endif diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx index 0836dc2e2..30745f97c 100644 --- a/src/output/SolarisOutputPlugin.cxx +++ b/src/output/plugins/SolarisOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -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" @@ -50,20 +50,18 @@ struct audio_info { #endif struct SolarisOutput { - struct audio_output base; + AudioOutput base; /* configuration */ const char *device; int fd; - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &solaris_output_plugin, param, - error_r); - } + SolarisOutput() + :base(solaris_output_plugin) {} - void Deinitialize() { - ao_base_finish(&base); + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); } }; @@ -76,7 +74,7 @@ solaris_output_test_default_device(void) access("/dev/audio", W_OK) == 0; } -static struct audio_output * +static AudioOutput * solaris_output_init(const config_param ¶m, Error &error_r) { SolarisOutput *so = new SolarisOutput(); @@ -91,16 +89,15 @@ solaris_output_init(const config_param ¶m, Error &error_r) } static void -solaris_output_finish(struct audio_output *ao) +solaris_output_finish(AudioOutput *ao) { SolarisOutput *so = (SolarisOutput *)ao; - so->Deinitialize(); delete so; } static bool -solaris_output_open(struct audio_output *ao, AudioFormat &audio_format, +solaris_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { SolarisOutput *so = (SolarisOutput *)ao; @@ -151,7 +148,7 @@ solaris_output_open(struct audio_output *ao, AudioFormat &audio_format, } static void -solaris_output_close(struct audio_output *ao) +solaris_output_close(AudioOutput *ao) { SolarisOutput *so = (SolarisOutput *)ao; @@ -159,7 +156,7 @@ solaris_output_close(struct audio_output *ao) } static size_t -solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, +solaris_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { SolarisOutput *so = (SolarisOutput *)ao; @@ -175,14 +172,14 @@ solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, } static void -solaris_output_cancel(struct audio_output *ao) +solaris_output_cancel(AudioOutput *ao) { SolarisOutput *so = (SolarisOutput *)ao; ioctl(so->fd, I_FLUSH); } -const struct audio_output_plugin solaris_output_plugin = { +const struct AudioOutputPlugin solaris_output_plugin = { "solaris", solaris_output_test_default_device, solaris_output_init, diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx index d0fbd32c8..3f9ede7a6 100644 --- a/src/output/SolarisOutputPlugin.hxx +++ b/src/output/plugins/SolarisOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX #define MPD_SOLARIS_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin solaris_output_plugin; +extern const struct AudioOutputPlugin solaris_output_plugin; #endif diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx index d2508ee2a..e5c5a6f0c 100644 --- a/src/output/WinmmOutputPlugin.cxx +++ b/src/output/plugins/WinmmOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,15 +19,13 @@ #include "config.h" #include "WinmmOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "pcm/PcmBuffer.hxx" -#include "MixerList.hxx" +#include "mixer/MixerList.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "util/Macros.hxx" -#include <glib.h> - #include <stdlib.h> #include <string.h> @@ -38,7 +36,7 @@ struct WinmmBuffer { }; struct WinmmOutput { - struct audio_output base; + AudioOutput base; UINT device_id; HWAVEOUT handle; @@ -51,14 +49,17 @@ struct WinmmOutput { WinmmBuffer buffers[8]; unsigned next_buffer; + + WinmmOutput() + :base(winmm_output_plugin) {} }; static constexpr Domain winmm_output_domain("winmm_output"); HWAVEOUT -winmm_output_get_handle(WinmmOutput *output) +winmm_output_get_handle(WinmmOutput &output) { - return output->handle; + return output.handle; } static bool @@ -108,18 +109,17 @@ fail: return false; } -static struct audio_output * +static AudioOutput * winmm_output_init(const config_param ¶m, Error &error) { WinmmOutput *wo = new WinmmOutput(); - if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) { + if (!wo->base.Configure(param, error)) { delete wo; return nullptr; } const char *device = param.GetBlockValue("device"); if (!get_device_id(device, &wo->device_id, error)) { - ao_base_finish(&wo->base); delete wo; return nullptr; } @@ -128,16 +128,15 @@ winmm_output_init(const config_param ¶m, Error &error) } static void -winmm_output_finish(struct audio_output *ao) +winmm_output_finish(AudioOutput *ao) { WinmmOutput *wo = (WinmmOutput *)ao; - ao_base_finish(&wo->base); delete wo; } static bool -winmm_output_open(struct audio_output *ao, AudioFormat &audio_format, +winmm_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { WinmmOutput *wo = (WinmmOutput *)ao; @@ -194,7 +193,7 @@ winmm_output_open(struct audio_output *ao, AudioFormat &audio_format, } static void -winmm_output_close(struct audio_output *ao) +winmm_output_close(AudioOutput *ao) { WinmmOutput *wo = (WinmmOutput *)ao; @@ -263,7 +262,7 @@ winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, } static size_t -winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error) +winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { WinmmOutput *wo = (WinmmOutput *)ao; @@ -318,7 +317,7 @@ winmm_stop(WinmmOutput *wo) } static void -winmm_output_drain(struct audio_output *ao) +winmm_output_drain(AudioOutput *ao) { WinmmOutput *wo = (WinmmOutput *)ao; @@ -327,14 +326,14 @@ winmm_output_drain(struct audio_output *ao) } static void -winmm_output_cancel(struct audio_output *ao) +winmm_output_cancel(AudioOutput *ao) { WinmmOutput *wo = (WinmmOutput *)ao; winmm_stop(wo); } -const struct audio_output_plugin winmm_output_plugin = { +const struct AudioOutputPlugin winmm_output_plugin = { "winmm", winmm_output_test_default_device, winmm_output_init, diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx index a6b7733ec..50fae4f2f 100644 --- a/src/output/WinmmOutputPlugin.hxx +++ b/src/output/plugins/WinmmOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -31,11 +31,11 @@ struct WinmmOutput; -extern const struct audio_output_plugin winmm_output_plugin; +extern const struct AudioOutputPlugin winmm_output_plugin; gcc_pure HWAVEOUT -winmm_output_get_handle(WinmmOutput *); +winmm_output_get_handle(WinmmOutput &output); #endif diff --git a/src/output/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx index dc337053d..3797c3d26 100644 --- a/src/output/HttpdClient.cxx +++ b/src/output/plugins/httpd/HttpdClient.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -26,8 +26,6 @@ #include "system/SocketError.hxx" #include "Log.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> #include <stdio.h> @@ -38,24 +36,26 @@ HttpdClient::~HttpdClient() if (current_page != nullptr) current_page->Unref(); - for (auto page : pages) - page->Unref(); + ClearQueue(); } if (metadata) metadata->Unref(); + + if (IsDefined()) + BufferedSocket::Close(); } void HttpdClient::Close() { - httpd->RemoveClient(*this); + httpd.RemoveClient(*this); } void HttpdClient::LockClose() { - const ScopeLock protect(httpd->mutex); + const ScopeLock protect(httpd.mutex); Close(); } @@ -68,7 +68,7 @@ HttpdClient::BeginResponse() current_page = nullptr; if (!head_method) - httpd->SendHeader(*this); + httpd.SendHeader(*this); } /** @@ -141,7 +141,9 @@ HttpdClient::HandleLine(const char *line) bool HttpdClient::SendResponse() { - char buffer[1024]; + char buffer[1024], *allocated = nullptr; + const char *response; + assert(state == RESPONSE); if (dlna_streaming_requested) { @@ -156,19 +158,15 @@ HttpdClient::SendResponse() "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" "\r\n", - httpd->content_type); + httpd.content_type); + response = buffer; } else if (metadata_requested) { - char *metadata_header = - icy_server_metadata_header(httpd->name, httpd->genre, - httpd->website, - httpd->content_type, + response = allocated = + icy_server_metadata_header(httpd.name, httpd.genre, + httpd.website, + httpd.content_type, metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - delete[] metadata_header; - } else { /* revert to a normal HTTP request */ snprintf(buffer, sizeof(buffer), "HTTP/1.1 200 OK\r\n" @@ -177,10 +175,12 @@ HttpdClient::SendResponse() "Pragma: no-cache\r\n" "Cache-Control: no-cache, no-store\r\n" "\r\n", - httpd->content_type); + httpd.content_type); + response = buffer; } - ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer)); + ssize_t nbytes = SocketMonitor::Write(response, strlen(response)); + delete[] allocated; if (gcc_unlikely(nbytes < 0)) { const SocketErrorMessage msg; FormatWarning(httpd_output_domain, @@ -193,11 +193,12 @@ HttpdClient::SendResponse() return true; } -HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, +HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop, bool _metadata_supported) :BufferedSocket(_fd, _loop), httpd(_httpd), state(REQUEST), + queue_size(0), head_method(false), dlna_streaming_requested(false), metadata_supported(_metadata_supported), @@ -208,16 +209,24 @@ HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, { } -size_t -HttpdClient::GetQueueSize() const +void +HttpdClient::ClearQueue() { - if (state != RESPONSE) - return 0; + assert(state == RESPONSE); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); - size_t size = 0; - for (auto page : pages) - size += page->size; - return size; +#ifndef NDEBUG + assert(queue_size >= page->size); + queue_size -= page->size; +#endif + + page->Unref(); + } + + assert(queue_size == 0); } void @@ -226,9 +235,7 @@ HttpdClient::CancelQueue() if (state != RESPONSE) return; - for (auto page : pages) - page->Unref(); - pages.clear(); + ClearQueue(); if (current_page == nullptr) CancelWrite(); @@ -263,7 +270,7 @@ HttpdClient::GetBytesTillMetaData() const inline bool HttpdClient::TryWrite() { - const ScopeLock protect(httpd->mutex); + const ScopeLock protect(httpd.mutex); assert(state == RESPONSE); @@ -271,14 +278,17 @@ HttpdClient::TryWrite() if (pages.empty()) { /* another thread has removed the event source while this thread was waiting for - httpd->mutex */ + httpd.mutex */ CancelWrite(); return true; } current_page = pages.front(); - pages.pop_front(); + pages.pop(); current_position = 0; + + assert(queue_size >= current_page->size); + queue_size -= current_page->size; } const ssize_t bytes_to_write = GetBytesTillMetaData(); @@ -310,7 +320,7 @@ HttpdClient::TryWrite() metadata_sent = true; } } else { - guchar empty_data = 0; + char empty_data = 0; ssize_t nbytes = Write(&empty_data, 1); if (nbytes < 0) { @@ -379,8 +389,15 @@ HttpdClient::PushPage(Page *page) /* the client is still writing the HTTP request */ return; + if (queue_size > 256 * 1024) { + FormatDebug(httpd_output_domain, + "client is too slow, flushing its queue"); + ClearQueue(); + } + page->Ref(); - pages.push_back(page); + pages.push(page); + queue_size += page->size; ScheduleWrite(); } @@ -388,13 +405,13 @@ HttpdClient::PushPage(Page *page) void HttpdClient::PushMetaData(Page *page) { + assert(page != nullptr); + if (metadata) { metadata->Unref(); metadata = nullptr; } - g_return_if_fail (page); - page->Ref(); metadata = page; metadata_sent = false; diff --git a/src/output/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx index 66a819232..f94f05769 100644 --- a/src/output/HttpdClient.hxx +++ b/src/output/plugins/httpd/HttpdClient.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -23,18 +23,19 @@ #include "event/BufferedSocket.hxx" #include "Compiler.h" +#include <queue> #include <list> #include <stddef.h> -struct HttpdOutput; +class HttpdOutput; class Page; -class HttpdClient final : public BufferedSocket { +class HttpdClient final : BufferedSocket { /** * The httpd output object this client is connected to. */ - HttpdOutput *const httpd; + HttpdOutput &httpd; /** * The current state of the client. @@ -53,7 +54,12 @@ class HttpdClient final : public BufferedSocket { /** * A queue of #Page objects to be sent to the client. */ - std::list<Page *> pages; + std::queue<Page *, std::list<Page *>> pages; + + /** + * The sum of all page sizes in #pages. + */ + size_t queue_size; /** * The #page which is currently being sent to the client. @@ -120,7 +126,7 @@ public: * @param httpd the HTTP output device * @param fd the socket file descriptor */ - HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop, + HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop, bool _metadata_supported); /** @@ -137,12 +143,6 @@ public: void LockClose(); /** - * Returns the total size of this client's page queue. - */ - gcc_pure - size_t GetQueueSize() const; - - /** * Clears the page queue. */ void CancelQueue(); @@ -180,6 +180,9 @@ public: */ void PushMetaData(Page *page); +private: + void ClearQueue(); + protected: virtual bool OnSocketReady(unsigned flags) override; virtual InputResult OnSocketInput(void *data, size_t length) override; diff --git a/src/output/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx index b76493a44..20ff15e42 100644 --- a/src/output/HttpdInternal.hxx +++ b/src/output/plugins/httpd/HttpdInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,10 +25,13 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H -#include "OutputInternal.hxx" -#include "Timer.hxx" +#include "output/Internal.hxx" +#include "output/Timer.hxx" #include "thread/Mutex.hxx" #include "event/ServerSocket.hxx" +#include "event/DeferredMonitor.hxx" +#include "util/Cast.hxx" +#include "Compiler.h" #ifdef _LIBCPP_VERSION /* can't use incomplete template arguments with libc++ */ @@ -36,6 +39,8 @@ #endif #include <forward_list> +#include <queue> +#include <list> struct config_param; class Error; @@ -46,8 +51,8 @@ class Page; struct Encoder; struct Tag; -struct HttpdOutput final : private ServerSocket { - struct audio_output base; +class HttpdOutput final : ServerSocket, DeferredMonitor { + AudioOutput base; /** * True if the audio output is open and accepts client @@ -68,6 +73,7 @@ struct HttpdOutput final : private ServerSocket { */ size_t unflushed_input; +public: /** * The MIME type produced by the #encoder. */ @@ -80,6 +86,13 @@ struct HttpdOutput final : private ServerSocket { mutable Mutex mutex; /** + * This condition gets signalled when an item is removed from + * #pages. + */ + Cond cond; + +private: + /** * A #Timer object to synchronize this output with the * wallclock. */ @@ -96,6 +109,15 @@ struct HttpdOutput final : private ServerSocket { Page *metadata; /** + * The page queue, i.e. pages from the encoder to be + * broadcasted to all clients. This container is necessary to + * pass pages from the OutputThread to the IOThread. It is + * protected by #mutex, and removing signals #cond. + */ + std::queue<Page *, std::list<Page *>> pages; + + public: + /** * The configured name. */ char const *name; @@ -108,6 +130,7 @@ struct HttpdOutput final : private ServerSocket { */ char const *website; +private: /** * A linked list containing all clients which are currently * connected. @@ -126,11 +149,34 @@ struct HttpdOutput final : private ServerSocket { */ unsigned clients_max, clients_cnt; +public: HttpdOutput(EventLoop &_loop); ~HttpdOutput(); +#if defined(__clang__) || GCC_CHECK_VERSION(4,7) + constexpr +#endif + static HttpdOutput *Cast(AudioOutput *ao) { + return &ContainerCast(*ao, &HttpdOutput::base); + } + + using DeferredMonitor::GetEventLoop; + + bool Init(const config_param ¶m, Error &error); + bool Configure(const config_param ¶m, Error &error); + AudioOutput *InitAndConfigure(const config_param ¶m, + Error &error) { + if (!Init(param, error)) + return nullptr; + + if (!Configure(param, error)) + return nullptr; + + return &base; + } + bool Bind(Error &error); void Unbind(); @@ -181,6 +227,9 @@ struct HttpdOutput final : private ServerSocket { */ void SendHeader(HttpdClient &client) const; + gcc_pure + unsigned Delay() const; + /** * Reads data from the encoder (as much as available) and * returns it as a new #page object. @@ -203,7 +252,13 @@ struct HttpdOutput final : private ServerSocket { void SendTag(const Tag *tag); + size_t Play(const void *chunk, size_t size, Error &error); + + void CancelAllClients(); + private: + virtual void RunDeferred() override; + virtual void OnAccept(int fd, const sockaddr &address, size_t address_length, int uid) override; }; diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx index 369c06937..e3ba7727d 100644 --- a/src/output/HttpdOutputPlugin.cxx +++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -21,20 +21,19 @@ #include "HttpdOutputPlugin.hxx" #include "HttpdInternal.hxx" #include "HttpdClient.hxx" -#include "OutputAPI.hxx" -#include "EncoderPlugin.hxx" -#include "EncoderList.hxx" +#include "output/OutputAPI.hxx" +#include "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" #include "system/Resolver.hxx" #include "Page.hxx" #include "IcyMetaDataServer.hxx" #include "system/fd_util.h" -#include "Main.hxx" +#include "IOThread.hxx" +#include "event/Call.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <assert.h> #include <sys/types.h> @@ -51,7 +50,8 @@ const Domain httpd_output_domain("httpd_output"); inline HttpdOutput::HttpdOutput(EventLoop &_loop) - :ServerSocket(_loop), + :ServerSocket(_loop), DeferredMonitor(_loop), + base(httpd_output_plugin), encoder(nullptr), unflushed_input(0), metadata(nullptr) { @@ -72,8 +72,11 @@ HttpdOutput::Bind(Error &error) { open = false; - const ScopeLock protect(mutex); - return ServerSocket::Open(error); + bool result = false; + BlockingCall(GetEventLoop(), [this, &error, &result](){ + result = ServerSocket::Open(error); + }); + return result; } inline void @@ -81,8 +84,9 @@ HttpdOutput::Unbind() { assert(!open); - const ScopeLock protect(mutex); - ServerSocket::Close(); + BlockingCall(GetEventLoop(), [this](){ + ServerSocket::Close(); + }); } inline bool @@ -130,47 +134,29 @@ HttpdOutput::Configure(const config_param ¶m, Error &error) return true; } -static struct audio_output * -httpd_output_init(const config_param ¶m, Error &error) +inline bool +HttpdOutput::Init(const config_param ¶m, Error &error) { - HttpdOutput *httpd = new HttpdOutput(*main_loop); + return base.Configure(param, error); +} - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, - error)) { - delete httpd; - return nullptr; - } +static AudioOutput * +httpd_output_init(const config_param ¶m, Error &error) +{ + HttpdOutput *httpd = new HttpdOutput(io_thread_get()); - if (!httpd->Configure(param, error)) { - ao_base_finish(&httpd->base); + AudioOutput *result = httpd->InitAndConfigure(param, error); + if (result == nullptr) delete httpd; - return nullptr; - } - - return &httpd->base; -} -#if GCC_CHECK_VERSION(4,6) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#endif - -static inline constexpr HttpdOutput * -Cast(audio_output *ao) -{ - return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base)); + return result; } -#if GCC_CHECK_VERSION(4,6) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - static void -httpd_output_finish(struct audio_output *ao) +httpd_output_finish(AudioOutput *ao) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); - ao_base_finish(&httpd->base); delete httpd; } @@ -181,7 +167,7 @@ httpd_output_finish(struct audio_output *ao) inline void HttpdOutput::AddClient(int fd) { - clients.emplace_front(this, fd, GetEventLoop(), + clients.emplace_front(*this, fd, GetEventLoop(), encoder->plugin.tag == nullptr); ++clients_cnt; @@ -191,6 +177,29 @@ HttpdOutput::AddClient(int fd) } void +HttpdOutput::RunDeferred() +{ + /* this method runs in the IOThread; it broadcasts pages from + our own queue to all clients */ + + const ScopeLock protect(mutex); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + + for (auto &client : clients) + client.PushPage(page); + + page->Unref(); + } + + /* wake up the client that may be waiting for the queue to be + flushed */ + cond.broadcast(); +} + +void HttpdOutput::OnAccept(int fd, const sockaddr &address, size_t address_length, gcc_unused int uid) { @@ -199,9 +208,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address, #ifdef HAVE_LIBWRAP if (address.sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(&address, address_length, - IgnoreError()); - const char *progname = g_get_prgname(); + const auto hostaddr = sockaddr_to_string(&address, + address_length); + // TODO: shall we obtain the program name from argv[0]? + const char *progname = "mpd"; struct request_info req; request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); @@ -212,13 +222,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address, /* tcp wrappers says no */ FormatWarning(httpd_output_domain, "libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - g_free(hostaddr); + progname, hostaddr.c_str()); close_socket(fd); return; } - - g_free(hostaddr); } #else (void)address; @@ -269,17 +276,17 @@ HttpdOutput::ReadPage() } static bool -httpd_output_enable(struct audio_output *ao, Error &error) +httpd_output_enable(AudioOutput *ao, Error &error) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); return httpd->Bind(error); } static void -httpd_output_disable(struct audio_output *ao) +httpd_output_disable(AudioOutput *ao) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); httpd->Unbind(); } @@ -322,12 +329,10 @@ HttpdOutput::Open(AudioFormat &audio_format, Error &error) } static bool -httpd_output_open(struct audio_output *ao, AudioFormat &audio_format, +httpd_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { - HttpdOutput *httpd = Cast(ao); - - assert(httpd->clients.empty()); + HttpdOutput *httpd = HttpdOutput::Cast(ao); const ScopeLock protect(httpd->mutex); return httpd->Open(audio_format, error); @@ -342,7 +347,9 @@ HttpdOutput::Close() delete timer; - clients.clear(); + BlockingCall(GetEventLoop(), [this](){ + clients.clear(); + }); if (header != nullptr) header->Unref(); @@ -351,9 +358,9 @@ HttpdOutput::Close() } static void -httpd_output_close(struct audio_output *ao) +httpd_output_close(AudioOutput *ao) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); const ScopeLock protect(httpd->mutex); httpd->Close(); @@ -382,17 +389,15 @@ HttpdOutput::SendHeader(HttpdClient &client) const client.PushPage(header); } -static unsigned -httpd_output_delay(struct audio_output *ao) +inline unsigned +HttpdOutput::Delay() const { - HttpdOutput *httpd = Cast(ao); - - if (!httpd->LockHasClients() && httpd->base.pause) { + if (!LockHasClients() && base.pause) { /* if there's no client and this output is paused, then httpd_output_pause() will not do anything, it will not fill the buffer and it will not update the timer; therefore, we reset the timer here */ - httpd->timer->Reset(); + timer->Reset(); /* some arbitrary delay that is long enough to avoid consuming too much CPU, and short enough to notice @@ -400,39 +405,47 @@ httpd_output_delay(struct audio_output *ao) return 1000; } - return httpd->timer->IsStarted() - ? httpd->timer->GetDelay() + return timer->IsStarted() + ? timer->GetDelay() : 0; } +static unsigned +httpd_output_delay(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Delay(); +} + void HttpdOutput::BroadcastPage(Page *page) { assert(page != nullptr); - const ScopeLock protect(mutex); - for (auto &client : clients) - client.PushPage(page); + mutex.lock(); + pages.push(page); + page->Ref(); + mutex.unlock(); + + DeferredMonitor::Schedule(); } void HttpdOutput::BroadcastFromEncoder() { + /* synchronize with the IOThread */ mutex.lock(); - for (auto &client : clients) { - if (client.GetQueueSize() > 256 * 1024) { - FormatDebug(httpd_output_domain, - "client is too slow, flushing its queue"); - client.CancelQueue(); - } - } - mutex.unlock(); + while (!pages.empty()) + cond.wait(mutex); Page *page; - while ((page = ReadPage()) != nullptr) { - BroadcastPage(page); - page->Unref(); - } + while ((page = ReadPage()) != nullptr) + pages.push(page); + + mutex.unlock(); + + DeferredMonitor::Schedule(); } inline bool @@ -447,28 +460,34 @@ HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error) return true; } -static size_t -httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) +inline size_t +HttpdOutput::Play(const void *chunk, size_t size, Error &error) { - HttpdOutput *httpd = Cast(ao); - - if (httpd->LockHasClients()) { - if (!httpd->EncodeAndPlay(chunk, size, error)) + if (LockHasClients()) { + if (!EncodeAndPlay(chunk, size, error)) return 0; } - if (!httpd->timer->IsStarted()) - httpd->timer->Start(); - httpd->timer->Add(size); + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); return size; } +static size_t +httpd_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Play(chunk, size, error); +} + static bool -httpd_output_pause(struct audio_output *ao) +httpd_output_pause(AudioOutput *ao) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); if (httpd->LockHasClients()) { static const char silence[1020] = { 0 }; @@ -529,24 +548,41 @@ HttpdOutput::SendTag(const Tag *tag) } static void -httpd_output_tag(struct audio_output *ao, const Tag *tag) +httpd_output_tag(AudioOutput *ao, const Tag *tag) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); httpd->SendTag(tag); } -static void -httpd_output_cancel(struct audio_output *ao) +inline void +HttpdOutput::CancelAllClients() { - HttpdOutput *httpd = Cast(ao); + const ScopeLock protect(mutex); - const ScopeLock protect(httpd->mutex); - for (auto &client : httpd->clients) + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + page->Unref(); + } + + for (auto &client : clients) client.CancelQueue(); + + cond.broadcast(); +} + +static void +httpd_output_cancel(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + BlockingCall(io_thread_get(), [httpd](){ + httpd->CancelAllClients(); + }); } -const struct audio_output_plugin httpd_output_plugin = { +const struct AudioOutputPlugin httpd_output_plugin = { "httpd", nullptr, httpd_output_init, diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/plugins/httpd/HttpdOutputPlugin.hxx index c74d2bd4a..df99e2b43 100644 --- a/src/output/HttpdOutputPlugin.hxx +++ b/src/output/plugins/httpd/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,6 +20,6 @@ #ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX #define MPD_HTTPD_OUTPUT_PLUGIN_HXX -extern const struct audio_output_plugin httpd_output_plugin; +extern const struct AudioOutputPlugin httpd_output_plugin; #endif diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx new file mode 100644 index 000000000..146df23d1 --- /dev/null +++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx @@ -0,0 +1,134 @@ +/* + * 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 "IcyMetaDataServer.hxx" +#include "Page.hxx" +#include "tag/Tag.hxx" +#include "util/FormatString.hxx" + +#include <glib.h> + +#include <string.h> + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint) +{ + return FormatNew("ICY 200 OK\r\n" + "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ + "icy-notice2:MPD - The music player daemon<BR>\r\n" + "icy-name: %s\r\n" /* TODO */ + "icy-genre: %s\r\n" /* TODO */ + "icy-url: %s\r\n" /* TODO */ + "icy-pub:1\r\n" + "icy-metaint:%d\r\n" + /* TODO "icy-br:%d\r\n" */ + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + name, + genre, + url, + metaint, + /* bitrate, */ + content_type); +} + +static char * +icy_server_metadata_string(const char *stream_title, const char* stream_url) +{ + gchar *icy_metadata; + guint meta_length; + + // The leading n is a placeholder for the length information + icy_metadata = FormatNew("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); + + meta_length = strlen(icy_metadata); + + meta_length--; // subtract placeholder + + meta_length = ((int)meta_length / 16) + 1; + + icy_metadata[0] = meta_length; + + if (meta_length > 255) { + delete[] icy_metadata; + return nullptr; + } + + return icy_metadata; +} + +Page * +icy_server_metadata_page(const Tag &tag, const TagType *types) +{ + const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; + gint last_item, item; + guint position; + gchar *icy_string; + gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - + // "StreamTitle='';StreamUrl='';" + // = 4081 - 28 + stream_title[0] = '\0'; + + last_item = -1; + + while (*types != TAG_NUM_OF_ITEM_TYPES) { + const gchar *tag_item = tag.GetValue(*types++); + if (tag_item) + tag_items[++last_item] = tag_item; + } + + position = item = 0; + while (position < sizeof(stream_title) && item <= last_item) { + gint length = 0; + + length = g_strlcpy(stream_title + position, + tag_items[item++], + sizeof(stream_title) - position); + + position += length; + + if (item <= last_item) { + length = g_strlcpy(stream_title + position, + " - ", + sizeof(stream_title) - position); + + position += length; + } + } + + icy_string = icy_server_metadata_string(stream_title, ""); + + if (icy_string == nullptr) + return nullptr; + + Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1); + + delete[] icy_string; + + return icy_metadata; +} diff --git a/src/output/plugins/httpd/IcyMetaDataServer.hxx b/src/output/plugins/httpd/IcyMetaDataServer.hxx new file mode 100644 index 000000000..773b46641 --- /dev/null +++ b/src/output/plugins/httpd/IcyMetaDataServer.hxx @@ -0,0 +1,39 @@ +/* + * 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_ICY_META_DATA_SERVER_HXX +#define MPD_ICY_META_DATA_SERVER_HXX + +#include "tag/TagType.h" + +struct Tag; +class Page; + +/** + * Free the return value with delete[]. + */ +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint); + +Page * +icy_server_metadata_page(const Tag &tag, const TagType *types); + +#endif diff --git a/src/output/plugins/httpd/Page.cxx b/src/output/plugins/httpd/Page.cxx new file mode 100644 index 000000000..e22134bbc --- /dev/null +++ b/src/output/plugins/httpd/Page.cxx @@ -0,0 +1,70 @@ +/* + * 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 "Page.hxx" +#include "util/Alloc.hxx" + +#include <new> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +Page * +Page::Create(size_t size) +{ + void *p = xalloc(sizeof(Page) + size - + sizeof(Page::data)); + return ::new(p) Page(size); +} + +Page * +Page::Copy(const void *data, size_t size) +{ + assert(data != nullptr); + + Page *page = Create(size); + memcpy(page->data, data, size); + return page; +} + +Page * +Page::Concat(const Page &a, const Page &b) +{ + Page *page = Create(a.size + b.size); + + memcpy(page->data, a.data, a.size); + memcpy(page->data + a.size, b.data, b.size); + + return page; +} + +bool +Page::Unref() +{ + bool unused = ref.Decrement(); + + if (unused) { + this->Page::~Page(); + free(this); + } + + return unused; +} diff --git a/src/output/plugins/httpd/Page.hxx b/src/output/plugins/httpd/Page.hxx new file mode 100644 index 000000000..95f35d06a --- /dev/null +++ b/src/output/plugins/httpd/Page.hxx @@ -0,0 +1,102 @@ +/* + * 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. + */ + +/** \file + * + * This is a library which manages reference counted buffers. + */ + +#ifndef MPD_PAGE_HXX +#define MPD_PAGE_HXX + +#include "util/RefCount.hxx" + +#include <stddef.h> + +/** + * A dynamically allocated buffer which keeps track of its reference + * count. This is useful for passing buffers around, when several + * instances hold references to one buffer. + */ +class Page { + /** + * The number of references to this buffer. This library uses + * atomic functions to access it, i.e. no locks are required. + * As soon as this attribute reaches zero, the buffer is + * freed. + */ + RefCount ref; + +public: + /** + * The size of this buffer in bytes. + */ + const size_t size; + + /** + * Dynamic array containing the buffer data. + */ + unsigned char data[sizeof(long)]; + +protected: + Page(size_t _size):size(_size) {} + ~Page() = default; + + /** + * Allocates a new #Page object, without filling the data + * element. + */ + static Page *Create(size_t size); + +public: + /** + * Creates a new #page object, and copies data from the + * specified buffer. It is initialized with a reference count + * of 1. + * + * @param data the source buffer + * @param size the size of the source buffer + */ + static Page *Copy(const void *data, size_t size); + + /** + * Concatenates two pages to a new page. + * + * @param a the first page + * @param b the second page, which is appended + */ + static Page *Concat(const Page &a, const Page &b); + + /** + * Increases the reference counter. + */ + void Ref() { + ref.Increment(); + } + + /** + * Decreases the reference counter. If it reaches zero, the #page is + * freed. + * + * @return true if the #page has been freed + */ + bool Unref(); +}; + +#endif diff --git a/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx new file mode 100644 index 000000000..c7dd4ccca --- /dev/null +++ b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP +#define SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP + +#include <SLES/OpenSLES_Android.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLAndroidSimpleBufferQueueItf + * variable. + */ + class AndroidSimpleBufferQueue { + SLAndroidSimpleBufferQueueItf queue; + + public: + AndroidSimpleBufferQueue() = default; + explicit AndroidSimpleBufferQueue(SLAndroidSimpleBufferQueueItf _queue) + :queue(_queue) {} + + SLresult Enqueue(const void *pBuffer, SLuint32 size) { + return (*queue)->Enqueue(queue, pBuffer, size); + } + + SLresult Clear() { + return (*queue)->Clear(queue); + } + + SLresult GetState(SLAndroidSimpleBufferQueueState *pState) { + return (*queue)->GetState(queue, pState); + } + + SLresult RegisterCallback(slAndroidSimpleBufferQueueCallback callback, + void *pContext) { + return (*queue)->RegisterCallback(queue, callback, pContext); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/Engine.hxx b/src/output/plugins/sles/Engine.hxx new file mode 100644 index 000000000..7c6e3cf50 --- /dev/null +++ b/src/output/plugins/sles/Engine.hxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_ENGINE_HPP +#define SLES_ENGINE_HPP + +#include <SLES/OpenSLES.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLEngineItf variable. + */ + class Engine { + SLEngineItf engine; + + public: + Engine() = default; + explicit Engine(SLEngineItf _engine):engine(_engine) {} + + SLresult CreateAudioPlayer(SLObjectItf *pPlayer, + SLDataSource *pAudioSrc, SLDataSink *pAudioSnk, + SLuint32 numInterfaces, + const SLInterfaceID *pInterfaceIds, + const SLboolean *pInterfaceRequired) { + return (*engine)->CreateAudioPlayer(engine, pPlayer, + pAudioSrc, pAudioSnk, + numInterfaces, pInterfaceIds, + pInterfaceRequired); + } + + SLresult CreateOutputMix(SLObjectItf *pMix, + SLuint32 numInterfaces, + const SLInterfaceID *pInterfaceIds, + const SLboolean *pInterfaceRequired) { + return (*engine)->CreateOutputMix(engine, pMix, + numInterfaces, pInterfaceIds, + pInterfaceRequired); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/Object.hxx b/src/output/plugins/sles/Object.hxx new file mode 100644 index 000000000..852d62d0d --- /dev/null +++ b/src/output/plugins/sles/Object.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_OBJECT_HPP +#define SLES_OBJECT_HPP + +#include <SLES/OpenSLES.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLObjectItf variable. + */ + class Object { + SLObjectItf object; + + public: + Object() = default; + explicit Object(SLObjectItf _object):object(_object) {} + + operator SLObjectItf() { + return object; + } + + SLresult Realize(bool async) { + return (*object)->Realize(object, async); + } + + void Destroy() { + (*object)->Destroy(object); + } + + SLresult GetInterface(const SLInterfaceID iid, void *pInterface) { + return (*object)->GetInterface(object, iid, pInterface); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/Play.hxx b/src/output/plugins/sles/Play.hxx new file mode 100644 index 000000000..c760151ef --- /dev/null +++ b/src/output/plugins/sles/Play.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_PLAY_HPP +#define SLES_PLAY_HPP + +#include <SLES/OpenSLES.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLPlayItf variable. + */ + class Play { + SLPlayItf play; + + public: + Play() = default; + explicit Play(SLPlayItf _play):play(_play) {} + + SLresult SetPlayState(SLuint32 state) { + return (*play)->SetPlayState(play, state); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx new file mode 100644 index 000000000..85fd9f2f2 --- /dev/null +++ b/src/output/plugins/sles/SlesOutputPlugin.cxx @@ -0,0 +1,539 @@ +/* + * 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 "SlesOutputPlugin.hxx" +#include "Object.hxx" +#include "Engine.hxx" +#include "Play.hxx" +#include "AndroidSimpleBufferQueue.hxx" +#include "../../OutputAPI.hxx" +#include "util/Macros.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#include <SLES/OpenSLES.h> +#include <SLES/OpenSLES_Android.h> + +class SlesOutput { + static constexpr unsigned N_BUFFERS = 3; + static constexpr size_t BUFFER_SIZE = 65536; + + AudioOutput base; + + SLES::Object engine_object, mix_object, play_object; + SLES::Play play; + SLES::AndroidSimpleBufferQueue queue; + + /** + * This mutex protects the attributes "next" and "filled". It + * is only needed while playback is launched, when the initial + * buffers are being enqueued in the caller thread, while + * another thread may invoke the registered callback. + */ + Mutex mutex; + + Cond cond; + + bool pause, cancel; + + /** + * The number of buffers queued to OpenSLES. + */ + unsigned n_queued; + + /** + * The index of the next buffer to be enqueued. + */ + unsigned next; + + /** + * Does the "next" buffer already contain synthesised samples? + * This can happen when PCMSynthesiser::Synthesise() has been + * called, but the OpenSL/ES buffer queue was full. The + * buffer will then be postponed. + */ + unsigned filled; + + /** + * An array of buffers. It's one more than being managed by + * OpenSL/ES, and the one not enqueued (see attribute #next) + * will be written to. + */ + uint8_t buffers[N_BUFFERS][BUFFER_SIZE]; + +public: + SlesOutput() + :base(sles_output_plugin) {} + + operator AudioOutput *() { + return &base; + } + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } + + bool Configure(const config_param ¶m, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + unsigned Delay() { + return pause && !cancel ? 100 : 0; + } + + size_t Play(const void *chunk, size_t size, Error &error); + + void Drain(); + void Cancel(); + bool Pause(); + +private: + void PlayedCallback(); + + /** + * OpenSL/ES callback which gets invoked when a buffer has + * been consumed. It synthesises and enqueues the next + * buffer. + */ + static void PlayedCallback(gcc_unused SLAndroidSimpleBufferQueueItf caller, + void *pContext) + { + SlesOutput &sles = *(SlesOutput *)pContext; + sles.PlayedCallback(); + } +}; + +static constexpr Domain sles_domain("sles"); + +inline bool +SlesOutput::Configure(const config_param &, Error &) +{ + return true; +} + +inline bool +SlesOutput::Open(AudioFormat &audio_format, Error &error) +{ + SLresult result; + SLObjectItf _object; + + result = slCreateEngine(&_object, 0, nullptr, 0, + nullptr, nullptr); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), "slCreateEngine() failed"); + return false; + } + + engine_object = SLES::Object(_object); + + result = engine_object.Realize(false); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), "Engine.Realize() failed"); + engine_object.Destroy(); + return false; + } + + SLEngineItf _engine; + result = engine_object.GetInterface(SL_IID_ENGINE, &_engine); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Engine.GetInterface(IID_ENGINE) failed"); + engine_object.Destroy(); + return false; + } + + SLES::Engine engine(_engine); + + result = engine.CreateOutputMix(&_object, 0, nullptr, nullptr); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Engine.CreateOutputMix() failed"); + engine_object.Destroy(); + return false; + } + + mix_object = SLES::Object(_object); + + result = mix_object.Realize(false); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Mix.Realize() failed"); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + N_BUFFERS, + }; + + if (audio_format.channels > 2) + audio_format.channels = 1; + + SLDataFormat_PCM format_pcm; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = audio_format.channels; + /* from the Android NDK docs: "Note that the field samplesPerSec is + actually in units of milliHz, despite the misleading name." */ + format_pcm.samplesPerSec = audio_format.sample_rate * 1000u; + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.channelMask = audio_format.channels == 1 + ? SL_SPEAKER_FRONT_CENTER + : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + format_pcm.endianness = IsLittleEndian() + ? SL_BYTEORDER_LITTLEENDIAN + : SL_BYTEORDER_BIGENDIAN; + + SLDataSource audioSrc = { &loc_bufq, &format_pcm }; + + SLDataLocator_OutputMix loc_outmix = { + SL_DATALOCATOR_OUTPUTMIX, + mix_object, + }; + + SLDataSink audioSnk = { + &loc_outmix, + nullptr, + }; + + const SLInterfaceID ids2[] = { + SL_IID_PLAY, + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_ANDROIDCONFIGURATION, + }; + + static constexpr SLboolean req2[] = { + SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE, + }; + + result = engine.CreateAudioPlayer(&_object, &audioSrc, &audioSnk, + ARRAY_SIZE(ids2), ids2, req2); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Engine.CreateAudioPlayer() failed"); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + play_object = SLES::Object(_object); + + SLAndroidConfigurationItf android_config; + if (play_object.GetInterface(SL_IID_ANDROIDCONFIGURATION, + &android_config) == SL_RESULT_SUCCESS) { + SLint32 stream_type = SL_ANDROID_STREAM_MEDIA; + (*android_config)->SetConfiguration(android_config, + SL_ANDROID_KEY_STREAM_TYPE, + &stream_type, + sizeof(stream_type)); + } + + result = play_object.Realize(false); + + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.Realize() failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + SLPlayItf _play; + result = play_object.GetInterface(SL_IID_PLAY, &_play); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.GetInterface(IID_PLAY) failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + play = SLES::Play(_play); + + SLAndroidSimpleBufferQueueItf _queue; + result = play_object.GetInterface(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &_queue); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.GetInterface(IID_ANDROIDSIMPLEBUFFERQUEUE) failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + queue = SLES::AndroidSimpleBufferQueue(_queue); + result = queue.RegisterCallback(PlayedCallback, (void *)this); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.RegisterCallback() failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + result = play.SetPlayState(SL_PLAYSTATE_PLAYING); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.SetPlayState(PLAYING) failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + pause = cancel = false; + n_queued = 0; + next = 0; + filled = 0; + + // TODO: support other sample formats + audio_format.format = SampleFormat::S16; + + return true; +} + +inline void +SlesOutput::Close() +{ + play.SetPlayState(SL_PLAYSTATE_STOPPED); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); +} + +inline size_t +SlesOutput::Play(const void *chunk, size_t size, Error &error) +{ + cancel = false; + + if (pause) { + SLresult result = play.SetPlayState(SL_PLAYSTATE_PLAYING); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.SetPlayState(PLAYING) failed"); + return false; + } + + pause = false; + } + + const ScopeLock protect(mutex); + + assert(filled < BUFFER_SIZE); + + while (n_queued == N_BUFFERS) { + assert(filled == 0); + cond.wait(mutex); + } + + size_t nbytes = std::min(BUFFER_SIZE - filled, size); + memcpy(buffers[next] + filled, chunk, nbytes); + filled += nbytes; + if (filled < BUFFER_SIZE) + return nbytes; + + SLresult result = queue.Enqueue(buffers[next], BUFFER_SIZE); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "AndroidSimpleBufferQueue.Enqueue() failed"); + return 0; + } + + ++n_queued; + next = (next + 1) % N_BUFFERS; + filled = 0; + + return nbytes; +} + +inline void +SlesOutput::Drain() +{ + const ScopeLock protect(mutex); + + assert(filled < BUFFER_SIZE); + + while (n_queued > 0) + cond.wait(mutex); +} + +inline void +SlesOutput::Cancel() +{ + pause = true; + cancel = true; + + SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED); + if (result != SL_RESULT_SUCCESS) + FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed"); + + result = queue.Clear(); + if (result != SL_RESULT_SUCCESS) + FormatWarning(sles_domain, + "AndroidSimpleBufferQueue.Clear() failed"); + + const ScopeLock protect(mutex); + n_queued = 0; + filled = 0; +} + +inline bool +SlesOutput::Pause() +{ + cancel = false; + + if (pause) + return true; + + pause = true; + + SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED); + if (result != SL_RESULT_SUCCESS) { + FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed"); + return false; + } + + return true; +} + +inline void +SlesOutput::PlayedCallback() +{ + const ScopeLock protect(mutex); + assert(n_queued > 0); + --n_queued; + cond.signal(); +} + +static bool +sles_test_default_device() +{ + /* this is the default output plugin on Android, and it should + be available in any case */ + return true; +} + +static AudioOutput * +sles_output_init(const config_param ¶m, Error &error) +{ + SlesOutput *sles = new SlesOutput(); + + if (!sles->Initialize(param, error) || + !sles->Configure(param, error)) { + delete sles; + return nullptr; + } + + return *sles; +} + +static void +sles_output_finish(AudioOutput *ao) +{ + SlesOutput *sles = (SlesOutput *)ao; + + delete sles; +} + +static bool +sles_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Open(audio_format, error); +} + +static void +sles_output_close(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + sles.Close(); +} + +static unsigned +sles_output_delay(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Delay(); +} + +static size_t +sles_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Play(chunk, size, error); +} + +static void +sles_output_drain(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + sles.Drain(); +} + +static void +sles_output_cancel(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + sles.Cancel(); +} + +static bool +sles_output_pause(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Pause(); +} + +const struct AudioOutputPlugin sles_output_plugin = { + "sles", + sles_test_default_device, + sles_output_init, + sles_output_finish, + nullptr, + nullptr, + sles_output_open, + sles_output_close, + sles_output_delay, + nullptr, + sles_output_play, + sles_output_drain, + sles_output_cancel, + sles_output_pause, + nullptr, +}; diff --git a/src/output/plugins/sles/SlesOutputPlugin.hxx b/src/output/plugins/sles/SlesOutputPlugin.hxx new file mode 100644 index 000000000..5424dec2e --- /dev/null +++ b/src/output/plugins/sles/SlesOutputPlugin.hxx @@ -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_SLES_OUTPUT_PLUGIN_HXX +#define MPD_SLES_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin sles_output_plugin; + +#endif |