diff options
Diffstat (limited to 'src/output')
67 files changed, 2752 insertions, 2025 deletions
diff --git a/src/output/Domain.cxx b/src/output/Domain.cxx index 878e5f3c5..abfdb6a5e 100644 --- a/src/output/Domain.cxx +++ b/src/output/Domain.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/Domain.hxx b/src/output/Domain.hxx index e3a20142f..136a2b8a9 100644 --- a/src/output/Domain.hxx +++ b/src/output/Domain.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/Finish.cxx b/src/output/Finish.cxx index be2ca463e..2dd4acda5 100644 --- a/src/output/Finish.cxx +++ b/src/output/Finish.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/Init.cxx b/src/output/Init.cxx index 79ef4f998..ae34bf846 100644 --- a/src/output/Init.cxx +++ b/src/output/Init.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -35,6 +35,7 @@ #include "filter/plugins/ChainFilterPlugin.hxx" #include "config/ConfigError.hxx" #include "config/ConfigGlobal.hxx" +#include "config/Block.hxx" #include "util/Error.hxx" #include "Log.hxx" @@ -58,7 +59,7 @@ AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin) filter(nullptr), replay_gain_filter(nullptr), other_replay_gain_filter(nullptr), - command(AO_COMMAND_NONE) + command(Command::NONE) { assert(plugin.finish != nullptr); assert(plugin.open != nullptr); @@ -94,27 +95,27 @@ audio_output_detect(Error &error) * mixer_enabled, if the mixer_type setting is not configured. */ gcc_pure -static enum mixer_type -audio_output_mixer_type(const config_param ¶m) +static MixerType +audio_output_mixer_type(const ConfigBlock &block) { /* read the local "mixer_type" setting */ - const char *p = param.GetBlockValue("mixer_type"); + const char *p = block.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; + if (!block.GetBlockValue("mixer_enabled", true)) + return MixerType::NONE; /* fall back to the global "mixer_type" setting (also deprecated) */ - return mixer_type_parse(config_get_string(CONF_MIXER_TYPE, + return mixer_type_parse(config_get_string(ConfigOption::MIXER_TYPE, "hardware")); } static Mixer * audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao, - const config_param ¶m, + const ConfigBlock &block, const MixerPlugin *plugin, Filter &filter_chain, MixerListener &listener, @@ -122,22 +123,26 @@ audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao, { Mixer *mixer; - switch (audio_output_mixer_type(param)) { - case MIXER_TYPE_NONE: - case MIXER_TYPE_UNKNOWN: + switch (audio_output_mixer_type(block)) { + case MixerType::NONE: + case MixerType::UNKNOWN: return nullptr; - case MIXER_TYPE_HARDWARE: + case MixerType::NULL_: + return mixer_new(event_loop, null_mixer_plugin, ao, listener, + block, error); + + case MixerType::HARDWARE: if (plugin == nullptr) return nullptr; return mixer_new(event_loop, *plugin, ao, listener, - param, error); + block, error); - case MIXER_TYPE_SOFTWARE: + case MixerType::SOFTWARE: mixer = mixer_new(event_loop, software_mixer_plugin, ao, listener, - config_param(), + ConfigBlock(), IgnoreError()); assert(mixer != nullptr); @@ -151,17 +156,17 @@ audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao, } bool -AudioOutput::Configure(const config_param ¶m, Error &error) +AudioOutput::Configure(const ConfigBlock &block, Error &error) { - if (!param.IsNull()) { - name = param.GetBlockValue(AUDIO_OUTPUT_NAME); + if (!block.IsNull()) { + name = block.GetBlockValue(AUDIO_OUTPUT_NAME); if (name == nullptr) { error.Set(config_domain, "Missing \"name\" configuration"); return false; } - const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT); + const char *p = block.GetBlockValue(AUDIO_OUTPUT_FORMAT); if (p != nullptr) { bool success = audio_format_parse(config_audio_format, @@ -176,9 +181,9 @@ AudioOutput::Configure(const config_param ¶m, Error &error) config_audio_format.Clear(); } - tags = param.GetBlockValue("tags", true); - always_on = param.GetBlockValue("always_on", false); - enabled = param.GetBlockValue("enabled", true); + tags = block.GetBlockValue("tags", true); + always_on = block.GetBlockValue("always_on", false); + enabled = block.GetBlockValue("enabled", true); /* set up the filter chain */ @@ -187,9 +192,9 @@ AudioOutput::Configure(const config_param ¶m, Error &error) /* create the normalization filter (if configured) */ - if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + if (config_get_bool(ConfigOption::VOLUME_NORMALIZATION, false)) { Filter *normalize_filter = - filter_new(&normalize_filter_plugin, config_param(), + filter_new(&normalize_filter_plugin, ConfigBlock(), IgnoreError()); assert(normalize_filter != nullptr); @@ -199,7 +204,7 @@ AudioOutput::Configure(const config_param ¶m, Error &error) Error filter_error; filter_chain_parse(*filter, - param.GetBlockValue(AUDIO_FILTERS, ""), + block.GetBlockValue(AUDIO_FILTERS, ""), filter_error); // It's not really fatal - Part of the filter chain has been set up already @@ -217,24 +222,24 @@ AudioOutput::Configure(const config_param ¶m, Error &error) static bool audio_output_setup(EventLoop &event_loop, AudioOutput &ao, MixerListener &mixer_listener, - const config_param ¶m, + const ConfigBlock &block, Error &error) { /* create the replay_gain filter */ const char *replay_gain_handler = - param.GetBlockValue("replay_gain_handler", "software"); + block.GetBlockValue("replay_gain_handler", "software"); if (strcmp(replay_gain_handler, "none") != 0) { ao.replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, IgnoreError()); + block, IgnoreError()); assert(ao.replay_gain_filter != nullptr); ao.replay_gain_serial = 0; ao.other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, + block, IgnoreError()); assert(ao.other_replay_gain_filter != nullptr); @@ -247,7 +252,7 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao, /* set up the mixer */ Error mixer_error; - ao.mixer = audio_output_load_mixer(event_loop, ao, param, + ao.mixer = audio_output_load_mixer(event_loop, ao, block, ao.plugin.mixer_plugin, *ao.filter, mixer_listener, @@ -275,7 +280,7 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao, /* the "convert" filter must be the last one in the chain */ - ao.convert_filter = filter_new(&convert_filter_plugin, config_param(), + ao.convert_filter = filter_new(&convert_filter_plugin, ConfigBlock(), IgnoreError()); assert(ao.convert_filter != nullptr); @@ -285,17 +290,17 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao, } AudioOutput * -audio_output_new(EventLoop &event_loop, const config_param ¶m, +audio_output_new(EventLoop &event_loop, const ConfigBlock &block, MixerListener &mixer_listener, PlayerControl &pc, Error &error) { const AudioOutputPlugin *plugin; - if (!param.IsNull()) { + if (!block.IsNull()) { const char *p; - p = param.GetBlockValue(AUDIO_OUTPUT_TYPE); + p = block.GetBlockValue(AUDIO_OUTPUT_TYPE); if (p == nullptr) { error.Set(config_domain, "Missing \"type\" configuration"); @@ -321,12 +326,12 @@ audio_output_new(EventLoop &event_loop, const config_param ¶m, plugin->name); } - AudioOutput *ao = ao_plugin_init(plugin, param, error); + AudioOutput *ao = ao_plugin_init(plugin, block, error); if (ao == nullptr) return nullptr; if (!audio_output_setup(event_loop, *ao, mixer_listener, - param, error)) { + block, error)) { ao_plugin_finish(ao); return nullptr; } diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx index 6e6ffb442..6b67a8783 100644 --- a/src/output/Internal.hxx +++ b/src/output/Internal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -36,36 +36,36 @@ class EventLoop; class Mixer; class MixerListener; struct MusicChunk; -struct config_param; +struct ConfigBlock; struct PlayerControl; struct AudioOutputPlugin; -enum audio_output_command { - AO_COMMAND_NONE = 0, - AO_COMMAND_ENABLE, - AO_COMMAND_DISABLE, - AO_COMMAND_OPEN, +struct AudioOutput { + enum class Command { + NONE, + ENABLE, + DISABLE, + OPEN, - /** - * This command is invoked when the input audio format - * changes. - */ - AO_COMMAND_REOPEN, + /** + * This command is invoked when the input audio format + * changes. + */ + REOPEN, - AO_COMMAND_CLOSE, - AO_COMMAND_PAUSE, + CLOSE, + PAUSE, - /** - * Drains the internal (hardware) buffers of the device. This - * operation may take a while to complete. - */ - AO_COMMAND_DRAIN, + /** + * Drains the internal (hardware) buffers of the device. This + * operation may take a while to complete. + */ + DRAIN, - AO_COMMAND_CANCEL, - AO_COMMAND_KILL -}; + CANCEL, + KILL + }; -struct AudioOutput { /** * The device's configured display name. */ @@ -231,7 +231,7 @@ struct AudioOutput { /** * The next command to be performed by the output thread. */ - enum audio_output_command command; + Command command; /** * The music pipe which provides music chunks to be played. @@ -272,7 +272,7 @@ struct AudioOutput { AudioOutput(const AudioOutputPlugin &_plugin); ~AudioOutput(); - bool Configure(const config_param ¶m, Error &error); + bool Configure(const ConfigBlock &block, Error &error); void StartThread(); void StopThread(); @@ -284,7 +284,7 @@ struct AudioOutput { } bool IsCommandFinished() const { - return command == AO_COMMAND_NONE; + return command == Command::NONE; } /** @@ -299,7 +299,7 @@ struct AudioOutput { * * Caller must lock the mutex. */ - void CommandAsync(audio_output_command cmd); + void CommandAsync(Command cmd); /** * Sends a command to the #AudioOutput object and waits for @@ -307,13 +307,13 @@ struct AudioOutput { * * Caller must lock the mutex. */ - void CommandWait(audio_output_command cmd); + void CommandWait(Command cmd); /** * Lock the #AudioOutput object and execute the command * synchronously. */ - void LockCommandWait(audio_output_command cmd); + void LockCommandWait(Command cmd); /** * Enables the device. @@ -382,6 +382,13 @@ private: void Close(bool drain); void Reopen(); + /** + * Close the output plugin. + * + * Mutex must not be locked. + */ + void CloseOutput(bool drain); + AudioFormat OpenFilter(AudioFormat &format, Error &error_r); /** @@ -430,7 +437,7 @@ private: extern struct notify audio_output_client_notify; AudioOutput * -audio_output_new(EventLoop &event_loop, const config_param ¶m, +audio_output_new(EventLoop &event_loop, const ConfigBlock &block, MixerListener &mixer_listener, PlayerControl &pc, Error &error); diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx index 33ab57894..fc8b3888b 100644 --- a/src/output/MultipleOutputs.cxx +++ b/src/output/MultipleOutputs.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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 "MultipleOutputs.hxx" -#include "PlayerControl.hxx" +#include "player/Control.hxx" #include "Internal.hxx" #include "Domain.hxx" #include "MusicBuffer.hxx" @@ -27,7 +27,7 @@ #include "MusicChunk.hxx" #include "system/FatalError.hxx" #include "util/Error.hxx" -#include "config/ConfigData.hxx" +#include "config/Block.hxx" #include "config/ConfigGlobal.hxx" #include "config/ConfigOption.hxx" #include "notify.hxx" @@ -53,16 +53,16 @@ MultipleOutputs::~MultipleOutputs() static AudioOutput * LoadOutput(EventLoop &event_loop, MixerListener &mixer_listener, - PlayerControl &pc, const config_param ¶m) + PlayerControl &pc, const ConfigBlock &block) { Error error; - AudioOutput *output = audio_output_new(event_loop, param, + AudioOutput *output = audio_output_new(event_loop, block, mixer_listener, pc, error); if (output == nullptr) { - if (param.line > 0) + if (block.line > 0) FormatFatalError("line %i: %s", - param.line, + block.line, error.GetMessage()); else FatalError(error); @@ -74,7 +74,7 @@ LoadOutput(EventLoop &event_loop, MixerListener &mixer_listener, void MultipleOutputs::Configure(EventLoop &event_loop, PlayerControl &pc) { - for (const config_param *param = config_get_param(CONF_AUDIO_OUTPUT); + for (const auto *param = config_get_block(ConfigBlockOption::AUDIO_OUTPUT); param != nullptr; param = param->next) { auto output = LoadOutput(event_loop, mixer_listener, pc, *param); @@ -87,7 +87,7 @@ MultipleOutputs::Configure(EventLoop &event_loop, PlayerControl &pc) if (outputs.empty()) { /* auto-detect device */ - const config_param empty; + const ConfigBlock empty; auto output = LoadOutput(event_loop, mixer_listener, pc, empty); outputs.push_back(output); diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx index 2c6536e2a..8b8360501 100644 --- a/src/output/MultipleOutputs.hxx +++ b/src/output/MultipleOutputs.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -120,7 +120,7 @@ public: * Opens all audio outputs which are not disabled. * * @param audio_format the preferred audio format - * @param buffer the #music_buffer where consumed #MusicChunk objects + * @param _buffer the #music_buffer where consumed #MusicChunk objects * should be returned * @return true on success, false on failure */ diff --git a/src/output/OutputAPI.hxx b/src/output/OutputAPI.hxx index e0fd6eec8..af3f90344 100644 --- a/src/output/OutputAPI.hxx +++ b/src/output/OutputAPI.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,7 @@ #include "Internal.hxx" #include "AudioFormat.hxx" #include "tag/Tag.hxx" -#include "config/ConfigData.hxx" +#include "config/Block.hxx" // IWYU pragma: end_exports diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx index e6b8a8e7f..dc7a540a2 100644 --- a/src/output/OutputCommand.cxx +++ b/src/output/OutputCommand.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ #include "OutputCommand.hxx" #include "MultipleOutputs.hxx" #include "Internal.hxx" -#include "PlayerControl.hxx" +#include "player/Control.hxx" #include "mixer/MixerControl.hxx" #include "mixer/Volume.hxx" #include "Idle.hxx" diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx index 53fc5c95e..5b53cd1c2 100644 --- a/src/output/OutputCommand.hxx +++ b/src/output/OutputCommand.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx index 9eafdb166..d7a114c01 100644 --- a/src/output/OutputControl.cxx +++ b/src/output/OutputControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -46,7 +46,7 @@ AudioOutput::WaitForCommand() } void -AudioOutput::CommandAsync(audio_output_command cmd) +AudioOutput::CommandAsync(Command cmd) { assert(IsCommandFinished()); @@ -55,14 +55,14 @@ AudioOutput::CommandAsync(audio_output_command cmd) } void -AudioOutput::CommandWait(audio_output_command cmd) +AudioOutput::CommandWait(Command cmd) { CommandAsync(cmd); WaitForCommand(); } void -AudioOutput::LockCommandWait(audio_output_command cmd) +AudioOutput::LockCommandWait(Command cmd) { const ScopeLock protect(mutex); CommandWait(cmd); @@ -92,7 +92,7 @@ AudioOutput::LockEnableWait() StartThread(); } - LockCommandWait(AO_COMMAND_ENABLE); + LockCommandWait(Command::ENABLE); } void @@ -109,7 +109,7 @@ AudioOutput::LockDisableWait() return; } - LockCommandWait(AO_COMMAND_DISABLE); + LockCommandWait(Command::DISABLE); } inline bool @@ -134,7 +134,7 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) /* we're not using audio_output_cancel() here, because that function is asynchronous */ - CommandWait(AO_COMMAND_CANCEL); + CommandWait(Command::CANCEL); } return true; @@ -148,7 +148,9 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) if (!thread.IsDefined()) StartThread(); - CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); + CommandWait(open + ? Command::REOPEN + : Command::OPEN); const bool open2 = open; if (open2 && mixer != nullptr) { @@ -172,7 +174,7 @@ AudioOutput::CloseWait() assert(!open || !fail_timer.IsDefined()); if (open) - CommandWait(AO_COMMAND_CLOSE); + CommandWait(Command::CLOSE); else fail_timer.Reset(); } @@ -220,7 +222,7 @@ AudioOutput::LockPauseAsync() assert(allow_play); if (IsOpen()) - CommandAsync(AO_COMMAND_PAUSE); + CommandAsync(Command::PAUSE); } void @@ -230,7 +232,7 @@ AudioOutput::LockDrainAsync() assert(allow_play); if (IsOpen()) - CommandAsync(AO_COMMAND_DRAIN); + CommandAsync(Command::DRAIN); } void @@ -240,7 +242,7 @@ AudioOutput::LockCancelAsync() if (IsOpen()) { allow_play = false; - CommandAsync(AO_COMMAND_CANCEL); + CommandAsync(Command::CANCEL); } } @@ -278,7 +280,7 @@ AudioOutput::StopThread() assert(thread.IsDefined()); assert(allow_play); - LockCommandWait(AO_COMMAND_KILL); + LockCommandWait(Command::KILL); thread.Join(); } diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx index fff3fe406..5c8f49718 100644 --- a/src/output/OutputControl.hxx +++ b/src/output/OutputControl.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx index 33bb854d4..7d95ef345 100644 --- a/src/output/OutputPlugin.cxx +++ b/src/output/OutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,13 +23,13 @@ AudioOutput * ao_plugin_init(const AudioOutputPlugin *plugin, - const config_param ¶m, + const ConfigBlock &block, Error &error) { assert(plugin != nullptr); assert(plugin->init != nullptr); - return plugin->init(param, error); + return plugin->init(block, error); } void @@ -75,7 +75,7 @@ ao_plugin_delay(AudioOutput *ao) } void -ao_plugin_send_tag(AudioOutput *ao, const Tag *tag) +ao_plugin_send_tag(AudioOutput *ao, const Tag &tag) { if (ao->plugin.send_tag != nullptr) ao->plugin.send_tag(ao, tag); diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx index 00fa36bc0..2f6869368 100644 --- a/src/output/OutputPlugin.hxx +++ b/src/output/OutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,7 +24,7 @@ #include <stddef.h> -struct config_param; +struct ConfigBlock; struct AudioFormat; struct Tag; struct AudioOutput; @@ -44,7 +44,7 @@ struct AudioOutputPlugin { * Test if this plugin can provide a default output, in case * none has been configured. This method is optional. */ - bool (*test_default_device)(void); + bool (*test_default_device)(); /** * Configure and initialize the device, but do not open it @@ -55,8 +55,7 @@ struct AudioOutputPlugin { * @return nullptr on error, or an opaque pointer to the plugin's * data */ - AudioOutput *(*init)(const config_param ¶m, - Error &error); + AudioOutput *(*init)(const ConfigBlock &block, Error &error); /** * Free resources allocated by this device. @@ -107,7 +106,7 @@ struct AudioOutputPlugin { * Display metadata for the next chunk. Optional method, * because not all devices can display metadata. */ - void (*send_tag)(AudioOutput *data, const Tag *tag); + void (*send_tag)(AudioOutput *data, const Tag &tag); /** * Play a chunk of audio data. @@ -162,7 +161,7 @@ ao_plugin_test_default_device(const AudioOutputPlugin *plugin) gcc_malloc AudioOutput * ao_plugin_init(const AudioOutputPlugin *plugin, - const config_param ¶m, + const ConfigBlock &block, Error &error); void @@ -186,7 +185,7 @@ unsigned ao_plugin_delay(AudioOutput *ao); void -ao_plugin_send_tag(AudioOutput *ao, const Tag *tag); +ao_plugin_send_tag(AudioOutput *ao, const Tag &tag); size_t ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size, diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx index 414a86e32..d2ddbbf8b 100644 --- a/src/output/OutputPrint.cxx +++ b/src/output/OutputPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,18 +26,17 @@ #include "OutputPrint.hxx" #include "MultipleOutputs.hxx" #include "Internal.hxx" -#include "client/Client.hxx" +#include "client/Response.hxx" void -printAudioDevices(Client &client, const MultipleOutputs &outputs) +printAudioDevices(Response &r, 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); + r.Format("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 index 29aa2b11c..e05c8efd5 100644 --- a/src/output/OutputPrint.hxx +++ b/src/output/OutputPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,10 +25,10 @@ #ifndef MPD_OUTPUT_PRINT_HXX #define MPD_OUTPUT_PRINT_HXX -class Client; +class Response; class MultipleOutputs; void -printAudioDevices(Client &client, const MultipleOutputs &outputs); +printAudioDevices(Response &r, const MultipleOutputs &outputs); #endif diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx index fb01b1c65..a1ca11b49 100644 --- a/src/output/OutputState.cxx +++ b/src/output/OutputState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/OutputState.hxx b/src/output/OutputState.hxx index 47f8429d5..45076d59f 100644 --- a/src/output/OutputState.hxx +++ b/src/output/OutputState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,6 +41,6 @@ audio_output_state_save(BufferedOutputStream &os, * whether the state has changed and the state file should be saved. */ unsigned -audio_output_state_get_version(void); +audio_output_state_get_version(); #endif diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx index 2ec0670c1..67205aa4c 100644 --- a/src/output/OutputThread.cxx +++ b/src/output/OutputThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,7 +27,7 @@ #include "filter/FilterInternal.hxx" #include "filter/plugins/ConvertFilterPlugin.hxx" #include "filter/plugins/ReplayGainFilterPlugin.hxx" -#include "PlayerControl.hxx" +#include "player/Control.hxx" #include "MusicPipe.hxx" #include "MusicChunk.hxx" #include "thread/Util.hxx" @@ -45,8 +45,8 @@ void AudioOutput::CommandFinished() { - assert(command != AO_COMMAND_NONE); - command = AO_COMMAND_NONE; + assert(command != Command::NONE); + command = Command::NONE; mutex.unlock(); audio_output_client_notify.Signal(); @@ -251,12 +251,7 @@ AudioOutput::Close(bool drain) mutex.unlock(); - if (drain) - ao_plugin_drain(this); - else - ao_plugin_cancel(this); - - ao_plugin_close(this); + CloseOutput(drain); CloseFilter(); mutex.lock(); @@ -265,6 +260,17 @@ AudioOutput::Close(bool drain) plugin.name, name); } +inline void +AudioOutput::CloseOutput(bool drain) +{ + if (drain) + ao_plugin_drain(this); + else + ao_plugin_cancel(this); + + ao_plugin_close(this); +} + void AudioOutput::ReopenFilter() { @@ -342,7 +348,7 @@ AudioOutput::WaitForDelay() (void)cond.timed_wait(mutex, delay); - if (command != AO_COMMAND_NONE) + if (command != Command::NONE) return false; } } @@ -455,7 +461,7 @@ AudioOutput::PlayChunk(const MusicChunk *chunk) if (tags && gcc_unlikely(chunk->tag != nullptr)) { mutex.unlock(); - ao_plugin_send_tag(this, chunk->tag); + ao_plugin_send_tag(this, *chunk->tag); mutex.lock(); } @@ -471,7 +477,7 @@ AudioOutput::PlayChunk(const MusicChunk *chunk) Error error; - while (!data.IsEmpty() && command == AO_COMMAND_NONE) { + while (!data.IsEmpty() && command == Command::NONE) { if (!WaitForDelay()) break; @@ -529,7 +535,7 @@ AudioOutput::Play() assert(!in_playback_loop); in_playback_loop = true; - while (chunk != nullptr && command == AO_COMMAND_NONE) { + while (chunk != nullptr && command == Command::NONE) { assert(!current_chunk_finished); current_chunk = chunk; @@ -577,7 +583,7 @@ AudioOutput::Pause() Close(false); break; } - } while (command == AO_COMMAND_NONE); + } while (command == Command::NONE); pause = false; } @@ -594,30 +600,30 @@ AudioOutput::Task() while (1) { switch (command) { - case AO_COMMAND_NONE: + case Command::NONE: break; - case AO_COMMAND_ENABLE: + case Command::ENABLE: Enable(); CommandFinished(); break; - case AO_COMMAND_DISABLE: + case Command::DISABLE: Disable(); CommandFinished(); break; - case AO_COMMAND_OPEN: + case Command::OPEN: Open(); CommandFinished(); break; - case AO_COMMAND_REOPEN: + case Command::REOPEN: Reopen(); CommandFinished(); break; - case AO_COMMAND_CLOSE: + case Command::CLOSE: assert(open); assert(pipe != nullptr); @@ -625,7 +631,7 @@ AudioOutput::Task() CommandFinished(); break; - case AO_COMMAND_PAUSE: + case Command::PAUSE: if (!open) { /* the output has failed after audio_output_all_pause() has @@ -642,7 +648,7 @@ AudioOutput::Task() the new command first */ continue; - case AO_COMMAND_DRAIN: + case Command::DRAIN: if (open) { assert(current_chunk == nullptr); assert(pipe->Peek() == nullptr); @@ -655,7 +661,7 @@ AudioOutput::Task() CommandFinished(); continue; - case AO_COMMAND_CANCEL: + case Command::CANCEL: current_chunk = nullptr; if (open) { @@ -667,7 +673,7 @@ AudioOutput::Task() CommandFinished(); continue; - case AO_COMMAND_KILL: + case Command::KILL: current_chunk = nullptr; CommandFinished(); mutex.unlock(); @@ -679,7 +685,7 @@ AudioOutput::Task() chunks in the pipe */ continue; - if (command == AO_COMMAND_NONE) { + if (command == Command::NONE) { woken_for_play = false; cond.wait(mutex); } @@ -696,7 +702,7 @@ AudioOutput::Task(void *arg) void AudioOutput::StartThread() { - assert(command == AO_COMMAND_NONE); + assert(command == Command::NONE); Error error; if (!thread.Start(Task, this, error)) diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx index 566f6b6a8..b1734983e 100644 --- a/src/output/Registry.cxx +++ b/src/output/Registry.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "plugins/AoOutputPlugin.hxx" #include "plugins/FifoOutputPlugin.hxx" #include "plugins/httpd/HttpdOutputPlugin.hxx" +#include "plugins/HaikuOutputPlugin.hxx" #include "plugins/JackOutputPlugin.hxx" #include "plugins/NullOutputPlugin.hxx" #include "plugins/OpenALOutputPlugin.hxx" @@ -51,16 +52,19 @@ const AudioOutputPlugin *const audio_output_plugins[] = { #ifdef HAVE_FIFO &fifo_output_plugin, #endif +#ifdef HAVE_HAIKU + &haiku_output_plugin, +#endif #ifdef ENABLE_PIPE_OUTPUT &pipe_output_plugin, #endif -#ifdef HAVE_ALSA +#ifdef ENABLE_ALSA &alsa_output_plugin, #endif -#ifdef HAVE_ROAR +#ifdef ENABLE_ROAR &roar_output_plugin, #endif -#ifdef HAVE_AO +#ifdef ENABLE_AO &ao_output_plugin, #endif #ifdef HAVE_OSS @@ -75,10 +79,10 @@ const AudioOutputPlugin *const audio_output_plugins[] = { #ifdef ENABLE_SOLARIS_OUTPUT &solaris_output_plugin, #endif -#ifdef HAVE_PULSE +#ifdef ENABLE_PULSE &pulse_output_plugin, #endif -#ifdef HAVE_JACK +#ifdef ENABLE_JACK &jack_output_plugin, #endif #ifdef ENABLE_HTTPD_OUTPUT diff --git a/src/output/Registry.hxx b/src/output/Registry.hxx index bc9c1ae2b..2c7202a75 100644 --- a/src/output/Registry.hxx +++ b/src/output/Registry.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/Timer.cxx b/src/output/Timer.cxx index d3dcc714d..a75744744 100644 --- a/src/output/Timer.cxx +++ b/src/output/Timer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/Timer.hxx b/src/output/Timer.hxx index 3c935cfac..057090c1e 100644 --- a/src/output/Timer.hxx +++ b/src/output/Timer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/Wrapper.hxx b/src/output/Wrapper.hxx new file mode 100644 index 000000000..c043849bb --- /dev/null +++ b/src/output/Wrapper.hxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2015 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_WRAPPER_HXX +#define MPD_OUTPUT_WRAPPER_HXX + +#include "util/Cast.hxx" + +struct ConfigBlock; + +template<class T> +struct AudioOutputWrapper { + static T &Cast(AudioOutput &ao) { + return ContainerCast(ao, &T::base); + } + + static AudioOutput *Init(const ConfigBlock &block, Error &error) { + T *t = T::Create(block, error); + return t != nullptr + ? &t->base + : nullptr; + } + + static void Finish(AudioOutput *ao) { + T *t = &Cast(*ao); + delete t; + } + + static bool Enable(AudioOutput *ao, Error &error) { + T &t = Cast(*ao); + return t.Enable(error); + } + + static void Disable(AudioOutput *ao) { + T &t = Cast(*ao); + t.Disable(); + } + + static bool Open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) { + T &t = Cast(*ao); + return t.Open(audio_format, error); + } + + static void Close(AudioOutput *ao) { + T &t = Cast(*ao); + t.Close(); + } + + gcc_pure + static unsigned Delay(AudioOutput *ao) { + T &t = Cast(*ao); + return t.Delay(); + } + + gcc_pure + static void SendTag(AudioOutput *ao, const Tag &tag) { + T &t = Cast(*ao); + t.SendTag(tag); + } + + static size_t Play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) { + T &t = Cast(*ao); + return t.Play(chunk, size, error); + } + + static void Drain(AudioOutput *ao) { + T &t = Cast(*ao); + t.Drain(); + } + + static void Cancel(AudioOutput *ao) { + T &t = Cast(*ao); + t.Cancel(); + } + + gcc_pure + static bool Pause(AudioOutput *ao) { + T &t = Cast(*ao); + return t.Pause(); + } +}; + +#endif diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 28c374a00..8a7bb9643 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "AlsaOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "mixer/MixerList.hxx" #include "pcm/PcmExport.hxx" #include "config/ConfigError.hxx" @@ -131,92 +132,108 @@ struct AlsaOutput { mode(0), writei(snd_pcm_writei) { } - bool Configure(const config_param ¶m, Error &error); + ~AlsaOutput() { + /* free libasound's config cache */ + snd_config_update_free_global(); + } + + gcc_pure + const char *GetDevice() { + return device.empty() ? default_device : device.c_str(); + } + + bool Configure(const ConfigBlock &block, Error &error); + static AlsaOutput *Create(const ConfigBlock &block, Error &error); + + bool Enable(Error &error); + void Disable(); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + size_t Play(const void *chunk, size_t size, Error &error); + void Drain(); + void Cancel(); + +private: + bool SetupDop(AudioFormat audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + Error &error); + bool SetupOrDop(AudioFormat &audio_format, Error &error); + + int Recover(int err); + + /** + * Write silence to the ALSA device. + */ + void WriteSilence(snd_pcm_uframes_t nframes) { + writei(pcm, silence, nframes); + } + }; static constexpr Domain alsa_output_domain("alsa_output"); -static const char * -alsa_device(const AlsaOutput *ad) -{ - return ad->device.empty() ? default_device : ad->device.c_str(); -} - inline bool -AlsaOutput::Configure(const config_param ¶m, Error &error) +AlsaOutput::Configure(const ConfigBlock &block, Error &error) { - if (!base.Configure(param, error)) + if (!base.Configure(block, error)) return false; - device = param.GetBlockValue("device", ""); + device = block.GetBlockValue("device", ""); - use_mmap = param.GetBlockValue("use_mmap", false); + use_mmap = block.GetBlockValue("use_mmap", false); - dop = param.GetBlockValue("dop", false) || + dop = block.GetBlockValue("dop", false) || /* legacy name from MPD 0.18 and older: */ - param.GetBlockValue("dsd_usb", false); + block.GetBlockValue("dsd_usb", false); - buffer_time = param.GetBlockValue("buffer_time", + buffer_time = block.GetBlockValue("buffer_time", MPD_ALSA_BUFFER_TIME_US); - period_time = param.GetBlockValue("period_time", 0u); + period_time = block.GetBlockValue("period_time", 0u); #ifdef SND_PCM_NO_AUTO_RESAMPLE - if (!param.GetBlockValue("auto_resample", true)) + if (!block.GetBlockValue("auto_resample", true)) mode |= SND_PCM_NO_AUTO_RESAMPLE; #endif #ifdef SND_PCM_NO_AUTO_CHANNELS - if (!param.GetBlockValue("auto_channels", true)) + if (!block.GetBlockValue("auto_channels", true)) mode |= SND_PCM_NO_AUTO_CHANNELS; #endif #ifdef SND_PCM_NO_AUTO_FORMAT - if (!param.GetBlockValue("auto_format", true)) + if (!block.GetBlockValue("auto_format", true)) mode |= SND_PCM_NO_AUTO_FORMAT; #endif return true; } -static AudioOutput * -alsa_init(const config_param ¶m, Error &error) +inline AlsaOutput * +AlsaOutput::Create(const ConfigBlock &block, Error &error) { AlsaOutput *ad = new AlsaOutput(); - if (!ad->Configure(param, error)) { + if (!ad->Configure(block, error)) { delete ad; return nullptr; } - return &ad->base; + return ad; } -static void -alsa_finish(AudioOutput *ao) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - delete ad; - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -static bool -alsa_output_enable(AudioOutput *ao, gcc_unused Error &error) +inline bool +AlsaOutput::Enable(gcc_unused Error &error) { - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->pcm_export.Construct(); + pcm_export.Construct(); return true; } -static void -alsa_output_disable(AudioOutput *ao) +inline void +AlsaOutput::Disable() { - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->pcm_export.Destruct(); + pcm_export.Destruct(); } static bool @@ -450,7 +467,7 @@ configure_hw: if (err < 0) { FormatWarning(alsa_output_domain, "Cannot set mmap'ed mode on ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(-err)); + ad->GetDevice(), snd_strerror(-err)); LogWarning(alsa_output_domain, "Falling back to direct write mode"); ad->use_mmap = false; @@ -472,7 +489,7 @@ configure_hw: if (err < 0) { error.Format(alsa_output_domain, err, "ALSA device \"%s\" does not support format %s: %s", - alsa_device(ad), + ad->GetDevice(), sample_format_to_string(audio_format.format), snd_strerror(-err)); return false; @@ -489,7 +506,7 @@ configure_hw: if (err < 0) { error.Format(alsa_output_domain, err, "ALSA device \"%s\" does not support %i channels: %s", - alsa_device(ad), (int)audio_format.channels, + ad->GetDevice(), (int)audio_format.channels, snd_strerror(-err)); return false; } @@ -500,7 +517,7 @@ configure_hw: if (err < 0 || sample_rate == 0) { error.Format(alsa_output_domain, err, "ALSA device \"%s\" does not support %u Hz audio", - alsa_device(ad), audio_format.sample_rate); + ad->GetDevice(), audio_format.sample_rate); return false; } audio_format.sample_rate = sample_rate; @@ -631,16 +648,16 @@ configure_hw: error: error.Format(alsa_output_domain, err, "Error opening ALSA device \"%s\" (%s): %s", - alsa_device(ad), cmd, snd_strerror(-err)); + ad->GetDevice(), cmd, snd_strerror(-err)); return false; } -static bool -alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format, - bool *shift8_r, bool *packed_r, bool *reverse_endian_r, - Error &error) +inline bool +AlsaOutput::SetupDop(const AudioFormat audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + Error &error) { - assert(ad->dop); + assert(dop); assert(audio_format.format == SampleFormat::DSD); /* pass 24 bit to alsa_setup() */ @@ -651,7 +668,7 @@ alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format, const AudioFormat check = dop_format; - if (!alsa_setup(ad, dop_format, packed_r, reverse_endian_r, error)) + if (!alsa_setup(this, dop_format, packed_r, reverse_endian_r, error)) return false; /* if the device allows only 32 bit, shift all DoP @@ -668,102 +685,91 @@ alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format, for DSD over USB */ error.Format(alsa_output_domain, "Failed to configure DSD-over-PCM on ALSA device \"%s\"", - alsa_device(ad)); - delete[] ad->silence; + GetDevice()); + delete[] silence; return false; } return true; } -static bool -alsa_setup_or_dop(AlsaOutput *ad, AudioFormat &audio_format, - Error &error) +inline bool +AlsaOutput::SetupOrDop(AudioFormat &audio_format, Error &error) { bool shift8 = false, packed, reverse_endian; - const bool dop = ad->dop && + const bool dop2 = dop && audio_format.format == SampleFormat::DSD; - const bool success = dop - ? alsa_setup_dop(ad, audio_format, - &shift8, &packed, &reverse_endian, - error) - : alsa_setup(ad, audio_format, &packed, &reverse_endian, + const bool success = dop2 + ? SetupDop(audio_format, + &shift8, &packed, &reverse_endian, + error) + : alsa_setup(this, audio_format, &packed, &reverse_endian, error); if (!success) return false; - ad->pcm_export->Open(audio_format.format, - audio_format.channels, - dop, shift8, packed, reverse_endian); + pcm_export->Open(audio_format.format, + audio_format.channels, + dop2, shift8, packed, reverse_endian); return true; } -static bool -alsa_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) +inline bool +AlsaOutput::Open(AudioFormat &audio_format, Error &error) { - AlsaOutput *ad = (AlsaOutput *)ao; - - int err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); + int err = snd_pcm_open(&pcm, GetDevice(), + SND_PCM_STREAM_PLAYBACK, mode); if (err < 0) { error.Format(alsa_output_domain, err, "Failed to open ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(err)); + GetDevice(), snd_strerror(err)); return false; } FormatDebug(alsa_output_domain, "opened %s type=%s", - snd_pcm_name(ad->pcm), - snd_pcm_type_name(snd_pcm_type(ad->pcm))); + snd_pcm_name(pcm), + snd_pcm_type_name(snd_pcm_type(pcm))); - if (!alsa_setup_or_dop(ad, audio_format, error)) { - snd_pcm_close(ad->pcm); + if (!SetupOrDop(audio_format, error)) { + snd_pcm_close(pcm); return false; } - ad->in_frame_size = audio_format.GetFrameSize(); - ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format); + in_frame_size = audio_format.GetFrameSize(); + out_frame_size = pcm_export->GetFrameSize(audio_format); - ad->must_prepare = false; + must_prepare = false; return true; } -/** - * Write silence to the ALSA device. - */ -static void -alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes) -{ - ad->writei(ad->pcm, ad->silence, nframes); -} - -static int -alsa_recover(AlsaOutput *ad, int err) +inline int +AlsaOutput::Recover(int err) { if (err == -EPIPE) { FormatDebug(alsa_output_domain, - "Underrun on ALSA device \"%s\"", alsa_device(ad)); + "Underrun on ALSA device \"%s\"", + GetDevice()); } else if (err == -ESTRPIPE) { FormatDebug(alsa_output_domain, "ALSA device \"%s\" was suspended", - alsa_device(ad)); + GetDevice()); } - switch (snd_pcm_state(ad->pcm)) { + switch (snd_pcm_state(pcm)) { case SND_PCM_STATE_PAUSED: - err = snd_pcm_pause(ad->pcm, /* disable */ 0); + err = snd_pcm_pause(pcm, /* disable */ 0); break; case SND_PCM_STATE_SUSPENDED: - err = snd_pcm_resume(ad->pcm); + err = snd_pcm_resume(pcm); if (err == -EAGAIN) return 0; /* fall-through to snd_pcm_prepare: */ case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: - ad->period_position = 0; - err = snd_pcm_prepare(ad->pcm); + period_position = 0; + err = snd_pcm_prepare(pcm); break; case SND_PCM_STATE_DISCONNECTED: break; @@ -779,67 +785,58 @@ alsa_recover(AlsaOutput *ad, int err) return err; } -static void -alsa_drain(AudioOutput *ao) +inline void +AlsaOutput::Drain() { - AlsaOutput *ad = (AlsaOutput *)ao; - - if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + if (snd_pcm_state(pcm) != SND_PCM_STATE_RUNNING) return; - if (ad->period_position > 0) { + if (period_position > 0) { /* generate some silence to finish the partial period */ snd_pcm_uframes_t nframes = - ad->period_frames - ad->period_position; - alsa_write_silence(ad, nframes); + period_frames - period_position; + WriteSilence(nframes); } - snd_pcm_drain(ad->pcm); + snd_pcm_drain(pcm); - ad->period_position = 0; + period_position = 0; } -static void -alsa_cancel(AudioOutput *ao) +inline void +AlsaOutput::Cancel() { - AlsaOutput *ad = (AlsaOutput *)ao; + period_position = 0; + must_prepare = true; - ad->period_position = 0; - ad->must_prepare = true; - - snd_pcm_drop(ad->pcm); + snd_pcm_drop(pcm); } -static void -alsa_close(AudioOutput *ao) +inline void +AlsaOutput::Close() { - AlsaOutput *ad = (AlsaOutput *)ao; - - snd_pcm_close(ad->pcm); - delete[] ad->silence; + snd_pcm_close(pcm); + delete[] silence; } -static size_t -alsa_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +AlsaOutput::Play(const void *chunk, size_t size, Error &error) { - AlsaOutput *ad = (AlsaOutput *)ao; - assert(size > 0); - assert(size % ad->in_frame_size == 0); + assert(size % in_frame_size == 0); - if (ad->must_prepare) { - ad->must_prepare = false; + if (must_prepare) { + must_prepare = false; - int err = snd_pcm_prepare(ad->pcm); + int err = snd_pcm_prepare(pcm); if (err < 0) { error.Set(alsa_output_domain, err, snd_strerror(-err)); return 0; } } - const auto e = ad->pcm_export->Export({chunk, size}); + const auto e = 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 @@ -852,43 +849,45 @@ alsa_play(AudioOutput *ao, const void *chunk, size_t size, chunk = e.data; size = e.size; - assert(size % ad->out_frame_size == 0); + assert(size % out_frame_size == 0); - size /= ad->out_frame_size; + size /= out_frame_size; assert(size > 0); while (true) { - snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); + snd_pcm_sframes_t ret = writei(pcm, chunk, size); if (ret > 0) { - ad->period_position = (ad->period_position + ret) - % ad->period_frames; + period_position = (period_position + ret) + % period_frames; - size_t bytes_written = ret * ad->out_frame_size; - return ad->pcm_export->CalcSourceSize(bytes_written); + size_t bytes_written = ret * out_frame_size; + return pcm_export->CalcSourceSize(bytes_written); } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && - alsa_recover(ad, ret) < 0) { + Recover(ret) < 0) { error.Set(alsa_output_domain, ret, snd_strerror(-ret)); return 0; } } } +typedef AudioOutputWrapper<AlsaOutput> Wrapper; + const struct AudioOutputPlugin alsa_output_plugin = { "alsa", alsa_test_default_device, - alsa_init, - alsa_finish, - alsa_output_enable, - alsa_output_disable, - alsa_open, - alsa_close, + &Wrapper::Init, + &Wrapper::Finish, + &Wrapper::Enable, + &Wrapper::Disable, + &Wrapper::Open, + &Wrapper::Close, nullptr, nullptr, - alsa_play, - alsa_drain, - alsa_cancel, + &Wrapper::Play, + &Wrapper::Drain, + &Wrapper::Cancel, nullptr, &alsa_mixer_plugin, diff --git a/src/output/plugins/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx index f72116f91..ff7d439a9 100644 --- a/src/output/plugins/AlsaOutputPlugin.hxx +++ b/src/output/plugins/AlsaOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx index af8c88fa1..3c0cf74a4 100644 --- a/src/output/plugins/AoOutputPlugin.cxx +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,12 +20,13 @@ #include "config.h" #include "AoOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "util/DivideString.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include <ao/ao.h> -#include <glib.h> #include <string.h> @@ -45,11 +46,11 @@ struct AoOutput { AoOutput() :base(ao_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); + bool Initialize(const ConfigBlock &block, Error &error) { + return base.Configure(block, error); } - bool Configure(const config_param ¶m, Error &error); + bool Configure(const ConfigBlock &block, Error &error); }; static constexpr Domain ao_output_domain("ao_output"); @@ -89,20 +90,20 @@ ao_output_error(Error &error_r) } inline bool -AoOutput::Configure(const config_param ¶m, Error &error) +AoOutput::Configure(const ConfigBlock &block, Error &error) { const char *value; options = nullptr; - write_size = param.GetBlockValue("write_size", 1024u); + write_size = block.GetBlockValue("write_size", 1024u); if (ao_output_ref == 0) { ao_initialize(); } ao_output_ref++; - value = param.GetBlockValue("driver", "default"); + value = block.GetBlockValue("driver", "default"); if (0 == strcmp(value, "default")) driver = ao_default_driver_id(); else @@ -122,45 +123,38 @@ AoOutput::Configure(const config_param ¶m, Error &error) } FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n", - ai->short_name, param.GetBlockValue("name", nullptr)); + ai->short_name, block.GetBlockValue("name", nullptr)); - value = param.GetBlockValue("options", nullptr); + value = block.GetBlockValue("options", nullptr); if (value != nullptr) { - gchar **_options = g_strsplit(value, ";", 0); + for (const auto &i : SplitString(value, ';')) { + const DivideString ss(i.c_str(), '=', true); - for (unsigned i = 0; _options[i] != nullptr; ++i) { - gchar **key_value = g_strsplit(_options[i], "=", 2); - - if (key_value[0] == nullptr || key_value[1] == nullptr) { + if (!ss.IsDefined()) { error.Format(ao_output_domain, "problems parsing options \"%s\"", - _options[i]); + i.c_str()); return false; } - ao_append_option(&options, key_value[0], - key_value[1]); - - g_strfreev(key_value); + ao_append_option(&options, ss.GetFirst(), ss.GetSecond()); } - - g_strfreev(_options); } return true; } static AudioOutput * -ao_output_init(const config_param ¶m, Error &error) +ao_output_init(const ConfigBlock &block, Error &error) { AoOutput *ad = new AoOutput(); - if (!ad->Initialize(param, error)) { + if (!ad->Initialize(block, error)) { delete ad; return nullptr; } - if (!ad->Configure(param, error)) { + if (!ad->Configure(block, error)) { delete ad; return nullptr; } diff --git a/src/output/plugins/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx index 07c2ba16b..582070c47 100644 --- a/src/output/plugins/AoOutputPlugin.hxx +++ b/src/output/plugins/AoOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx index 9df5a74dd..d4019df53 100644 --- a/src/output/plugins/FifoOutputPlugin.cxx +++ b/src/output/plugins/FifoOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,9 +21,11 @@ #include "FifoOutputPlugin.hxx" #include "config/ConfigError.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "../Timer.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" +#include "fs/FileInfo.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -33,9 +35,9 @@ #include <errno.h> #include <unistd.h> -#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ +class FifoOutput { + friend struct AudioOutputWrapper<FifoOutput>; -struct FifoOutput { AudioOutput base; AllocatedPath path; @@ -46,21 +48,35 @@ struct FifoOutput { bool created; Timer *timer; +public: FifoOutput() :base(fifo_output_plugin), path(AllocatedPath::Null()), input(-1), output(-1), created(false) {} - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); + ~FifoOutput() { + CloseFifo(); } + bool Initialize(const ConfigBlock &block, Error &error) { + return base.Configure(block, error); + } + + static FifoOutput *Create(const ConfigBlock &block, Error &error); + bool Create(Error &error); bool Check(Error &error); void Delete(); - bool Open(Error &error); + bool OpenFifo(Error &error); + void CloseFifo(); + + bool Open(AudioFormat &audio_format, Error &error); void Close(); + + unsigned Delay() const; + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); }; static constexpr Domain fifo_output_domain("fifo_output"); @@ -82,7 +98,7 @@ FifoOutput::Delete() } void -FifoOutput::Close() +FifoOutput::CloseFifo() { if (input >= 0) { close(input); @@ -94,8 +110,8 @@ FifoOutput::Close() output = -1; } - struct stat st; - if (created && StatFile(path, st)) + FileInfo fi; + if (created && GetFileInfo(path, fi)) Delete(); } @@ -138,7 +154,7 @@ FifoOutput::Check(Error &error) } inline bool -FifoOutput::Open(Error &error) +FifoOutput::OpenFifo(Error &error) { if (!Check(error)) return false; @@ -147,7 +163,7 @@ FifoOutput::Open(Error &error) if (input < 0) { error.FormatErrno("Could not open FIFO \"%s\" for reading", path_utf8.c_str()); - Close(); + CloseFifo(); return false; } @@ -155,25 +171,19 @@ FifoOutput::Open(Error &error) if (output < 0) { error.FormatErrno("Could not open FIFO \"%s\" for writing", path_utf8.c_str()); - Close(); + CloseFifo(); return false; } return true; } -static bool -fifo_open(FifoOutput *fd, Error &error) -{ - return fd->Open(error); -} - -static AudioOutput * -fifo_output_init(const config_param ¶m, Error &error) +inline FifoOutput * +FifoOutput::Create(const ConfigBlock &block, Error &error) { FifoOutput *fd = new FifoOutput(); - fd->path = param.GetBlockPath("path", error); + fd->path = block.GetBlockPath("path", error); if (fd->path.IsNull()) { delete fd; @@ -185,89 +195,67 @@ fifo_output_init(const config_param ¶m, Error &error) fd->path_utf8 = fd->path.ToUTF8(); - if (!fd->Initialize(param, error)) { + if (!fd->Initialize(block, error)) { delete fd; return nullptr; } - if (!fifo_open(fd, error)) { + if (!fd->OpenFifo(error)) { delete fd; return nullptr; } - return &fd->base; -} - -static void -fifo_output_finish(AudioOutput *ao) -{ - FifoOutput *fd = (FifoOutput *)ao; - - fd->Close(); - delete fd; + return fd; } -static bool -fifo_output_open(AudioOutput *ao, AudioFormat &audio_format, - gcc_unused Error &error) +bool +FifoOutput::Open(AudioFormat &audio_format, gcc_unused Error &error) { - FifoOutput *fd = (FifoOutput *)ao; - - fd->timer = new Timer(audio_format); - + timer = new Timer(audio_format); return true; } -static void -fifo_output_close(AudioOutput *ao) +void +FifoOutput::Close() { - FifoOutput *fd = (FifoOutput *)ao; - - delete fd->timer; + delete timer; } -static void -fifo_output_cancel(AudioOutput *ao) +inline void +FifoOutput::Cancel() { - FifoOutput *fd = (FifoOutput *)ao; - char buf[FIFO_BUFFER_SIZE]; - int bytes = 1; - - fd->timer->Reset(); + timer->Reset(); - while (bytes > 0 && errno != EINTR) - bytes = read(fd->input, buf, FIFO_BUFFER_SIZE); + ssize_t bytes; + do { + char buffer[16384]; + bytes = read(input, buffer, sizeof(buffer)); + } while (bytes > 0 && errno != EINTR); if (bytes < 0 && errno != EAGAIN) { FormatErrno(fifo_output_domain, "Flush of FIFO \"%s\" failed", - fd->path_utf8.c_str()); + path_utf8.c_str()); } } -static unsigned -fifo_output_delay(AudioOutput *ao) +inline unsigned +FifoOutput::Delay() const { - FifoOutput *fd = (FifoOutput *)ao; - - return fd->timer->IsStarted() - ? fd->timer->GetDelay() + return timer->IsStarted() + ? timer->GetDelay() : 0; } -static size_t -fifo_output_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +FifoOutput::Play(const void *chunk, size_t size, Error &error) { - FifoOutput *fd = (FifoOutput *)ao; - ssize_t bytes; - - if (!fd->timer->IsStarted()) - fd->timer->Start(); - fd->timer->Add(size); + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); while (true) { - bytes = write(fd->output, chunk, size); + ssize_t bytes = write(output, chunk, size); if (bytes > 0) return (size_t)bytes; @@ -275,33 +263,35 @@ fifo_output_play(AudioOutput *ao, const void *chunk, size_t size, switch (errno) { case EAGAIN: /* The pipe is full, so empty it */ - fifo_output_cancel(&fd->base); + Cancel(); continue; case EINTR: continue; } error.FormatErrno("Failed to write to FIFO %s", - fd->path_utf8.c_str()); + path_utf8.c_str()); return 0; } } } +typedef AudioOutputWrapper<FifoOutput> Wrapper; + const struct AudioOutputPlugin fifo_output_plugin = { "fifo", nullptr, - fifo_output_init, - fifo_output_finish, + &Wrapper::Init, + &Wrapper::Finish, nullptr, nullptr, - fifo_output_open, - fifo_output_close, - fifo_output_delay, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - fifo_output_play, + &Wrapper::Play, nullptr, - fifo_output_cancel, + &Wrapper::Cancel, nullptr, nullptr, }; diff --git a/src/output/plugins/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx index f41ceded6..353be51a6 100644 --- a/src/output/plugins/FifoOutputPlugin.hxx +++ b/src/output/plugins/FifoOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/HaikuOutputPlugin.cxx b/src/output/plugins/HaikuOutputPlugin.cxx new file mode 100644 index 000000000..e235d595a --- /dev/null +++ b/src/output/plugins/HaikuOutputPlugin.cxx @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2003-2015 The Music Player Daemon Project + * http://www.musicpd.org + * Copyright (C) 2014-2015 François 'mmu_man' Revol + * + * 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 "HaikuOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "../Wrapper.hxx" +#include "mixer/MixerList.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <AppFileInfo.h> +#include <Application.h> +#include <Bitmap.h> +#include <IconUtils.h> +#include <MediaDefs.h> +#include <MediaRoster.h> +#include <Notification.h> +#include <OS.h> +#include <Resources.h> +#include <StringList.h> +#include <SoundPlayer.h> + +#include <string.h> + +#define UTF8_PLAY "\xE2\x96\xB6" + +class HaikuOutput { + friend struct AudioOutputWrapper<HaikuOutput>; + friend int haiku_output_get_volume(HaikuOutput &haiku); + friend bool haiku_output_set_volume(HaikuOutput &haiku, unsigned volume); + + AudioOutput base; + + size_t write_size; + + media_raw_audio_format* format; + BSoundPlayer* sound_player; + + sem_id new_buffer; + sem_id buffer_done; + + uint8* buffer; + size_t buffer_size; + size_t buffer_filled; + + unsigned buffer_delay; + +public: + HaikuOutput() + :base(haiku_output_plugin) {} + ~HaikuOutput(); + + bool Initialize(const ConfigBlock &block, Error &error) { + return base.Configure(block, error); + } + + static HaikuOutput *Create(const ConfigBlock &block, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + + void Close() { + DoClose(); + } + + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); + + bool Configure(const ConfigBlock &block, Error &error); + + size_t Delay(); + + void FillBuffer(void* _buffer, size_t size, + gcc_unused const media_raw_audio_format& _format); + + void SendTag(const Tag &tag); + +private: + + void DoClose(); +}; + +static constexpr Domain haiku_output_domain("haiku_output"); + +static void +haiku_output_error(Error &error_r, status_t err) +{ + const char *error = strerror(err); + error_r.Set(haiku_output_domain, err, error); +} + +static void +initialize_application() +{ + // required to send the notification with a bitmap + // TODO: actually Run() it and handle B_QUIT_REQUESTED + // TODO: use some locking? + if (be_app == NULL) { + FormatDebug(haiku_output_domain, "creating be_app\n"); + new BApplication("application/x-vnd.MusicPD"); + } +} + +static void +finalize_application() +{ + // TODO: use some locking? + delete be_app; + be_app = NULL; + FormatDebug(haiku_output_domain, "deleting be_app\n"); +} + +inline bool +HaikuOutput::Configure(const ConfigBlock &block, Error &error) +{ + /* XXX: by default we should let the MediaKit propose the buffer size */ + write_size = block.GetBlockValue("write_size", 4096u); + + format = (media_raw_audio_format*)malloc( + sizeof(media_raw_audio_format)); + if (format == nullptr) { + haiku_output_error(error, B_NO_MEMORY); + return false; + } + + return true; +} + +static bool +haiku_test_default_device(void) +{ + BSoundPlayer testPlayer; + return testPlayer.InitCheck() == B_OK; + +} + +inline HaikuOutput * +HaikuOutput::Create(const ConfigBlock &block, Error &error) +{ + initialize_application(); + + HaikuOutput *ad = new HaikuOutput(); + + if (!ad->Initialize(block, error)) { + delete ad; + return nullptr; + } + + if (!ad->Configure(block, error)) { + delete ad; + return nullptr; + } + + return ad; +} + +void +HaikuOutput::DoClose() +{ + sound_player->SetHasData(false); + delete_sem(new_buffer); + delete_sem(buffer_done); + sound_player->Stop(); + delete sound_player; + sound_player = nullptr; +} + + + +HaikuOutput::~HaikuOutput() +{ + free(format); + delete_sem(new_buffer); + delete_sem(buffer_done); + + finalize_application(); +} + +static void +fill_buffer(void* cookie, void* buffer, size_t size, + const media_raw_audio_format& format) +{ + HaikuOutput *ad = (HaikuOutput *)cookie; + ad->FillBuffer(buffer, size, format); +} + + +void +HaikuOutput::FillBuffer(void* _buffer, size_t size, + gcc_unused const media_raw_audio_format& _format) +{ + + buffer = (uint8*)_buffer; + buffer_size = size; + buffer_filled = 0; + bigtime_t start = system_time(); + release_sem(new_buffer); + acquire_sem(buffer_done); + bigtime_t w = system_time() - start; + + if (w > 5000LL) { + FormatDebug(haiku_output_domain, + "haiku:fill_buffer waited %Ldus\n", w); + } + + if (buffer_filled < buffer_size) { + memset(buffer + buffer_filled, 0, + buffer_size - buffer_filled); + FormatDebug(haiku_output_domain, + "haiku:fill_buffer filled %d size %d clearing remainder\n", + (int)buffer_filled, (int)buffer_size); + + } +} + +inline bool +HaikuOutput::Open(AudioFormat &audio_format, Error &error) +{ + status_t err; + *format = media_multi_audio_format::wildcard; + + switch (audio_format.format) { + case SampleFormat::S8: + format->format = media_raw_audio_format::B_AUDIO_CHAR; + break; + + case SampleFormat::S16: + format->format = media_raw_audio_format::B_AUDIO_SHORT; + break; + + case SampleFormat::S32: + format->format = media_raw_audio_format::B_AUDIO_INT; + break; + + case SampleFormat::FLOAT: + format->format = media_raw_audio_format::B_AUDIO_FLOAT; + break; + + default: + /* fall back to float */ + audio_format.format = SampleFormat::FLOAT; + format->format = media_raw_audio_format::B_AUDIO_FLOAT; + break; + } + + format->frame_rate = audio_format.sample_rate; + format->byte_order = B_MEDIA_HOST_ENDIAN; + format->channel_count = audio_format.channels; + + buffer_size = 0; + + if (write_size) + format->buffer_size = write_size; + else + format->buffer_size = BMediaRoster::Roster()->AudioBufferSizeFor( + format->channel_count, format->format, + format->frame_rate, B_UNKNOWN_BUS) * 2; + + FormatDebug(haiku_output_domain, + "using haiku driver ad: bs: %d ws: %d " + "channels %d rate %f fmt %08lx bs %d\n", + (int)buffer_size, (int)write_size, + (int)format->channel_count, format->frame_rate, + format->format, (int)format->buffer_size); + + sound_player = new BSoundPlayer(format, "MPD Output", + fill_buffer, NULL, this); + + err = sound_player->InitCheck(); + if (err != B_OK) { + delete sound_player; + sound_player = NULL; + haiku_output_error(error, err); + return false; + } + + // calculate the allowable delay for the buffer (ms) + buffer_delay = format->buffer_size; + buffer_delay /= (format->format & + media_raw_audio_format::B_AUDIO_SIZE_MASK); + buffer_delay /= format->channel_count; + buffer_delay *= 1000 / format->frame_rate; + // half of the total buffer play time + buffer_delay /= 2; + FormatDebug(haiku_output_domain, + "buffer delay: %d ms\n", buffer_delay); + + new_buffer = create_sem(0, "New buffer request"); + buffer_done = create_sem(0, "Buffer done"); + + sound_player->SetVolume(1.0); + sound_player->Start(); + sound_player->SetHasData(false); + + return true; +} + +inline size_t +HaikuOutput::Play(const void *chunk, size_t size, gcc_unused Error &error) +{ + BSoundPlayer* const soundPlayer = sound_player; + const uint8 *data = (const uint8 *)chunk; + + if (size == 0) { + soundPlayer->SetHasData(false); + return 0; + } + + if (!soundPlayer->HasData()) + soundPlayer->SetHasData(true); + acquire_sem(new_buffer); + + size_t bytesLeft = size; + while (bytesLeft > 0) { + if (buffer_filled == buffer_size) { + // Request another buffer from BSoundPlayer + release_sem(buffer_done); + acquire_sem(new_buffer); + } + + const size_t copyBytes = std::min(bytesLeft, buffer_size + - buffer_filled); + memcpy(buffer + buffer_filled, data, + copyBytes); + buffer_filled += copyBytes; + data += copyBytes; + bytesLeft -= copyBytes; + } + + + if (buffer_filled < buffer_size) { + // Continue filling this buffer the next time this function is called + release_sem(new_buffer); + } else { + // Buffer is full + release_sem(buffer_done); + //soundPlayer->SetHasData(false); + } + + return size; +} + +inline size_t +HaikuOutput::Delay() +{ + unsigned delay = buffer_filled ? 0 : buffer_delay; + + //FormatDebug(haiku_output_domain, + // "delay=%d\n", delay / 2); + // XXX: doesn't work + //return (delay / 2) ? 1 : 0; + (void)delay; + + return 0; +} + +inline void +HaikuOutput::SendTag(const Tag &tag) +{ + status_t err; + + /* lazily initialized */ + static BBitmap *icon = NULL; + + if (icon == NULL) { + BAppFileInfo info; + BResources resources; + err = resources.SetToImage((const void *)&HaikuOutput::SendTag); + BFile file(resources.File()); + err = info.SetTo(&file); + icon = new BBitmap(BRect(0, 0, (float)B_LARGE_ICON - 1, + (float)B_LARGE_ICON - 1), B_BITMAP_NO_SERVER_LINK, B_RGBA32); + err = info.GetIcon(icon, B_LARGE_ICON); + if (err != B_OK) { + delete icon; + icon = NULL; + } + } + + BNotification notification(B_INFORMATION_NOTIFICATION); + + BString messageId("mpd_"); + messageId << find_thread(NULL); + notification.SetMessageID(messageId); + + notification.SetGroup("Music Player Daemon"); + + char timebuf[16]; + unsigned seconds = 0; + if (!tag.duration.IsNegative()) { + seconds = tag.duration.ToS(); + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + seconds / 3600, (seconds % 3600) / 60, seconds % 60); + } + + BString artist; + BString album; + BString title; + BString track; + BString name; + + for (const auto &item : tag) + { + switch (item.type) { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + if (artist.Length() == 0) + artist << item.value; + break; + case TAG_ALBUM: + if (album.Length() == 0) + album << item.value; + break; + case TAG_TITLE: + if (title.Length() == 0) + title << item.value; + break; + case TAG_TRACK: + if (track.Length() == 0) + track << item.value; + break; + case TAG_NAME: + if (name.Length() == 0) + name << item.value; + break; + case TAG_GENRE: + case TAG_DATE: + case TAG_PERFORMER: + case TAG_COMMENT: + case TAG_DISC: + case TAG_COMPOSER: + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + default: + FormatDebug(haiku_output_domain, + "tag item: type %d value '%s'\n", item.type, item.value); + break; + } + } + + notification.SetTitle(UTF8_PLAY " Now Playing:"); + + BStringList content; + if (name.Length()) + content.Add(name); + if (artist.Length()) + content.Add(artist); + if (album.Length()) + content.Add(album); + if (track.Length()) + content.Add(track); + if (title.Length()) + content.Add(title); + + if (content.CountStrings() == 0) + content.Add("(Unknown)"); + + BString full = content.Join(" " B_UTF8_BULLET " "); + + if (seconds > 0) + full << " (" << timebuf << ")"; + + notification.SetContent(full); + + err = notification.SetIcon(icon); + + notification.Send(); +} + +int +haiku_output_get_volume(HaikuOutput &haiku) +{ + BSoundPlayer* const soundPlayer = haiku.sound_player; + + if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK) + return 0; + + return (int)(soundPlayer->Volume() * 100 + 0.5); +} + +bool +haiku_output_set_volume(HaikuOutput &haiku, unsigned volume) +{ + BSoundPlayer* const soundPlayer = haiku.sound_player; + + if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK) + return false; + + soundPlayer->SetVolume((float)volume / 100); + return true; +} + +typedef AudioOutputWrapper<HaikuOutput> Wrapper; + +const struct AudioOutputPlugin haiku_output_plugin = { + "haiku", + haiku_test_default_device, + &Wrapper::Init, + &Wrapper::Finish, + nullptr, + nullptr, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, + &Wrapper::SendTag, + &Wrapper::Play, + nullptr, + nullptr, + nullptr, + + &haiku_mixer_plugin, +}; diff --git a/src/output/plugins/HaikuOutputPlugin.hxx b/src/output/plugins/HaikuOutputPlugin.hxx new file mode 100644 index 000000000..fb85d4b83 --- /dev/null +++ b/src/output/plugins/HaikuOutputPlugin.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2015 The Music Player Daemon Project + * http://www.musicpd.org + * Copyright (C) 2014-2015 François 'mmu_man' Revol + * + * 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_HAIKU_OUTPUT_PLUGIN_HXX +#define MPD_HAIKU_OUTPUT_PLUGIN_HXX + +class HaikuOutput; + +extern const struct AudioOutputPlugin haiku_output_plugin; + +int +haiku_output_get_volume(HaikuOutput &haiku); + +bool +haiku_output_set_volume(HaikuOutput &haiku, unsigned volume); + +#endif diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index e1dad7893..23843ab5e 100644 --- a/src/output/plugins/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,26 +20,27 @@ #include "config.h" #include "JackOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "config/ConfigError.hxx" +#include "util/ConstBuffer.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include <assert.h> -#include <glib.h> #include <jack/jack.h> #include <jack/types.h> #include <jack/ringbuffer.h> +#include <unistd.h> /* for usleep() */ #include <stdlib.h> #include <string.h> -enum { - MAX_PORTS = 16, -}; +static constexpr unsigned MAX_PORTS = 16; -static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); +static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t); struct JackOutput { AudioOutput base; @@ -55,10 +56,10 @@ struct JackOutput { /* configuration */ - char *source_ports[MAX_PORTS]; + std::string source_ports[MAX_PORTS]; unsigned num_source_ports; - char *destination_ports[MAX_PORTS]; + std::string destination_ports[MAX_PORTS]; unsigned num_destination_ports; size_t ringbuffer_size; @@ -82,24 +83,65 @@ struct JackOutput { JackOutput() :base(jack_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + bool Configure(const ConfigBlock &block, Error &error); + + bool Connect(Error &error); + + /** + * Disconnect the JACK client. + */ + void Disconnect(); + + void Shutdown() { + shutdown = true; + } + + bool Enable(Error &error); + void Disable(); + + bool Open(AudioFormat &new_audio_format, Error &error); + + void Close() { + Stop(); + } + + bool Start(Error &error); + void Stop(); + + /** + * Determine the number of frames guaranteed to be available + * on all channels. + */ + gcc_pure + jack_nframes_t GetAvailable() const; + + void Process(jack_nframes_t nframes); + + /** + * @return the number of frames that were written + */ + size_t WriteSamples(const float *src, size_t n_frames); + + unsigned Delay() const { + return base.pause && pause && !shutdown + ? 1000 + : 0; } + + size_t Play(const void *chunk, size_t size, Error &error); + + bool Pause(); }; static constexpr Domain jack_output_domain("jack_output"); -/** - * Determine the number of frames guaranteed to be available on all - * channels. - */ -static jack_nframes_t -mpd_jack_available(const JackOutput *jd) +inline jack_nframes_t +JackOutput::GetAvailable() const { - size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); + size_t min = jack_ringbuffer_read_space(ringbuffer[0]); - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); + for (unsigned i = 1; i < audio_format.channels; ++i) { + size_t current = jack_ringbuffer_read_space(ringbuffer[i]); if (current < min) min = current; } @@ -109,85 +151,121 @@ mpd_jack_available(const JackOutput *jd) return min / jack_sample_size; } -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) +/** + * Call jack_ringbuffer_read_advance() on all buffers in the list. + */ +static void +MultiReadAdvance(ConstBuffer<jack_ringbuffer_t *> buffers, + size_t size) { - JackOutput *jd = (JackOutput *) arg; + for (auto *i : buffers) + jack_ringbuffer_read_advance(i, size); +} +/** + * Write a specific amount of "silence" to the given port. + */ +static void +WriteSilence(jack_port_t &port, jack_nframes_t nframes) +{ + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(&port, nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is invoked + anyway, but unable to get a buffer */ + return; + + std::fill_n(out, nframes, 0.0); +} + +/** + * Write a specific amount of "silence" to all ports in the list. + */ +static void +MultiWriteSilence(ConstBuffer<jack_port_t *> ports, jack_nframes_t nframes) +{ + for (auto *i : ports) + WriteSilence(*i, nframes); +} + +/** + * Copy data from the buffer to the port. If the buffer underruns, + * fill with silence. + */ +static void +Copy(jack_port_t &dest, jack_nframes_t nframes, + jack_ringbuffer_t &src, jack_nframes_t available) +{ + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(&dest, nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + return; + + /* copy from buffer to port */ + jack_ringbuffer_read(&src, (char *)out, + available * jack_sample_size); + + /* ringbuffer underrun, fill with silence */ + std::fill(out + available, out + nframes, 0.0); +} + +inline void +JackOutput::Process(jack_nframes_t nframes) +{ if (nframes <= 0) - return 0; + return; - if (jd->pause) { + jack_nframes_t available = GetAvailable(); + + const unsigned n_channels = audio_format.channels; + + if (pause) { /* empty the ring buffers */ - const jack_nframes_t available = mpd_jack_available(jd); - for (unsigned i = 0; i < jd->audio_format.channels; ++i) - jack_ringbuffer_read_advance(jd->ringbuffer[i], - available * jack_sample_size); + MultiReadAdvance({ringbuffer, n_channels}, + available * jack_sample_size); /* generate silence while MPD is paused */ - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); + MultiWriteSilence({ports, n_channels}, nframes); - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } - - return 0; + return; } - jack_nframes_t available = mpd_jack_available(jd); if (available > nframes) available = nframes; - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); - if (out == nullptr) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * jack_sample_size); - - for (jack_nframes_t f = available; f < nframes; ++f) - /* ringbuffer underrun, fill with silence */ - out[f] = 0.0; - } + for (unsigned i = 0; i < n_channels; ++i) + Copy(*ports[i], nframes, *ringbuffer[i], available); /* generate silence for the unused source ports */ - for (unsigned i = jd->audio_format.channels; - i < jd->num_source_ports; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); - if (out == nullptr) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } + MultiWriteSilence({ports + n_channels, num_source_ports - n_channels}, + nframes); +} + +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + JackOutput &jo = *(JackOutput *) arg; + jo.Process(nframes); return 0; } static void mpd_jack_shutdown(void *arg) { - JackOutput *jd = (JackOutput *) arg; - jd->shutdown = true; + JackOutput &jo = *(JackOutput *) arg; + + jo.Shutdown(); } static void @@ -200,9 +278,10 @@ set_audioformat(JackOutput *jd, AudioFormat &audio_format) else if (audio_format.channels > jd->num_source_ports) audio_format.channels = 2; - if (audio_format.format != SampleFormat::S16 && - audio_format.format != SampleFormat::S24_P32) - audio_format.format = SampleFormat::S24_P32; + /* JACK uses 32 bit float in the range [-1 .. 1] - just like + MPD's SampleFormat::FLOAT*/ + static_assert(jack_sample_size == sizeof(float), "Expected float32"); + audio_format.format = SampleFormat::FLOAT; } static void @@ -219,55 +298,47 @@ mpd_jack_info(const char *msg) } #endif -/** - * Disconnect the JACK client. - */ -static void -mpd_jack_disconnect(JackOutput *jd) +void +JackOutput::Disconnect() { - assert(jd != nullptr); - assert(jd->client != nullptr); + assert(client != nullptr); - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = nullptr; + jack_deactivate(client); + jack_client_close(client); + client = nullptr; } /** * Connect the JACK client and performs some basic setup * (e.g. register callbacks). */ -static bool -mpd_jack_connect(JackOutput *jd, Error &error) +bool +JackOutput::Connect(Error &error) { - jack_status_t status; - - assert(jd != nullptr); - - jd->shutdown = false; + shutdown = false; - jd->client = jack_client_open(jd->name, jd->options, &status, - jd->server_name); - if (jd->client == nullptr) { + jack_status_t status; + client = jack_client_open(name, options, &status, server_name); + if (client == nullptr) { error.Format(jack_output_domain, status, "Failed to connect to JACK server, status=%d", status); return false; } - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + jack_set_process_callback(client, mpd_jack_process, this); + jack_on_shutdown(client, mpd_jack_shutdown, this); - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - jd->ports[i] = jack_port_register(jd->client, - jd->source_ports[i], - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - if (jd->ports[i] == nullptr) { + for (unsigned i = 0; i < num_source_ports; ++i) { + ports[i] = jack_port_register(client, + source_ports[i].c_str(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (ports[i] == nullptr) { error.Format(jack_output_domain, "Cannot register output port \"%s\"", - jd->source_ports[i]); - mpd_jack_disconnect(jd); + source_ports[i].c_str()); + Disconnect(); return false; } } @@ -282,23 +353,19 @@ mpd_jack_test_default_device(void) } static unsigned -parse_port_list(const char *source, char **dest, Error &error) +parse_port_list(const char *source, std::string dest[], Error &error) { - char **list = g_strsplit(source, ",", 0); unsigned n = 0; - - for (n = 0; list[n] != nullptr; ++n) { + for (auto &&i : SplitString(source, ',')) { if (n >= MAX_PORTS) { error.Set(config_domain, "too many port names"); return 0; } - dest[n] = list[n]; + dest[n++] = std::move(i); } - g_free(list); - if (n == 0) { error.Format(config_domain, "at least one port name expected"); @@ -308,243 +375,221 @@ parse_port_list(const char *source, char **dest, Error &error) return n; } -static AudioOutput * -mpd_jack_init(const config_param ¶m, Error &error) +bool +JackOutput::Configure(const ConfigBlock &block, Error &error) { - JackOutput *jd = new JackOutput(); - - if (!jd->Initialize(param, error)) { - delete jd; - return nullptr; - } - - const char *value; + if (!base.Configure(block, error)) + return false; - jd->options = JackNullOption; + options = JackNullOption; - jd->name = param.GetBlockValue("client_name", nullptr); - if (jd->name != nullptr) - jd->options = jack_options_t(jd->options | JackUseExactName); + name = block.GetBlockValue("client_name", nullptr); + if (name != nullptr) + options = jack_options_t(options | JackUseExactName); else /* if there's a no configured client name, we don't care about the JackUseExactName option */ - jd->name = "Music Player Daemon"; + name = "Music Player Daemon"; - jd->server_name = param.GetBlockValue("server_name", nullptr); - if (jd->server_name != nullptr) - jd->options = jack_options_t(jd->options | JackServerName); + server_name = block.GetBlockValue("server_name", nullptr); + if (server_name != nullptr) + options = jack_options_t(options | JackServerName); - if (!param.GetBlockValue("autostart", false)) - jd->options = jack_options_t(jd->options | JackNoStartServer); + if (!block.GetBlockValue("autostart", false)) + options = jack_options_t(options | JackNoStartServer); /* configure the source ports */ - value = param.GetBlockValue("source_ports", "left,right"); - jd->num_source_ports = parse_port_list(value, - jd->source_ports, error); - if (jd->num_source_ports == 0) - return nullptr; + const char *value = block.GetBlockValue("source_ports", "left,right"); + num_source_ports = parse_port_list(value, source_ports, error); + if (num_source_ports == 0) + return false; /* configure the destination ports */ - value = param.GetBlockValue("destination_ports", nullptr); + value = block.GetBlockValue("destination_ports", nullptr); if (value == nullptr) { /* compatibility with MPD < 0.16 */ - value = param.GetBlockValue("ports", nullptr); + value = block.GetBlockValue("ports", nullptr); if (value != nullptr) FormatWarning(jack_output_domain, "deprecated option 'ports' in line %d", - param.line); + block.line); } if (value != nullptr) { - jd->num_destination_ports = - parse_port_list(value, - jd->destination_ports, error); - if (jd->num_destination_ports == 0) - return nullptr; + num_destination_ports = + parse_port_list(value, destination_ports, error); + if (num_destination_ports == 0) + return false; } else { - jd->num_destination_ports = 0; + num_destination_ports = 0; } - if (jd->num_destination_ports > 0 && - jd->num_destination_ports != jd->num_source_ports) + if (num_destination_ports > 0 && + num_destination_ports != num_source_ports) FormatWarning(jack_output_domain, "number of source ports (%u) mismatches the " "number of destination ports (%u) in line %d", - jd->num_source_ports, jd->num_destination_ports, - param.line); - - jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); + num_source_ports, num_destination_ports, + block.line); - jack_set_error_function(mpd_jack_error); + ringbuffer_size = block.GetBlockValue("ringbuffer_size", 32768u); -#ifdef HAVE_JACK_SET_INFO_FUNCTION - jack_set_info_function(mpd_jack_info); -#endif - - return &jd->base; + return true; } -static void -mpd_jack_finish(AudioOutput *ao) +inline bool +JackOutput::Enable(Error &error) { - JackOutput *jd = (JackOutput *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - g_free(jd->source_ports[i]); + for (unsigned i = 0; i < num_source_ports; ++i) + ringbuffer[i] = nullptr; - for (unsigned i = 0; i < jd->num_destination_ports; ++i) - g_free(jd->destination_ports[i]); - - delete jd; + return Connect(error); } -static bool -mpd_jack_enable(AudioOutput *ao, Error &error) +inline void +JackOutput::Disable() { - JackOutput *jd = (JackOutput *)ao; + if (client != nullptr) + Disconnect(); - for (unsigned i = 0; i < jd->num_source_ports; ++i) - jd->ringbuffer[i] = nullptr; - - return mpd_jack_connect(jd, error); + for (unsigned i = 0; i < num_source_ports; ++i) { + if (ringbuffer[i] != nullptr) { + jack_ringbuffer_free(ringbuffer[i]); + ringbuffer[i] = nullptr; + } + } } -static void -mpd_jack_disable(AudioOutput *ao) +static AudioOutput * +mpd_jack_init(const ConfigBlock &block, Error &error) { - JackOutput *jd = (JackOutput *)ao; - - if (jd->client != nullptr) - mpd_jack_disconnect(jd); + JackOutput *jd = new JackOutput(); - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] != nullptr) { - jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = nullptr; - } + if (!jd->Configure(block, error)) { + delete jd; + return nullptr; } + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return &jd->base; } /** * Stops the playback on the JACK connection. */ -static void -mpd_jack_stop(JackOutput *jd) +void +JackOutput::Stop() { - assert(jd != nullptr); - - if (jd->client == nullptr) + if (client == nullptr) return; - if (jd->shutdown) + if (shutdown) /* the connection has failed; close it */ - mpd_jack_disconnect(jd); + Disconnect(); else /* the connection is alive: just stop playback */ - jack_deactivate(jd->client); + jack_deactivate(client); } -static bool -mpd_jack_start(JackOutput *jd, Error &error) +inline bool +JackOutput::Start(Error &error) { - const char *destination_ports[MAX_PORTS], **jports; - const char *duplicate_port = nullptr; - unsigned num_destination_ports; - - assert(jd->client != nullptr); - assert(jd->audio_format.channels <= jd->num_source_ports); + assert(client != nullptr); + assert(audio_format.channels <= num_source_ports); /* allocate the ring buffers on the first open(); these persist until MPD exits. It's too unsafe to delete them because we can never know when mpd_jack_process() gets called */ - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] == nullptr) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); + for (unsigned i = 0; i < num_source_ports; ++i) { + if (ringbuffer[i] == nullptr) + ringbuffer[i] = + jack_ringbuffer_create(ringbuffer_size); /* clear the ring buffer to be sure that data from previous playbacks are gone */ - jack_ringbuffer_reset(jd->ringbuffer[i]); + jack_ringbuffer_reset(ringbuffer[i]); } - if ( jack_activate(jd->client) ) { + if ( jack_activate(client) ) { error.Set(jack_output_domain, "cannot activate client"); - mpd_jack_stop(jd); + Stop(); return false; } - if (jd->num_destination_ports == 0) { + const char *dports[MAX_PORTS], **jports; + unsigned num_dports; + if (num_destination_ports == 0) { /* no output ports were configured - ask libjack for defaults */ - jports = jack_get_ports(jd->client, nullptr, nullptr, + jports = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); if (jports == nullptr) { error.Set(jack_output_domain, "no ports found"); - mpd_jack_stop(jd); + Stop(); return false; } assert(*jports != nullptr); - for (num_destination_ports = 0; - num_destination_ports < MAX_PORTS && - jports[num_destination_ports] != nullptr; - ++num_destination_ports) { + for (num_dports = 0; num_dports < MAX_PORTS && + jports[num_dports] != nullptr; + ++num_dports) { FormatDebug(jack_output_domain, "destination_port[%u] = '%s'\n", - num_destination_ports, - jports[num_destination_ports]); - destination_ports[num_destination_ports] = - jports[num_destination_ports]; + num_dports, + jports[num_dports]); + dports[num_dports] = jports[num_dports]; } } else { /* use the configured output ports */ - num_destination_ports = jd->num_destination_ports; - memcpy(destination_ports, jd->destination_ports, - num_destination_ports * sizeof(*destination_ports)); + num_dports = num_destination_ports; + for (unsigned i = 0; i < num_dports; ++i) + dports[i] = destination_ports[i].c_str(); jports = nullptr; } - assert(num_destination_ports > 0); + assert(num_dports > 0); - if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + const char *duplicate_port = nullptr; + if (audio_format.channels >= 2 && num_dports == 1) { /* mix stereo signal on one speaker */ - while (num_destination_ports < jd->audio_format.channels) - destination_ports[num_destination_ports++] = - destination_ports[0]; - } else if (num_destination_ports > jd->audio_format.channels) { - if (jd->audio_format.channels == 1 && num_destination_ports > 2) { + std::fill(dports + num_dports, dports + audio_format.channels, + dports[0]); + } else if (num_dports > audio_format.channels) { + if (audio_format.channels == 1 && num_dports > 2) { /* mono input file: connect the one source channel to the both destination channels */ - duplicate_port = destination_ports[1]; - num_destination_ports = 1; + duplicate_port = dports[1]; + num_dports = 1; } else /* connect only as many ports as we need */ - num_destination_ports = jd->audio_format.channels; + num_dports = audio_format.channels; } - assert(num_destination_ports <= jd->num_source_ports); - - for (unsigned i = 0; i < num_destination_ports; ++i) { - int ret; + assert(num_dports <= num_source_ports); - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - destination_ports[i]); + for (unsigned i = 0; i < num_dports; ++i) { + int ret = jack_connect(client, jack_port_name(ports[i]), + dports[i]); if (ret != 0) { error.Format(jack_output_domain, - "Not a valid JACK port: %s", - destination_ports[i]); + "Not a valid JACK port: %s", dports[i]); if (jports != nullptr) free(jports); - mpd_jack_stop(jd); + Stop(); return false; } } @@ -554,7 +599,7 @@ mpd_jack_start(JackOutput *jd, Error &error) the both destination channels */ int ret; - ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), + ret = jack_connect(client, jack_port_name(ports[0]), duplicate_port); if (ret != 0) { error.Format(jack_output_domain, @@ -564,7 +609,7 @@ mpd_jack_start(JackOutput *jd, Error &error) if (jports != nullptr) free(jports); - mpd_jack_stop(jd); + Stop(); return false; } } @@ -575,188 +620,119 @@ mpd_jack_start(JackOutput *jd, Error &error) return true; } -static bool -mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +JackOutput::Open(AudioFormat &new_audio_format, Error &error) { - JackOutput *jd = (JackOutput *)ao; + pause = false; - assert(jd != nullptr); + if (client != nullptr && shutdown) + Disconnect(); - jd->pause = false; - - if (jd->client != nullptr && jd->shutdown) - mpd_jack_disconnect(jd); - - if (jd->client == nullptr && !mpd_jack_connect(jd, error)) + if (client == nullptr && !Connect(error)) return false; - set_audioformat(jd, audio_format); - jd->audio_format = audio_format; + set_audioformat(this, new_audio_format); + audio_format = new_audio_format; - if (!mpd_jack_start(jd, error)) - return false; - - return true; + return Start(error); } -static void -mpd_jack_close(gcc_unused AudioOutput *ao) +inline size_t +JackOutput::WriteSamples(const float *src, size_t n_frames) { - JackOutput *jd = (JackOutput *)ao; + assert(n_frames > 0); - mpd_jack_stop(jd); -} + const unsigned n_channels = audio_format.channels; -static unsigned -mpd_jack_delay(AudioOutput *ao) -{ - JackOutput *jd = (JackOutput *)ao; + float *dest[MAX_CHANNELS]; + size_t space = -1; + for (unsigned i = 0; i < n_channels; ++i) { + jack_ringbuffer_data_t d[2]; + jack_ringbuffer_get_write_vector(ringbuffer[i], d); - return jd->base.pause && jd->pause && !jd->shutdown - ? 1000 - : 0; -} + /* choose the first non-empty writable area */ + const jack_ringbuffer_data_t &e = d[d[0].len == 0]; -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} + if (e.len < space) + /* send data symmetrically */ + space = e.len; -static void -mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - while (num_samples-- > 0) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], - (const char *)&sample, - sizeof(sample)); - } + dest[i] = (float *)e.buf; } -} -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} + space /= jack_sample_size; + if (space == 0) + return 0; -static void -mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - while (num_samples-- > 0) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], - (const char *)&sample, - sizeof(sample)); - } - } -} + const size_t result = n_frames = std::min(space, n_frames); -static void -mpd_jack_write_samples(JackOutput *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.format) { - case SampleFormat::S16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case SampleFormat::S24_P32: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - gcc_unreachable(); - } + while (n_frames-- > 0) + for (unsigned i = 0; i < n_channels; ++i) + *dest[i]++ = *src++; + + const size_t per_channel_advance = result * jack_sample_size; + for (unsigned i = 0; i < n_channels; ++i) + jack_ringbuffer_write_advance(ringbuffer[i], + per_channel_advance); + + return result; } -static size_t -mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +JackOutput::Play(const void *chunk, size_t size, Error &error) { - JackOutput *jd = (JackOutput *)ao; - const size_t frame_size = jd->audio_format.GetFrameSize(); - size_t space = 0, space1; - - jd->pause = false; + pause = false; + const size_t frame_size = audio_format.GetFrameSize(); assert(size % frame_size == 0); size /= frame_size; while (true) { - if (jd->shutdown) { + if (shutdown) { error.Set(jack_output_domain, "Refusing to play, because " "there is no client thread"); return 0; } - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); - if (space > space1) - /* send data symmetrically */ - space = space1; - } - - if (space >= jack_sample_size) - break; + size_t frames_written = + WriteSamples((const float *)chunk, size); + if (frames_written > 0) + return frames_written * frame_size; /* XXX do something more intelligent to synchronize */ - g_usleep(1000); + usleep(1000); } - - space /= jack_sample_size; - if (space < size) - size = space; - - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; } -static bool -mpd_jack_pause(AudioOutput *ao) +inline bool +JackOutput::Pause() { - JackOutput *jd = (JackOutput *)ao; - - if (jd->shutdown) + if (shutdown) return false; - jd->pause = true; + pause = true; return true; } +typedef AudioOutputWrapper<JackOutput> Wrapper; + const struct AudioOutputPlugin jack_output_plugin = { "jack", mpd_jack_test_default_device, mpd_jack_init, - mpd_jack_finish, - mpd_jack_enable, - mpd_jack_disable, - mpd_jack_open, - mpd_jack_close, - mpd_jack_delay, + &Wrapper::Finish, + &Wrapper::Enable, + &Wrapper::Disable, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - mpd_jack_play, + &Wrapper::Play, nullptr, nullptr, - mpd_jack_pause, + &Wrapper::Pause, nullptr, }; diff --git a/src/output/plugins/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx index 6f1f7ecb9..f76431690 100644 --- a/src/output/plugins/JackOutputPlugin.hxx +++ b/src/output/plugins/JackOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx index 098f58926..e1731f0fe 100644 --- a/src/output/plugins/NullOutputPlugin.cxx +++ b/src/output/plugins/NullOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,119 +20,94 @@ #include "config.h" #include "NullOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "../Timer.hxx" -struct NullOutput { +class NullOutput { + friend struct AudioOutputWrapper<NullOutput>; + AudioOutput base; bool sync; Timer *timer; +public: NullOutput() :base(null_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); - } -}; - -static AudioOutput * -null_init(const config_param ¶m, Error &error) -{ - NullOutput *nd = new NullOutput(); - - if (!nd->Initialize(param, error)) { - delete nd; - return nullptr; + bool Initialize(const ConfigBlock &block, Error &error) { + return base.Configure(block, error); } - nd->sync = param.GetBlockValue("sync", true); + static NullOutput *Create(const ConfigBlock &block, Error &error); - return &nd->base; -} + bool Open(AudioFormat &audio_format, gcc_unused Error &error) { + if (sync) + timer = new Timer(audio_format); -static void -null_finish(AudioOutput *ao) -{ - NullOutput *nd = (NullOutput *)ao; - - delete nd; -} - -static bool -null_open(AudioOutput *ao, AudioFormat &audio_format, - gcc_unused Error &error) -{ - NullOutput *nd = (NullOutput *)ao; - - if (nd->sync) - nd->timer = new Timer(audio_format); + return true; + } - return true; -} + void Close() { + if (sync) + delete timer; + } -static void -null_close(AudioOutput *ao) -{ - NullOutput *nd = (NullOutput *)ao; + unsigned Delay() const { + return sync && timer->IsStarted() + ? timer->GetDelay() + : 0; + } - if (nd->sync) - delete nd->timer; -} + size_t Play(gcc_unused const void *chunk, size_t size, + gcc_unused Error &error) { + if (sync) { + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); + } -static unsigned -null_delay(AudioOutput *ao) -{ - NullOutput *nd = (NullOutput *)ao; + return size; + } - return nd->sync && nd->timer->IsStarted() - ? nd->timer->GetDelay() - : 0; -} + void Cancel() { + if (sync) + timer->Reset(); + } +}; -static size_t -null_play(AudioOutput *ao, gcc_unused const void *chunk, size_t size, - gcc_unused Error &error) +inline NullOutput * +NullOutput::Create(const ConfigBlock &block, Error &error) { - NullOutput *nd = (NullOutput *)ao; - Timer *timer = nd->timer; + NullOutput *nd = new NullOutput(); - if (!nd->sync) - return size; + if (!nd->Initialize(block, error)) { + delete nd; + return nullptr; + } - if (!timer->IsStarted()) - timer->Start(); - timer->Add(size); + nd->sync = block.GetBlockValue("sync", true); - return size; + return nd; } -static void -null_cancel(AudioOutput *ao) -{ - NullOutput *nd = (NullOutput *)ao; - - if (!nd->sync) - return; - - nd->timer->Reset(); -} +typedef AudioOutputWrapper<NullOutput> Wrapper; const struct AudioOutputPlugin null_output_plugin = { "null", nullptr, - null_init, - null_finish, + &Wrapper::Init, + &Wrapper::Finish, nullptr, nullptr, - null_open, - null_close, - null_delay, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - null_play, + &Wrapper::Play, nullptr, - null_cancel, + &Wrapper::Cancel, nullptr, nullptr, }; diff --git a/src/output/plugins/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx index f25f5b9f3..9a1d1558b 100644 --- a/src/output/plugins/NullOutputPlugin.hxx +++ b/src/output/plugins/NullOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx index 13ac7b35e..16c042ba3 100644 --- a/src/output/plugins/OSXOutputPlugin.cxx +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -61,17 +61,17 @@ osx_output_test_default_device(void) } static void -osx_output_configure(OSXOutput *oo, const config_param ¶m) +osx_output_configure(OSXOutput *oo, const ConfigBlock &block) { - const char *device = param.GetBlockValue("device"); + const char *device = block.GetBlockValue("device"); - if (device == NULL || 0 == strcmp(device, "default")) { + if (device == nullptr || 0 == strcmp(device, "default")) { oo->component_subtype = kAudioUnitSubType_DefaultOutput; - oo->device_name = NULL; + oo->device_name = nullptr; } else if (0 == strcmp(device, "system")) { oo->component_subtype = kAudioUnitSubType_SystemOutput; - oo->device_name = NULL; + oo->device_name = nullptr; } else { oo->component_subtype = kAudioUnitSubType_HALOutput; @@ -81,15 +81,15 @@ osx_output_configure(OSXOutput *oo, const config_param ¶m) } static AudioOutput * -osx_output_init(const config_param ¶m, Error &error) +osx_output_init(const ConfigBlock &block, Error &error) { OSXOutput *oo = new OSXOutput(); - if (!oo->base.Configure(param, error)) { + if (!oo->base.Configure(block, error)) { delete oo; - return NULL; + return nullptr; } - osx_output_configure(oo, param); + osx_output_configure(oo, block); return &oo->base; } @@ -108,7 +108,7 @@ osx_output_set_device(OSXOutput *oo, Error &error) bool ret = true; OSStatus status; UInt32 size, numdevices; - AudioDeviceID *deviceids = NULL; + AudioDeviceID *deviceids = nullptr; char name[256]; unsigned int i; @@ -118,7 +118,7 @@ osx_output_set_device(OSXOutput *oo, Error &error) /* how many audio devices are there? */ status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, - NULL); + nullptr); if (status != noErr) { error.Format(osx_output_domain, status, "Unable to determine number of OS X audio devices: %s", @@ -206,7 +206,7 @@ osx_render(void *vdata, AudioBuffer *buffer = &buffer_list->mBuffers[0]; size_t buffer_size = buffer->mDataByteSize; - assert(od->buffer != NULL); + assert(od->buffer != nullptr); od->mutex.lock(); @@ -245,7 +245,7 @@ osx_output_enable(AudioOutput *ao, Error &error) desc.componentFlags = 0; desc.componentFlagsMask = 0; - Component comp = FindNextComponent(NULL, &desc); + Component comp = FindNextComponent(nullptr, &desc); if (comp == 0) { error.Set(osx_output_domain, "Error finding OS X component"); diff --git a/src/output/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx index d7aed40b6..89cd3fe91 100644 --- a/src/output/plugins/OSXOutputPlugin.hxx +++ b/src/output/plugins/OSXOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx index 2f095c0a4..eb55c6e9b 100644 --- a/src/output/plugins/OpenALOutputPlugin.cxx +++ b/src/output/plugins/OpenALOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "OpenALOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -33,10 +34,12 @@ #include <OpenAL/alc.h> #endif -/* should be enough for buffer size = 2048 */ -#define NUM_BUFFERS 16 +class OpenALOutput { + friend struct AudioOutputWrapper<OpenALOutput>; + + /* should be enough for buffer size = 2048 */ + static constexpr unsigned NUM_BUFFERS = 16; -struct OpenALOutput { AudioOutput base; const char *device_name; @@ -51,9 +54,47 @@ struct OpenALOutput { OpenALOutput() :base(openal_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + bool Configure(const ConfigBlock &block, Error &error); + + static OpenALOutput *Create(const ConfigBlock &block, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + + void Close(); + + gcc_pure + unsigned Delay() const { + return filled < NUM_BUFFERS || HasProcessed() + ? 0 + /* we don't know exactly how long we must wait + for the next buffer to finish, so this is a + random guess: */ + : 50; + } + + size_t Play(const void *chunk, size_t size, Error &error); + + void Cancel(); + +private: + gcc_pure + ALint GetSourceI(ALenum param) const { + ALint value; + alGetSourcei(source, param, &value); + return value; + } + + gcc_pure + bool HasProcessed() const { + return GetSourceI(AL_BUFFERS_PROCESSED) > 0; } + + gcc_pure + bool IsPlaying() const { + return GetSourceI(AL_SOURCE_STATE) == AL_PLAYING; + } + + bool SetupContext(Error &error); }; static constexpr Domain openal_output_domain("openal_output"); @@ -83,200 +124,154 @@ openal_audio_format(AudioFormat &audio_format) } } -gcc_pure -static inline ALint -openal_get_source_i(const OpenALOutput *od, ALenum param) -{ - ALint value; - alGetSourcei(od->source, param, &value); - return value; -} - -gcc_pure -static inline bool -openal_has_processed(const OpenALOutput *od) -{ - return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; -} - -gcc_pure -static inline ALint -openal_is_playing(const OpenALOutput *od) -{ - return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; -} - -static bool -openal_setup_context(OpenALOutput *od, Error &error) +inline bool +OpenALOutput::SetupContext(Error &error) { - od->device = alcOpenDevice(od->device_name); + device = alcOpenDevice(device_name); - if (od->device == nullptr) { + if (device == nullptr) { error.Format(openal_output_domain, "Error opening OpenAL device \"%s\"", - od->device_name); + device_name); return false; } - od->context = alcCreateContext(od->device, nullptr); + context = alcCreateContext(device, nullptr); - if (od->context == nullptr) { + if (context == nullptr) { error.Format(openal_output_domain, "Error creating context for \"%s\"", - od->device_name); - alcCloseDevice(od->device); + device_name); + alcCloseDevice(device); return false; } return true; } -static AudioOutput * -openal_init(const config_param ¶m, Error &error) +inline bool +OpenALOutput::Configure(const ConfigBlock &block, Error &error) { - const char *device_name = param.GetBlockValue("device"); - if (device_name == nullptr) { - device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); - } - - OpenALOutput *od = new OpenALOutput(); - if (!od->Initialize(param, error)) { - delete od; - return nullptr; - } + if (!base.Configure(block, error)) + return false; - od->device_name = device_name; + device_name = block.GetBlockValue("device"); + if (device_name == nullptr) + device_name = alcGetString(nullptr, + ALC_DEFAULT_DEVICE_SPECIFIER); - return &od->base; + return true; } -static void -openal_finish(AudioOutput *ao) +inline OpenALOutput * +OpenALOutput::Create(const ConfigBlock &block, Error &error) { - OpenALOutput *od = (OpenALOutput *)ao; + OpenALOutput *oo = new OpenALOutput(); + + if (!oo->Configure(block, error)) { + delete oo; + return nullptr; + } - delete od; + return oo; } -static bool -openal_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +OpenALOutput::Open(AudioFormat &audio_format, Error &error) { - OpenALOutput *od = (OpenALOutput *)ao; + format = openal_audio_format(audio_format); - od->format = openal_audio_format(audio_format); - - if (!openal_setup_context(od, error)) { + if (!SetupContext(error)) return false; - } - alcMakeContextCurrent(od->context); - alGenBuffers(NUM_BUFFERS, od->buffers); + alcMakeContextCurrent(context); + alGenBuffers(NUM_BUFFERS, buffers); if (alGetError() != AL_NO_ERROR) { error.Set(openal_output_domain, "Failed to generate buffers"); return false; } - alGenSources(1, &od->source); + alGenSources(1, &source); if (alGetError() != AL_NO_ERROR) { error.Set(openal_output_domain, "Failed to generate source"); - alDeleteBuffers(NUM_BUFFERS, od->buffers); + alDeleteBuffers(NUM_BUFFERS, buffers); return false; } - od->filled = 0; - od->frequency = audio_format.sample_rate; + filled = 0; + frequency = audio_format.sample_rate; return true; } -static void -openal_close(AudioOutput *ao) +inline void +OpenALOutput::Close() { - OpenALOutput *od = (OpenALOutput *)ao; - - alcMakeContextCurrent(od->context); - alDeleteSources(1, &od->source); - alDeleteBuffers(NUM_BUFFERS, od->buffers); - alcDestroyContext(od->context); - alcCloseDevice(od->device); + alcMakeContextCurrent(context); + alDeleteSources(1, &source); + alDeleteBuffers(NUM_BUFFERS, buffers); + alcDestroyContext(context); + alcCloseDevice(device); } -static unsigned -openal_delay(AudioOutput *ao) +inline size_t +OpenALOutput::Play(const void *chunk, size_t size, gcc_unused Error &error) { - OpenALOutput *od = (OpenALOutput *)ao; - - return od->filled < NUM_BUFFERS || openal_has_processed(od) - ? 0 - /* we don't know exactly how long we must wait for the - next buffer to finish, so this is a random - guess: */ - : 50; -} + if (alcGetCurrentContext() != context) + alcMakeContextCurrent(context); -static size_t -openal_play(AudioOutput *ao, const void *chunk, size_t size, - gcc_unused Error &error) -{ - OpenALOutput *od = (OpenALOutput *)ao; ALuint buffer; - - if (alcGetCurrentContext() != od->context) { - alcMakeContextCurrent(od->context); - } - - if (od->filled < NUM_BUFFERS) { + if (filled < NUM_BUFFERS) { /* fill all buffers */ - buffer = od->buffers[od->filled]; - od->filled++; + buffer = buffers[filled]; + filled++; } else { /* wait for processed buffer */ - while (!openal_has_processed(od)) + while (!HasProcessed()) usleep(10); - alSourceUnqueueBuffers(od->source, 1, &buffer); + alSourceUnqueueBuffers(source, 1, &buffer); } - alBufferData(buffer, od->format, chunk, size, od->frequency); - alSourceQueueBuffers(od->source, 1, &buffer); + alBufferData(buffer, format, chunk, size, frequency); + alSourceQueueBuffers(source, 1, &buffer); - if (!openal_is_playing(od)) - alSourcePlay(od->source); + if (!IsPlaying()) + alSourcePlay(source); return size; } -static void -openal_cancel(AudioOutput *ao) +inline void +OpenALOutput::Cancel() { - OpenALOutput *od = (OpenALOutput *)ao; - - od->filled = 0; - alcMakeContextCurrent(od->context); - alSourceStop(od->source); + filled = 0; + alcMakeContextCurrent(context); + alSourceStop(source); /* force-unqueue all buffers */ - alSourcei(od->source, AL_BUFFER, 0); - od->filled = 0; + alSourcei(source, AL_BUFFER, 0); + filled = 0; } +typedef AudioOutputWrapper<OpenALOutput> Wrapper; + const struct AudioOutputPlugin openal_output_plugin = { "openal", nullptr, - openal_init, - openal_finish, + &Wrapper::Init, + &Wrapper::Finish, nullptr, nullptr, - openal_open, - openal_close, - openal_delay, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - openal_play, + &Wrapper::Play, nullptr, - openal_cancel, + &Wrapper::Cancel, nullptr, nullptr, }; diff --git a/src/output/plugins/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx index a27e6b53c..abf4ffdb1 100644 --- a/src/output/plugins/OpenALOutputPlugin.hxx +++ b/src/output/plugins/OpenALOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx index 39d87fc35..7f75f4e31 100644 --- a/src/output/plugins/OssOutputPlugin.cxx +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "OssOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "mixer/MixerList.hxx" #include "system/fd_util.h" #include "util/ConstBuffer.hxx" @@ -57,7 +58,9 @@ #include "util/Manual.hxx" #endif -struct OssOutput { +class OssOutput { + friend struct AudioOutputWrapper<OssOutput>; + AudioOutput base; #ifdef AFMT_S24_PACKED @@ -79,13 +82,49 @@ struct OssOutput { */ int oss_format; - OssOutput() +public: + OssOutput(const char *_device=nullptr) :base(oss_output_plugin), - fd(-1), device(nullptr) {} + fd(-1), device(_device) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + bool Initialize(const ConfigBlock &block, Error &error_r) { + return base.Configure(block, error_r); } + + static OssOutput *Create(const ConfigBlock &block, Error &error); + +#ifdef AFMT_S24_PACKED + bool Enable(gcc_unused Error &error) { + pcm_export.Construct(); + return true; + } + + void Disable() { + pcm_export.Destruct(); + } +#endif + + bool Open(AudioFormat &audio_format, Error &error); + + void Close() { + DoClose(); + } + + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); + +private: + /** + * Sets up the OSS device which was opened before. + */ + bool Setup(AudioFormat &audio_format, Error &error); + + /** + * Reopen the device with the saved audio_format, without any probing. + */ + bool Reopen(Error &error); + + void DoClose(); }; static constexpr Domain oss_output_domain("oss_output"); @@ -124,7 +163,7 @@ oss_stat_device(const char *device, int *errno_r) return OSS_STAT_NO_ERROR; } -static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; +static const char *const default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; static bool oss_output_test_default_device(void) @@ -147,24 +186,23 @@ oss_output_test_default_device(void) return false; } -static AudioOutput * +static OssOutput * oss_open_default(Error &error) { int err[ARRAY_SIZE(default_devices)]; enum oss_stat ret[ARRAY_SIZE(default_devices)]; - const config_param empty; + const ConfigBlock empty; for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) { ret[i] = oss_stat_device(default_devices[i], &err[i]); if (ret[i] == OSS_STAT_NO_ERROR) { - OssOutput *od = new OssOutput(); + OssOutput *od = new OssOutput(default_devices[i]); if (!od->Initialize(empty, error)) { delete od; - return NULL; + return nullptr; } - od->device = default_devices[i]; - return &od->base; + return od; } } @@ -194,62 +232,33 @@ oss_open_default(Error &error) error.Set(oss_output_domain, "error trying to open default OSS device"); - return NULL; + return nullptr; } -static AudioOutput * -oss_output_init(const config_param ¶m, Error &error) +inline OssOutput * +OssOutput::Create(const ConfigBlock &block, Error &error) { - const char *device = param.GetBlockValue("device"); - if (device != NULL) { + const char *device = block.GetBlockValue("device"); + if (device != nullptr) { OssOutput *od = new OssOutput(); - if (!od->Initialize(param, error)) { + if (!od->Initialize(block, error)) { delete od; - return NULL; + return nullptr; } od->device = device; - return &od->base; + return od; } return oss_open_default(error); } -static void -oss_output_finish(AudioOutput *ao) +void +OssOutput::DoClose() { - OssOutput *od = (OssOutput *)ao; - - delete od; -} - -#ifdef AFMT_S24_PACKED - -static bool -oss_output_enable(AudioOutput *ao, gcc_unused Error &error) -{ - OssOutput *od = (OssOutput *)ao; - - od->pcm_export.Construct(); - return true; -} - -static void -oss_output_disable(AudioOutput *ao) -{ - OssOutput *od = (OssOutput *)ao; - - od->pcm_export.Destruct(); -} - -#endif - -static void -oss_close(OssOutput *od) -{ - if (od->fd >= 0) - close(od->fd); - od->fd = -1; + if (fd >= 0) + close(fd); + fd = -1; } /** @@ -271,8 +280,8 @@ oss_try_ioctl_r(int fd, unsigned long request, int *value_r, const char *msg, Error &error) { assert(fd >= 0); - assert(value_r != NULL); - assert(msg != NULL); + assert(value_r != nullptr); + assert(msg != nullptr); assert(!error.IsDefined()); int ret = ioctl(fd, request, value_r); @@ -380,7 +389,7 @@ oss_setup_sample_rate(int fd, AudioFormat &audio_format, break; } - static const int sample_rates[] = { 48000, 44100, 0 }; + static constexpr int sample_rates[] = { 48000, 44100, 0 }; for (unsigned i = 0; sample_rates[i] != 0; ++i) { sample_rate = sample_rates[i]; if (sample_rate == (int)audio_format.sample_rate) @@ -412,6 +421,7 @@ oss_setup_sample_rate(int fd, AudioFormat &audio_format, * Convert a MPD sample format to its OSS counterpart. Returns * AFMT_QUERY if there is no direct counterpart. */ +gcc_const static int sample_format_to_oss(SampleFormat format) { @@ -442,13 +452,15 @@ sample_format_to_oss(SampleFormat format) #endif } - return AFMT_QUERY; + assert(false); + gcc_unreachable(); } /** * Convert an OSS sample format to its MPD counterpart. Returns * SampleFormat::UNDEFINED if there is no direct counterpart. */ +gcc_const static SampleFormat sample_format_from_oss(int format) { @@ -572,7 +584,7 @@ oss_setup_sample_format(int fd, AudioFormat &audio_format, /* the requested sample format is not available - probe for other formats supported by MPD */ - static const SampleFormat sample_formats[] = { + static constexpr SampleFormat sample_formats[] = { SampleFormat::S24_P32, SampleFormat::S32, SampleFormat::S16, @@ -609,18 +621,14 @@ oss_setup_sample_format(int fd, AudioFormat &audio_format, return false; } -/** - * Sets up the OSS device which was opened before. - */ -static bool -oss_setup(OssOutput *od, AudioFormat &audio_format, - Error &error) +inline bool +OssOutput::Setup(AudioFormat &_audio_format, Error &error) { - return oss_setup_channels(od->fd, audio_format, error) && - oss_setup_sample_rate(od->fd, audio_format, error) && - oss_setup_sample_format(od->fd, audio_format, &od->oss_format, + return oss_setup_channels(fd, _audio_format, error) && + oss_setup_sample_rate(fd, _audio_format, error) && + oss_setup_sample_format(fd, _audio_format, &oss_format, #ifdef AFMT_S24_PACKED - od->pcm_export, + pcm_export, #endif error); } @@ -628,46 +636,46 @@ oss_setup(OssOutput *od, AudioFormat &audio_format, /** * Reopen the device with the saved audio_format, without any probing. */ -static bool -oss_reopen(OssOutput *od, Error &error) +inline bool +OssOutput::Reopen(Error &error) { - assert(od->fd < 0); + assert(fd < 0); - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { + fd = open_cloexec(device, O_WRONLY, 0); + if (fd < 0) { error.FormatErrno("Error opening OSS device \"%s\"", - od->device); + device); return false; } enum oss_setup_result result; const char *const msg1 = "Failed to set channel count"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, - od->audio_format.channels, msg1, error); + result = oss_try_ioctl(fd, SNDCTL_DSP_CHANNELS, + audio_format.channels, msg1, error); if (result != SUCCESS) { - oss_close(od); + DoClose(); if (result == UNSUPPORTED) error.Set(oss_output_domain, msg1); return false; } const char *const msg2 = "Failed to set sample rate"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, - od->audio_format.sample_rate, msg2, error); + result = oss_try_ioctl(fd, SNDCTL_DSP_SPEED, + audio_format.sample_rate, msg2, error); if (result != SUCCESS) { - oss_close(od); + DoClose(); if (result == UNSUPPORTED) error.Set(oss_output_domain, msg2); return false; } const char *const msg3 = "Failed to set sample format"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, - od->oss_format, + result = oss_try_ioctl(fd, SNDCTL_DSP_SAMPLESIZE, + oss_format, msg3, error); if (result != SUCCESS) { - oss_close(od); + DoClose(); if (result == UNSUPPORTED) error.Set(oss_output_domain, msg3); return false; @@ -676,62 +684,47 @@ oss_reopen(OssOutput *od, Error &error) return true; } -static bool -oss_output_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +OssOutput::Open(AudioFormat &_audio_format, Error &error) { - OssOutput *od = (OssOutput *)ao; - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { + fd = open_cloexec(device, O_WRONLY, 0); + if (fd < 0) { error.FormatErrno("Error opening OSS device \"%s\"", - od->device); + device); return false; } - if (!oss_setup(od, audio_format, error)) { - oss_close(od); + if (!Setup(_audio_format, error)) { + DoClose(); return false; } - od->audio_format = audio_format; + audio_format = _audio_format; return true; } -static void -oss_output_close(AudioOutput *ao) +inline void +OssOutput::Cancel() { - OssOutput *od = (OssOutput *)ao; - - oss_close(od); -} - -static void -oss_output_cancel(AudioOutput *ao) -{ - OssOutput *od = (OssOutput *)ao; - - if (od->fd >= 0) { - ioctl(od->fd, SNDCTL_DSP_RESET, 0); - oss_close(od); + if (fd >= 0) { + ioctl(fd, SNDCTL_DSP_RESET, 0); + DoClose(); } } -static size_t -oss_output_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +OssOutput::Play(const void *chunk, size_t size, Error &error) { - OssOutput *od = (OssOutput *)ao; ssize_t ret; assert(size > 0); /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_reopen(od, error)) + if (fd < 0 && !Reopen(error)) return 0; #ifdef AFMT_S24_PACKED - const auto e = od->pcm_export->Export({chunk, size}); + const auto e = pcm_export->Export({chunk, size}); chunk = e.data; size = e.size; #endif @@ -739,40 +732,42 @@ oss_output_play(AudioOutput *ao, const void *chunk, size_t size, assert(size > 0); while (true) { - ret = write(od->fd, chunk, size); + ret = write(fd, chunk, size); if (ret > 0) { #ifdef AFMT_S24_PACKED - ret = od->pcm_export->CalcSourceSize(ret); + ret = pcm_export->CalcSourceSize(ret); #endif return ret; } if (ret < 0 && errno != EINTR) { - error.FormatErrno("Write error on %s", od->device); + error.FormatErrno("Write error on %s", device); return 0; } } } +typedef AudioOutputWrapper<OssOutput> Wrapper; + const struct AudioOutputPlugin oss_output_plugin = { "oss", oss_output_test_default_device, - oss_output_init, - oss_output_finish, + &Wrapper::Init, + &Wrapper::Finish, #ifdef AFMT_S24_PACKED - oss_output_enable, - oss_output_disable, + &Wrapper::Enable, + &Wrapper::Disable, #else nullptr, nullptr, #endif - oss_output_open, - oss_output_close, + &Wrapper::Open, + &Wrapper::Close, nullptr, nullptr, - oss_output_play, + &Wrapper::Play, nullptr, - oss_output_cancel, + &Wrapper::Cancel, nullptr, &oss_mixer_plugin, diff --git a/src/output/plugins/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx index f9970c8f0..8f9b3d424 100644 --- a/src/output/plugins/OssOutputPlugin.hxx +++ b/src/output/plugins/OssOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx index d8075d505..c70e2d498 100644 --- a/src/output/plugins/PipeOutputPlugin.cxx +++ b/src/output/plugins/PipeOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "PipeOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "config/ConfigError.hxx" #include "util/Error.hxx" @@ -27,7 +28,9 @@ #include <stdio.h> -struct PipeOutput { +class PipeOutput { + friend struct AudioOutputWrapper<PipeOutput>; + AudioOutput base; std::string cmd; @@ -36,17 +39,27 @@ struct PipeOutput { PipeOutput() :base(pipe_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); + bool Configure(const ConfigBlock &block, Error &error); + +public: + static PipeOutput *Create(const ConfigBlock &block, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + + void Close() { + pclose(fh); } - bool Configure(const config_param ¶m, Error &error); + size_t Play(const void *chunk, size_t size, Error &error); }; inline bool -PipeOutput::Configure(const config_param ¶m, Error &error) +PipeOutput::Configure(const ConfigBlock &block, Error &error) { - cmd = param.GetBlockValue("command", ""); + if (!base.Configure(block, error)) + return false; + + cmd = block.GetBlockValue("command", ""); if (cmd.empty()) { error.Set(config_domain, "No \"command\" parameter specified"); @@ -56,83 +69,56 @@ PipeOutput::Configure(const config_param ¶m, Error &error) return true; } -static AudioOutput * -pipe_output_init(const config_param ¶m, Error &error) +inline PipeOutput * +PipeOutput::Create(const ConfigBlock &block, Error &error) { - PipeOutput *pd = new PipeOutput(); + PipeOutput *po = new PipeOutput(); - if (!pd->Initialize(param, error)) { - delete pd; + if (!po->Configure(block, error)) { + delete po; return nullptr; } - if (!pd->Configure(param, error)) { - delete pd; - return nullptr; - } - - return &pd->base; + return po; } -static void -pipe_output_finish(AudioOutput *ao) -{ - PipeOutput *pd = (PipeOutput *)ao; - - delete pd; -} - -static bool -pipe_output_open(AudioOutput *ao, - gcc_unused AudioFormat &audio_format, - Error &error) +inline bool +PipeOutput::Open(gcc_unused AudioFormat &audio_format, Error &error) { - PipeOutput *pd = (PipeOutput *)ao; - - pd->fh = popen(pd->cmd.c_str(), "w"); - if (pd->fh == nullptr) { + fh = popen(cmd.c_str(), "w"); + if (fh == nullptr) { error.FormatErrno("Error opening pipe \"%s\"", - pd->cmd.c_str()); + cmd.c_str()); return false; } return true; } -static void -pipe_output_close(AudioOutput *ao) -{ - PipeOutput *pd = (PipeOutput *)ao; - - pclose(pd->fh); -} - -static size_t -pipe_output_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +PipeOutput::Play(const void *chunk, size_t size, Error &error) { - PipeOutput *pd = (PipeOutput *)ao; - size_t ret; - - ret = fwrite(chunk, 1, size, pd->fh); - if (ret == 0) + size_t nbytes = fwrite(chunk, 1, size, fh); + if (nbytes == 0) error.SetErrno("Write error on pipe"); - return ret; + return nbytes; } +typedef AudioOutputWrapper<PipeOutput> Wrapper; + const struct AudioOutputPlugin pipe_output_plugin = { "pipe", nullptr, - pipe_output_init, - pipe_output_finish, + &Wrapper::Init, + &Wrapper::Finish, nullptr, nullptr, - pipe_output_open, - pipe_output_close, + &Wrapper::Open, + &Wrapper::Close, nullptr, nullptr, - pipe_output_play, + &Wrapper::Play, nullptr, nullptr, nullptr, diff --git a/src/output/plugins/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx index bdaf2ecfd..5d92848c2 100644 --- a/src/output/plugins/PipeOutputPlugin.hxx +++ b/src/output/plugins/PipeOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx index 120bad090..8b5225584 100644 --- a/src/output/plugins/PulseOutputPlugin.cxx +++ b/src/output/plugins/PulseOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,14 @@ #include "config.h" #include "PulseOutputPlugin.hxx" +#include "lib/pulse/Domain.hxx" +#include "lib/pulse/Error.hxx" +#include "lib/pulse/LogError.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "mixer/MixerList.hxx" #include "mixer/plugins/PulseMixerPlugin.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" #include "Log.hxx" #include <pulse/thread-mainloop.h> @@ -31,7 +34,6 @@ #include <pulse/stream.h> #include <pulse/introspect.h> #include <pulse/subscribe.h> -#include <pulse/error.h> #include <pulse/version.h> #include <assert.h> @@ -40,7 +42,9 @@ #define MPD_PULSE_NAME "Music Player Daemon" -struct PulseOutput { +class PulseOutput { + friend struct AudioOutputWrapper<PulseOutput>; + AudioOutput base; const char *name; @@ -56,80 +60,190 @@ struct PulseOutput { size_t writable; PulseOutput() - :base(pulse_output_plugin) {} -}; + :base(pulse_output_plugin), + mixer(nullptr), + mainloop(nullptr), stream(nullptr) {} -static constexpr Domain pulse_output_domain("pulse_output"); +public: + void SetMixer(PulseMixer &_mixer); -static void -SetError(Error &error, pa_context *context, const char *msg) -{ - const int e = pa_context_errno(context); - error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e)); -} + void ClearMixer(gcc_unused PulseMixer &old_mixer) { + assert(mixer == &old_mixer); + + mixer = nullptr; + } + + bool SetVolume(const pa_cvolume &volume, Error &error); + + void Lock() { + pa_threaded_mainloop_lock(mainloop); + } + + void Unlock() { + pa_threaded_mainloop_unlock(mainloop); + } + + void OnContextStateChanged(pa_context_state_t new_state); + void OnServerLayoutChanged(pa_subscription_event_type_t t, + uint32_t idx); + void OnStreamSuspended(pa_stream *_stream); + void OnStreamStateChanged(pa_stream *_stream, + pa_stream_state_t new_state); + void OnStreamWrite(size_t nbytes); + + void OnStreamSuccess() { + Signal(); + } + + gcc_const + static bool TestDefaultDevice(); + + bool Configure(const ConfigBlock &block, Error &error); + static PulseOutput *Create(const ConfigBlock &block, Error &error); + + bool Enable(Error &error); + void Disable(); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + unsigned Delay(); + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); + bool Pause(); + +private: + /** + * Attempt to connect asynchronously to the PulseAudio server. + * + * @return true on success, false on error + */ + bool Connect(Error &error); + + /** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ + bool SetupContext(Error &error); + + /** + * Frees and clears the context. + * + * Caller must lock the main loop. + */ + void DeleteContext(); + + void Signal() { + pa_threaded_mainloop_signal(mainloop, 0); + } + + /** + * Check if the context is (already) connected, and waits if + * not. If the context has been disconnected, retry to + * connect. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ + bool WaitConnection(Error &error); + + /** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ + bool SetupStream(const pa_sample_spec &ss, Error &error); + + /** + * Frees and clears the stream. + */ + void DeleteStream(); + + /** + * Check if the stream is (already) connected, and waits if + * not. The mainloop must be locked before calling this + * function. + * + * @return true on success, false on error + */ + bool WaitStream(Error &error); + + /** + * Sets cork mode on the stream. + */ + bool StreamPause(bool pause, Error &error); +}; void pulse_output_lock(PulseOutput &po) { - pa_threaded_mainloop_lock(po.mainloop); + po.Lock(); } void pulse_output_unlock(PulseOutput &po) { - pa_threaded_mainloop_unlock(po.mainloop); + po.Unlock(); } -void -pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm) +inline void +PulseOutput::SetMixer(PulseMixer &_mixer) { - assert(po.mixer == nullptr); + assert(mixer == nullptr); - po.mixer = ± + mixer = &_mixer; - if (po.mainloop == nullptr) + if (mainloop == nullptr) return; - pa_threaded_mainloop_lock(po.mainloop); + pa_threaded_mainloop_lock(mainloop); - if (po.context != nullptr && - pa_context_get_state(po.context) == PA_CONTEXT_READY) { - pulse_mixer_on_connect(pm, po.context); + if (context != nullptr && + pa_context_get_state(context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(_mixer, context); - if (po.stream != nullptr && - pa_stream_get_state(po.stream) == PA_STREAM_READY) - pulse_mixer_on_change(pm, po.context, po.stream); + if (stream != nullptr && + pa_stream_get_state(stream) == PA_STREAM_READY) + pulse_mixer_on_change(_mixer, context, stream); } - pa_threaded_mainloop_unlock(po.mainloop); + pa_threaded_mainloop_unlock(mainloop); } void -pulse_output_clear_mixer(PulseOutput &po, gcc_unused PulseMixer &pm) +pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm) { - assert(po.mixer == &pm); - - po.mixer = nullptr; + po.SetMixer(pm); } -bool -pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, - Error &error) +void +pulse_output_clear_mixer(PulseOutput &po, PulseMixer &pm) { - pa_operation *o; + po.ClearMixer(pm); +} - if (po.context == nullptr || po.stream == nullptr || - pa_stream_get_state(po.stream) != PA_STREAM_READY) { - error.Set(pulse_output_domain, "disconnected"); +inline bool +PulseOutput::SetVolume(const pa_cvolume &volume, Error &error) +{ + if (context == nullptr || stream == nullptr || + pa_stream_get_state(stream) != PA_STREAM_READY) { + error.Set(pulse_domain, "disconnected"); return false; } - o = pa_context_set_sink_input_volume(po.context, - pa_stream_get_index(po.stream), - volume, nullptr, nullptr); + pa_operation *o = + pa_context_set_sink_input_volume(context, + pa_stream_get_index(stream), + &volume, nullptr, nullptr); if (o == nullptr) { - SetError(error, po.context, - "failed to set PulseAudio volume"); + SetPulseError(error, context, + "failed to set PulseAudio volume"); return false; } @@ -137,6 +251,13 @@ pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, return true; } +bool +pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, + Error &error) +{ + return po.SetVolume(*volume, error); +} + /** * \brief waits for a pulseaudio operation to finish, frees it and * unlocks the mainloop @@ -169,32 +290,30 @@ static void pulse_output_stream_success_cb(gcc_unused pa_stream *s, gcc_unused int success, void *userdata) { - PulseOutput *po = (PulseOutput *)userdata; + PulseOutput &po = *(PulseOutput *)userdata; - pa_threaded_mainloop_signal(po->mainloop, 0); + po.OnStreamSuccess(); } -static void -pulse_output_context_state_cb(struct pa_context *context, void *userdata) +inline void +PulseOutput::OnContextStateChanged(pa_context_state_t new_state) { - PulseOutput *po = (PulseOutput *)userdata; - - switch (pa_context_get_state(context)) { + switch (new_state) { case PA_CONTEXT_READY: - if (po->mixer != nullptr) - pulse_mixer_on_connect(*po->mixer, context); + if (mixer != nullptr) + pulse_mixer_on_connect(*mixer, context); - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: - if (po->mixer != nullptr) - pulse_mixer_on_disconnect(*po->mixer); + if (mixer != nullptr) + pulse_mixer_on_disconnect(*mixer); /* the caller thread might be waiting for these states */ - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_CONTEXT_UNCONNECTED: @@ -206,230 +325,203 @@ pulse_output_context_state_cb(struct pa_context *context, void *userdata) } static void -pulse_output_subscribe_cb(pa_context *context, - pa_subscription_event_type_t t, - uint32_t idx, void *userdata) +pulse_output_context_state_cb(struct pa_context *context, void *userdata) +{ + PulseOutput &po = *(PulseOutput *)userdata; + + po.OnContextStateChanged(pa_context_get_state(context)); +} + +inline void +PulseOutput::OnServerLayoutChanged(pa_subscription_event_type_t t, + uint32_t idx) { - PulseOutput *po = (PulseOutput *)userdata; pa_subscription_event_type_t facility = pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); pa_subscription_event_type_t type = pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); - if (po->mixer != nullptr && + if (mixer != nullptr && facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && - po->stream != nullptr && - pa_stream_get_state(po->stream) == PA_STREAM_READY && - idx == pa_stream_get_index(po->stream) && + stream != nullptr && + pa_stream_get_state(stream) == PA_STREAM_READY && + idx == pa_stream_get_index(stream) && (type == PA_SUBSCRIPTION_EVENT_NEW || type == PA_SUBSCRIPTION_EVENT_CHANGE)) - pulse_mixer_on_change(*po->mixer, context, po->stream); + pulse_mixer_on_change(*mixer, context, stream); } -/** - * Attempt to connect asynchronously to the PulseAudio server. - * - * @return true on success, false on error - */ -static bool -pulse_output_connect(PulseOutput *po, Error &error) +static void +pulse_output_subscribe_cb(gcc_unused pa_context *context, + pa_subscription_event_type_t t, + uint32_t idx, void *userdata) { - assert(po != nullptr); - assert(po->context != nullptr); + PulseOutput &po = *(PulseOutput *)userdata; - if (pa_context_connect(po->context, po->server, + po.OnServerLayoutChanged(t, idx); +} + +inline bool +PulseOutput::Connect(Error &error) +{ + assert(context != nullptr); + + if (pa_context_connect(context, server, (pa_context_flags_t)0, nullptr) < 0) { - SetError(error, po->context, - "pa_context_connect() has failed"); + SetPulseError(error, context, + "pa_context_connect() has failed"); return false; } return true; } -/** - * Frees and clears the stream. - */ -static void -pulse_output_delete_stream(PulseOutput *po) +void +PulseOutput::DeleteStream() { - assert(po != nullptr); - assert(po->stream != nullptr); + assert(stream != nullptr); - pa_stream_set_suspended_callback(po->stream, nullptr, nullptr); + pa_stream_set_suspended_callback(stream, nullptr, nullptr); - pa_stream_set_state_callback(po->stream, nullptr, nullptr); - pa_stream_set_write_callback(po->stream, nullptr, nullptr); + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_disconnect(po->stream); - pa_stream_unref(po->stream); - po->stream = nullptr; + pa_stream_disconnect(stream); + pa_stream_unref(stream); + stream = nullptr; } -/** - * Frees and clears the context. - * - * Caller must lock the main loop. - */ -static void -pulse_output_delete_context(PulseOutput *po) +void +PulseOutput::DeleteContext() { - assert(po != nullptr); - assert(po->context != nullptr); + assert(context != nullptr); - pa_context_set_state_callback(po->context, nullptr, nullptr); - pa_context_set_subscribe_callback(po->context, nullptr, nullptr); + pa_context_set_state_callback(context, nullptr, nullptr); + pa_context_set_subscribe_callback(context, nullptr, nullptr); - pa_context_disconnect(po->context); - pa_context_unref(po->context); - po->context = nullptr; + pa_context_disconnect(context); + pa_context_unref(context); + context = nullptr; } -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_context(PulseOutput *po, Error &error) +bool +PulseOutput::SetupContext(Error &error) { - assert(po != nullptr); - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), - MPD_PULSE_NAME); - if (po->context == nullptr) { - error.Set(pulse_output_domain, "pa_context_new() has failed"); + context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), + MPD_PULSE_NAME); + if (context == nullptr) { + error.Set(pulse_domain, "pa_context_new() has failed"); return false; } - pa_context_set_state_callback(po->context, - pulse_output_context_state_cb, po); - pa_context_set_subscribe_callback(po->context, - pulse_output_subscribe_cb, po); + pa_context_set_state_callback(context, + pulse_output_context_state_cb, this); + pa_context_set_subscribe_callback(context, + pulse_output_subscribe_cb, this); - if (!pulse_output_connect(po, error)) { - pulse_output_delete_context(po); + if (!Connect(error)) { + DeleteContext(); return false; } return true; } -static AudioOutput * -pulse_output_init(const config_param ¶m, Error &error) +inline bool +PulseOutput::Configure(const ConfigBlock &block, Error &error) { - PulseOutput *po; + if (!base.Configure(block, error)) + return false; + + name = block.GetBlockValue("name", "mpd_pulse"); + server = block.GetBlockValue("server"); + sink = block.GetBlockValue("sink"); + return true; +} + +PulseOutput * +PulseOutput::Create(const ConfigBlock &block, Error &error) +{ setenv("PULSE_PROP_media.role", "music", true); setenv("PULSE_PROP_application.icon_name", "mpd", true); - po = new PulseOutput(); - if (!po->base.Configure(param, error)) { + auto *po = new PulseOutput(); + if (!po->Configure(block, error)) { delete po; return nullptr; } - po->name = param.GetBlockValue("name", "mpd_pulse"); - po->server = param.GetBlockValue("server"); - po->sink = param.GetBlockValue("sink"); - - po->mixer = nullptr; - po->mainloop = nullptr; - po->context = nullptr; - po->stream = nullptr; - - return &po->base; + return po; } -static void -pulse_output_finish(AudioOutput *ao) +inline bool +PulseOutput::Enable(Error &error) { - PulseOutput *po = (PulseOutput *)ao; - - delete po; -} - -static bool -pulse_output_enable(AudioOutput *ao, Error &error) -{ - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop == nullptr); - assert(po->context == nullptr); + assert(mainloop == nullptr); /* create the libpulse mainloop and start the thread */ - po->mainloop = pa_threaded_mainloop_new(); - if (po->mainloop == nullptr) { - error.Set(pulse_output_domain, + mainloop = pa_threaded_mainloop_new(); + if (mainloop == nullptr) { + error.Set(pulse_domain, "pa_threaded_mainloop_new() has failed"); return false; } - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (pa_threaded_mainloop_start(po->mainloop) < 0) { - pa_threaded_mainloop_unlock(po->mainloop); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; + if (pa_threaded_mainloop_start(mainloop) < 0) { + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_free(mainloop); + mainloop = nullptr; - error.Set(pulse_output_domain, + error.Set(pulse_domain, "pa_threaded_mainloop_start() has failed"); return false; } /* create the libpulse context and connect it */ - if (!pulse_output_setup_context(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - pa_threaded_mainloop_stop(po->mainloop); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; + if (!SetupContext(error)) { + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + mainloop = nullptr; return false; } - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); return true; } -static void -pulse_output_disable(AudioOutput *ao) +inline void +PulseOutput::Disable() { - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - pa_threaded_mainloop_stop(po->mainloop); - if (po->context != nullptr) - pulse_output_delete_context(po); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; + pa_threaded_mainloop_stop(mainloop); + if (context != nullptr) + DeleteContext(); + pa_threaded_mainloop_free(mainloop); + mainloop = nullptr; } -/** - * Check if the context is (already) connected, and waits if not. If - * the context has been disconnected, retry to connect. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_wait_connection(PulseOutput *po, Error &error) +bool +PulseOutput::WaitConnection(Error &error) { - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); pa_context_state_t state; - if (po->context == nullptr && !pulse_output_setup_context(po, error)) + if (context == nullptr && !SetupContext(error)) return false; while (true) { - state = pa_context_get_state(po->context); + state = pa_context_get_state(context); switch (state) { case PA_CONTEXT_READY: /* nothing to do */ @@ -439,56 +531,61 @@ pulse_output_wait_connection(PulseOutput *po, Error &error) case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: /* failure */ - SetError(error, po->context, "failed to connect"); - pulse_output_delete_context(po); + SetPulseError(error, context, "failed to connect"); + DeleteContext(); return false; case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: /* wait some more */ - pa_threaded_mainloop_wait(po->mainloop); + pa_threaded_mainloop_wait(mainloop); break; } } } -static void -pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata) +inline void +PulseOutput::OnStreamSuspended(gcc_unused pa_stream *_stream) { - PulseOutput *po = (PulseOutput *)userdata; - - assert(stream == po->stream || po->stream == nullptr); - assert(po->mainloop != nullptr); + assert(_stream == stream || stream == nullptr); + assert(mainloop != nullptr); /* wake up the main loop to break out of the loop in pulse_output_play() */ - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); } static void -pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +pulse_output_stream_suspended_cb(pa_stream *stream, void *userdata) { - PulseOutput *po = (PulseOutput *)userdata; + PulseOutput &po = *(PulseOutput *)userdata; - assert(stream == po->stream || po->stream == nullptr); - assert(po->mainloop != nullptr); - assert(po->context != nullptr); + po.OnStreamSuspended(stream); +} + +inline void +PulseOutput::OnStreamStateChanged(pa_stream *_stream, + pa_stream_state_t new_state) +{ + assert(_stream == stream || stream == nullptr); + assert(mainloop != nullptr); + assert(context != nullptr); - switch (pa_stream_get_state(stream)) { + switch (new_state) { case PA_STREAM_READY: - if (po->mixer != nullptr) - pulse_mixer_on_change(*po->mixer, po->context, stream); + if (mixer != nullptr) + pulse_mixer_on_change(*mixer, context, _stream); - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: - if (po->mixer != nullptr) - pulse_mixer_on_disconnect(*po->mixer); + if (mixer != nullptr) + pulse_mixer_on_disconnect(*mixer); - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_STREAM_UNCONNECTED: @@ -498,68 +595,75 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata) } static void +pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +{ + PulseOutput &po = *(PulseOutput *)userdata; + + return po.OnStreamStateChanged(stream, pa_stream_get_state(stream)); +} + +inline void +PulseOutput::OnStreamWrite(size_t nbytes) +{ + assert(mainloop != nullptr); + + writable = nbytes; + Signal(); +} + +static void pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes, void *userdata) { - PulseOutput *po = (PulseOutput *)userdata; - - assert(po->mainloop != nullptr); + PulseOutput &po = *(PulseOutput *)userdata; - po->writable = nbytes; - pa_threaded_mainloop_signal(po->mainloop, 0); + return po.OnStreamWrite(nbytes); } -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, - Error &error) +inline bool +PulseOutput::SetupStream(const pa_sample_spec &ss, Error &error) { - assert(po != nullptr); - assert(po->context != nullptr); + assert(context != nullptr); - po->stream = pa_stream_new(po->context, po->name, ss, nullptr); - if (po->stream == nullptr) { - SetError(error, po->context, "pa_stream_new() has failed"); + /* WAVE-EX is been adopted as the speaker map for most media files */ + pa_channel_map chan_map; + pa_channel_map_init_auto(&chan_map, ss.channels, + PA_CHANNEL_MAP_WAVEEX); + stream = pa_stream_new(context, name, &ss, &chan_map); + if (stream == nullptr) { + SetPulseError(error, context, + "pa_stream_new() has failed"); return false; } - pa_stream_set_suspended_callback(po->stream, - pulse_output_stream_suspended_cb, po); + pa_stream_set_suspended_callback(stream, + pulse_output_stream_suspended_cb, + this); - pa_stream_set_state_callback(po->stream, - pulse_output_stream_state_cb, po); - pa_stream_set_write_callback(po->stream, - pulse_output_stream_write_cb, po); + pa_stream_set_state_callback(stream, + pulse_output_stream_state_cb, this); + pa_stream_set_write_callback(stream, + pulse_output_stream_write_cb, this); return true; } -static bool -pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +PulseOutput::Open(AudioFormat &audio_format, Error &error) { - PulseOutput *po = (PulseOutput *)ao; - pa_sample_spec ss; - - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (po->context != nullptr) { - switch (pa_context_get_state(po->context)) { + if (context != nullptr) { + switch (pa_context_get_state(context)) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: /* the connection was closed meanwhile; delete it, and pulse_output_wait_connection() will reopen it */ - pulse_output_delete_context(po); + DeleteContext(); break; case PA_CONTEXT_READY: @@ -570,8 +674,8 @@ pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, } } - if (!pulse_output_wait_connection(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!WaitConnection(error)) { + pa_threaded_mainloop_unlock(mainloop); return false; } @@ -579,304 +683,282 @@ pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, we just force MPD to send us everything as 16 bit */ audio_format.format = SampleFormat::S16; + pa_sample_spec ss; ss.format = PA_SAMPLE_S16NE; ss.rate = audio_format.sample_rate; ss.channels = audio_format.channels; /* create a stream .. */ - if (!pulse_output_setup_stream(po, &ss, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!SetupStream(ss, error)) { + pa_threaded_mainloop_unlock(mainloop); return false; } /* .. and connect it (asynchronously) */ - if (pa_stream_connect_playback(po->stream, po->sink, + if (pa_stream_connect_playback(stream, sink, nullptr, pa_stream_flags_t(0), nullptr, nullptr) < 0) { - pulse_output_delete_stream(po); + DeleteStream(); - SetError(error, po->context, - "pa_stream_connect_playback() has failed"); - pa_threaded_mainloop_unlock(po->mainloop); + SetPulseError(error, context, + "pa_stream_connect_playback() has failed"); + pa_threaded_mainloop_unlock(mainloop); return false; } - pa_threaded_mainloop_unlock(po->mainloop); - + pa_threaded_mainloop_unlock(mainloop); return true; } -static void -pulse_output_close(AudioOutput *ao) +inline void +PulseOutput::Close() { - PulseOutput *po = (PulseOutput *)ao; - pa_operation *o; - - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { - o = pa_stream_drain(po->stream, - pulse_output_stream_success_cb, po); + if (pa_stream_get_state(stream) == PA_STREAM_READY) { + pa_operation *o = + pa_stream_drain(stream, + pulse_output_stream_success_cb, this); if (o == nullptr) { - FormatWarning(pulse_output_domain, - "pa_stream_drain() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + LogPulseError(context, + "pa_stream_drain() has failed"); } else - pulse_wait_for_operation(po->mainloop, o); + pulse_wait_for_operation(mainloop, o); } - pulse_output_delete_stream(po); + DeleteStream(); - if (po->context != nullptr && - pa_context_get_state(po->context) != PA_CONTEXT_READY) - pulse_output_delete_context(po); + if (context != nullptr && + pa_context_get_state(context) != PA_CONTEXT_READY) + DeleteContext(); - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); } -/** - * Check if the stream is (already) connected, and waits if not. The - * mainloop must be locked before calling this function. - * - * @return true on success, false on error - */ -static bool -pulse_output_wait_stream(PulseOutput *po, Error &error) +bool +PulseOutput::WaitStream(Error &error) { while (true) { - switch (pa_stream_get_state(po->stream)) { + switch (pa_stream_get_state(stream)) { case PA_STREAM_READY: return true; case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: case PA_STREAM_UNCONNECTED: - SetError(error, po->context, - "failed to connect the stream"); + SetPulseError(error, context, + "failed to connect the stream"); return false; case PA_STREAM_CREATING: - pa_threaded_mainloop_wait(po->mainloop); + pa_threaded_mainloop_wait(mainloop); break; } } } -/** - * Sets cork mode on the stream. - */ -static bool -pulse_output_stream_pause(PulseOutput *po, bool pause, - Error &error) +bool +PulseOutput::StreamPause(bool pause, Error &error) { - pa_operation *o; - - assert(po->mainloop != nullptr); - assert(po->context != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(context != nullptr); + assert(stream != nullptr); - o = pa_stream_cork(po->stream, pause, - pulse_output_stream_success_cb, po); + pa_operation *o = pa_stream_cork(stream, pause, + pulse_output_stream_success_cb, this); if (o == nullptr) { - SetError(error, po->context, "pa_stream_cork() has failed"); + SetPulseError(error, context, + "pa_stream_cork() has failed"); return false; } - if (!pulse_wait_for_operation(po->mainloop, o)) { - SetError(error, po->context, "pa_stream_cork() has failed"); + if (!pulse_wait_for_operation(mainloop, o)) { + SetPulseError(error, context, + "pa_stream_cork() has failed"); return false; } return true; } -static unsigned -pulse_output_delay(AudioOutput *ao) +inline unsigned +PulseOutput::Delay() { - PulseOutput *po = (PulseOutput *)ao; - unsigned result = 0; - - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (po->base.pause && pa_stream_is_corked(po->stream) && - pa_stream_get_state(po->stream) == PA_STREAM_READY) + unsigned result = 0; + if (base.pause && pa_stream_is_corked(stream) && + pa_stream_get_state(stream) == PA_STREAM_READY) /* idle while paused */ result = 1000; - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); return result; } -static size_t -pulse_output_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +PulseOutput::Play(const void *chunk, size_t size, Error &error) { - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(stream != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); /* check if the stream is (already) connected */ - if (!pulse_output_wait_stream(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!WaitStream(error)) { + pa_threaded_mainloop_unlock(mainloop); return 0; } - assert(po->context != nullptr); + assert(context != nullptr); /* unpause if previously paused */ - if (pa_stream_is_corked(po->stream) && - !pulse_output_stream_pause(po, false, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (pa_stream_is_corked(stream) && !StreamPause(false, error)) { + pa_threaded_mainloop_unlock(mainloop); return 0; } /* wait until the server allows us to write */ - while (po->writable == 0) { - if (pa_stream_is_suspended(po->stream)) { - pa_threaded_mainloop_unlock(po->mainloop); - error.Set(pulse_output_domain, "suspended"); + while (writable == 0) { + if (pa_stream_is_suspended(stream)) { + pa_threaded_mainloop_unlock(mainloop); + error.Set(pulse_domain, "suspended"); return 0; } - pa_threaded_mainloop_wait(po->mainloop); + pa_threaded_mainloop_wait(mainloop); - if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { - pa_threaded_mainloop_unlock(po->mainloop); - error.Set(pulse_output_domain, "disconnected"); + if (pa_stream_get_state(stream) != PA_STREAM_READY) { + pa_threaded_mainloop_unlock(mainloop); + error.Set(pulse_domain, "disconnected"); return 0; } } /* now write */ - if (size > po->writable) + if (size > writable) /* don't send more than possible */ - size = po->writable; + size = writable; - po->writable -= size; + writable -= size; - int result = pa_stream_write(po->stream, chunk, size, nullptr, + int result = pa_stream_write(stream, chunk, size, nullptr, 0, PA_SEEK_RELATIVE); - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); if (result < 0) { - SetError(error, po->context, "pa_stream_write() failed"); + SetPulseError(error, context, "pa_stream_write() failed"); return 0; } return size; } -static void -pulse_output_cancel(AudioOutput *ao) +inline void +PulseOutput::Cancel() { - PulseOutput *po = (PulseOutput *)ao; - pa_operation *o; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(stream != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + if (pa_stream_get_state(stream) != PA_STREAM_READY) { /* no need to flush when the stream isn't connected yet */ - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); return; } - assert(po->context != nullptr); + assert(context != nullptr); - o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); + pa_operation *o = pa_stream_flush(stream, + pulse_output_stream_success_cb, + this); if (o == nullptr) { - FormatWarning(pulse_output_domain, - "pa_stream_flush() has failed: %s", - pa_strerror(pa_context_errno(po->context))); - pa_threaded_mainloop_unlock(po->mainloop); + LogPulseError(context, "pa_stream_flush() has failed"); + pa_threaded_mainloop_unlock(mainloop); return; } - pulse_wait_for_operation(po->mainloop, o); - pa_threaded_mainloop_unlock(po->mainloop); + pulse_wait_for_operation(mainloop, o); + pa_threaded_mainloop_unlock(mainloop); } -static bool -pulse_output_pause(AudioOutput *ao) +inline bool +PulseOutput::Pause() { - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(stream != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); /* check if the stream is (already/still) connected */ Error error; - if (!pulse_output_wait_stream(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!WaitStream(error)) { + pa_threaded_mainloop_unlock(mainloop); LogError(error); return false; } - assert(po->context != nullptr); + assert(context != nullptr); /* cork the stream */ - if (!pa_stream_is_corked(po->stream) && - !pulse_output_stream_pause(po, true, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!pa_stream_is_corked(stream) && !StreamPause(true, error)) { + pa_threaded_mainloop_unlock(mainloop); LogError(error); return false; } - pa_threaded_mainloop_unlock(po->mainloop); - + pa_threaded_mainloop_unlock(mainloop); return true; } -static bool -pulse_output_test_default_device(void) +inline bool +PulseOutput::TestDefaultDevice() { - bool success; - - const config_param empty; - PulseOutput *po = (PulseOutput *) - pulse_output_init(empty, IgnoreError()); + const ConfigBlock empty; + PulseOutput *po = PulseOutput::Create(empty, IgnoreError()); if (po == nullptr) return false; - success = pulse_output_wait_connection(po, IgnoreError()); - pulse_output_finish(&po->base); - + bool success = po->WaitConnection(IgnoreError()); + delete po; return success; } +static bool +pulse_output_test_default_device(void) +{ + return PulseOutput::TestDefaultDevice(); +} + +typedef AudioOutputWrapper<PulseOutput> Wrapper; + const struct AudioOutputPlugin pulse_output_plugin = { "pulse", pulse_output_test_default_device, - pulse_output_init, - pulse_output_finish, - pulse_output_enable, - pulse_output_disable, - pulse_output_open, - pulse_output_close, - pulse_output_delay, + &Wrapper::Init, + &Wrapper::Finish, + &Wrapper::Enable, + &Wrapper::Disable, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - pulse_output_play, + &Wrapper::Play, nullptr, - pulse_output_cancel, - pulse_output_pause, + &Wrapper::Cancel, + &Wrapper::Pause, &pulse_mixer_plugin, }; diff --git a/src/output/plugins/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx index 9219780a5..f193db1d8 100644 --- a/src/output/plugins/PulseOutputPlugin.hxx +++ b/src/output/plugins/PulseOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX #define MPD_PULSE_OUTPUT_PLUGIN_HXX -struct PulseOutput; +class PulseOutput; class PulseMixer; struct pa_cvolume; class Error; diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx index 87e23f55a..4f9231c70 100644 --- a/src/output/plugins/RecorderOutputPlugin.cxx +++ b/src/output/plugins/RecorderOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,21 +20,28 @@ #include "config.h" #include "RecorderOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" +#include "tag/Format.hxx" +#include "encoder/ToOutputStream.hxx" +#include "encoder/EncoderInterface.hxx" #include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderList.hxx" #include "config/ConfigError.hxx" +#include "config/ConfigPath.hxx" +#include "Log.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/io/FileOutputStream.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include "system/fd_util.h" -#include "open.h" #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> +#include <stdlib.h> + +static constexpr Domain recorder_domain("recorder"); + +class RecorderOutput { + friend struct AudioOutputWrapper<RecorderOutput>; -struct RecorderOutput { AudioOutput base; /** @@ -45,44 +52,77 @@ struct RecorderOutput { /** * The destination file name. */ - const char *path; + AllocatedPath path; + + /** + * A string that will be used with FormatTag() to build the + * destination path. + */ + std::string format_path; /** - * The destination file descriptor. + * The #AudioFormat that is currently active. This is used + * for switching to another file. */ - int fd; + AudioFormat effective_audio_format; /** - * The buffer for encoder_read(). + * The destination file. */ - char buffer[32768]; + FileOutputStream *file; RecorderOutput() - :base(recorder_output_plugin) {} + :base(recorder_output_plugin), + encoder(nullptr), + path(AllocatedPath::Null()) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + ~RecorderOutput() { + if (encoder != nullptr) + encoder->Dispose(); } - bool Configure(const config_param ¶m, Error &error); + bool Initialize(const ConfigBlock &block, Error &error_r) { + return base.Configure(block, error_r); + } + + static RecorderOutput *Create(const ConfigBlock &block, Error &error); + + bool Configure(const ConfigBlock &block, Error &error); - bool WriteToFile(const void *data, size_t length, Error &error); + bool Open(AudioFormat &audio_format, Error &error); + void Close(); /** * Writes pending data from the encoder to the output file. */ bool EncoderToFile(Error &error); -}; -static constexpr Domain recorder_output_domain("recorder_output"); + void SendTag(const Tag &tag); + + size_t Play(const void *chunk, size_t size, Error &error); + +private: + gcc_pure + bool HasDynamicPath() const { + return !format_path.empty(); + } + + /** + * Finish the encoder and commit the file. + */ + bool Commit(Error &error); + + void FinishFormat(); + bool ReopenFormat(AllocatedPath &&new_path, Error &error); +}; inline bool -RecorderOutput::Configure(const config_param ¶m, Error &error) +RecorderOutput::Configure(const ConfigBlock &block, Error &error) { /* read configuration */ const char *encoder_name = - param.GetBlockValue("encoder", "vorbis"); + block.GetBlockValue("encoder", "vorbis"); const auto encoder_plugin = encoder_plugin_get(encoder_name); if (encoder_plugin == nullptr) { error.Format(config_domain, @@ -90,167 +130,269 @@ RecorderOutput::Configure(const config_param ¶m, Error &error) return false; } - path = param.GetBlockValue("path"); - if (path == nullptr) { + path = block.GetBlockPath("path", error); + if (error.IsDefined()) + return false; + + const char *fmt = block.GetBlockValue("format_path", nullptr); + if (fmt != nullptr) + format_path = fmt; + + if (path.IsNull() && fmt == nullptr) { error.Set(config_domain, "'path' not configured"); return false; } + if (!path.IsNull() && fmt != nullptr) { + error.Set(config_domain, "Cannot have both 'path' and 'format_path'"); + return false; + } + /* initialize encoder */ - encoder = encoder_init(*encoder_plugin, param, error); + encoder = encoder_init(*encoder_plugin, block, error); if (encoder == nullptr) return false; return true; } -static AudioOutput * -recorder_output_init(const config_param ¶m, Error &error) +RecorderOutput * +RecorderOutput::Create(const ConfigBlock &block, Error &error) { RecorderOutput *recorder = new RecorderOutput(); - if (!recorder->Initialize(param, error)) { + if (!recorder->Initialize(block, error)) { delete recorder; return nullptr; } - if (!recorder->Configure(param, error)) { + if (!recorder->Configure(block, error)) { delete recorder; return nullptr; } - return &recorder->base; + return recorder; } -static void -recorder_output_finish(AudioOutput *ao) +inline bool +RecorderOutput::EncoderToFile(Error &error) { - RecorderOutput *recorder = (RecorderOutput *)ao; + assert(file != nullptr); + assert(file->IsDefined()); - encoder_finish(recorder->encoder); - delete recorder; + return EncoderToOutputStream(*file, *encoder, error); } inline bool -RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error) +RecorderOutput::Open(AudioFormat &audio_format, Error &error) { - assert(length > 0); - - const uint8_t *data = (const uint8_t *)_data, *end = data + length; - - while (true) { - ssize_t nbytes = write(fd, data, end - data); - if (nbytes > 0) { - data += nbytes; - if (data == end) - return true; - } else if (nbytes == 0) { - /* shouldn't happen for files */ - error.Set(recorder_output_domain, - "write() returned 0"); + /* create the output file */ + + if (!HasDynamicPath()) { + assert(!path.IsNull()); + + file = FileOutputStream::Create(path, error); + if (file == nullptr) return false; - } else if (errno != EINTR) { - error.FormatErrno("Failed to write to '%s'", path); + } else { + /* don't open the file just yet; wait until we have + a tag that we can use to build the path */ + assert(path.IsNull()); + + file = nullptr; + } + + /* open the encoder */ + + if (!encoder->Open(audio_format, error)) { + delete file; + return false; + } + + if (!HasDynamicPath()) { + if (!EncoderToFile(error)) { + encoder->Close(); + delete file; return false; } + } else { + /* remember the AudioFormat for ReopenFormat() */ + effective_audio_format = audio_format; + + /* close the encoder for now; it will be opened as + soon as we have received a tag */ + encoder->Close(); } + + return true; } inline bool -RecorderOutput::EncoderToFile(Error &error) +RecorderOutput::Commit(Error &error) { - assert(fd >= 0); + assert(!path.IsNull()); - while (true) { - /* read from the encoder */ + /* flush the encoder and write the rest to the file */ - size_t size = encoder_read(encoder, buffer, sizeof(buffer)); - if (size == 0) - return true; + bool success = encoder_end(encoder, error) && + EncoderToFile(error); - /* write everything into the file */ + /* now really close everything */ - if (!WriteToFile(buffer, size, error)) - return false; - } + encoder->Close(); + + if (success && !file->Commit(error)) + success = false; + + delete file; + + return success; } -static bool -recorder_output_open(AudioOutput *ao, - AudioFormat &audio_format, - Error &error) +inline void +RecorderOutput::Close() { - RecorderOutput *recorder = (RecorderOutput *)ao; + if (file == nullptr) { + /* not currently encoding to a file; nothing needs to + be done now */ + assert(HasDynamicPath()); + assert(path.IsNull()); + return; + } - /* create the output file */ + Error error; + if (!Commit(error)) + LogError(error); - recorder->fd = open_cloexec(recorder->path, - O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, - 0666); - if (recorder->fd < 0) { - error.FormatErrno("Failed to create '%s'", recorder->path); - return false; + if (HasDynamicPath()) { + assert(!path.IsNull()); + path.SetNull(); } +} - /* open the encoder */ +void +RecorderOutput::FinishFormat() +{ + assert(HasDynamicPath()); + + if (file == nullptr) + return; + + Error error; + if (!Commit(error)) + LogError(error); + + file = nullptr; + path.SetNull(); +} - if (!encoder_open(recorder->encoder, audio_format, error)) { - close(recorder->fd); - unlink(recorder->path); +inline bool +RecorderOutput::ReopenFormat(AllocatedPath &&new_path, Error &error) +{ + assert(HasDynamicPath()); + assert(path.IsNull()); + assert(file == nullptr); + + FileOutputStream *new_file = + FileOutputStream::Create(new_path, error); + if (new_file == nullptr) + return false; + + AudioFormat new_audio_format = effective_audio_format; + if (!encoder->Open(new_audio_format, error)) { + delete new_file; return false; } - if (!recorder->EncoderToFile(error)) { - encoder_close(recorder->encoder); - close(recorder->fd); - unlink(recorder->path); + /* reopening the encoder must always result in the same + AudioFormat as before */ + assert(new_audio_format == effective_audio_format); + + if (!EncoderToOutputStream(*new_file, *encoder, error)) { + encoder->Close(); + delete new_file; return false; } + path = std::move(new_path); + file = new_file; + + FormatDebug(recorder_domain, "Recording to \"%s\"", + path.ToUTF8().c_str()); + return true; } -static void -recorder_output_close(AudioOutput *ao) +inline void +RecorderOutput::SendTag(const Tag &tag) { - RecorderOutput *recorder = (RecorderOutput *)ao; - - /* flush the encoder and write the rest to the file */ + if (HasDynamicPath()) { + char *p = FormatTag(tag, format_path.c_str()); + if (p == nullptr || *p == 0) { + /* no path could be composed with this tag: + don't write a file */ + free(p); + FinishFormat(); + return; + } - if (encoder_end(recorder->encoder, IgnoreError())) - recorder->EncoderToFile(IgnoreError()); + Error error; + AllocatedPath new_path = ParsePath(p, error); + free(p); + if (new_path.IsNull()) { + LogError(error); + FinishFormat(); + return; + } - /* now really close everything */ + if (new_path != path) { + FinishFormat(); - encoder_close(recorder->encoder); + if (!ReopenFormat(std::move(new_path), error)) { + LogError(error); + return; + } + } + } - close(recorder->fd); + Error error; + if (!encoder_pre_tag(encoder, error) || + !EncoderToFile(error) || + !encoder_tag(encoder, tag, error)) + LogError(error); } -static size_t -recorder_output_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +RecorderOutput::Play(const void *chunk, size_t size, Error &error) { - RecorderOutput *recorder = (RecorderOutput *)ao; + if (file == nullptr) { + /* not currently encoding to a file; discard incoming + data */ + assert(HasDynamicPath()); + assert(path.IsNull()); + return size; + } - return encoder_write(recorder->encoder, chunk, size, error) && - recorder->EncoderToFile(error) + return encoder_write(encoder, chunk, size, error) && + EncoderToFile(error) ? size : 0; } +typedef AudioOutputWrapper<RecorderOutput> Wrapper; + const struct AudioOutputPlugin recorder_output_plugin = { "recorder", nullptr, - recorder_output_init, - recorder_output_finish, - nullptr, + &Wrapper::Init, + &Wrapper::Finish, nullptr, - recorder_output_open, - recorder_output_close, nullptr, + &Wrapper::Open, + &Wrapper::Close, nullptr, - recorder_output_play, + &Wrapper::SendTag, + &Wrapper::Play, nullptr, nullptr, nullptr, diff --git a/src/output/plugins/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx index ea1044e0f..061279fba 100644 --- a/src/output/plugins/RecorderOutputPlugin.hxx +++ b/src/output/plugins/RecorderOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx index aa37c91b7..11b0f1671 100644 --- a/src/output/plugins/RoarOutputPlugin.cxx +++ b/src/output/plugins/RoarOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen * @@ -21,6 +21,7 @@ #include "config.h" #include "RoarOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "mixer/MixerList.hxx" #include "thread/Mutex.hxx" #include "util/Error.hxx" @@ -36,6 +37,8 @@ #undef new class RoarOutput { + friend struct AudioOutputWrapper<RoarOutput>; + AudioOutput base; std::string host, name; @@ -57,11 +60,11 @@ public: return &base; } - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); + bool Initialize(const ConfigBlock &block, Error &error) { + return base.Configure(block, error); } - void Configure(const config_param ¶m); + void Configure(const ConfigBlock &block); bool Open(AudioFormat &audio_format, Error &error); void Close(); @@ -121,40 +124,32 @@ roar_output_set_volume(RoarOutput &roar, unsigned volume) } inline void -RoarOutput::Configure(const config_param ¶m) +RoarOutput::Configure(const ConfigBlock &block) { - host = param.GetBlockValue("server", ""); - name = param.GetBlockValue("name", "MPD"); + host = block.GetBlockValue("server", ""); + name = block.GetBlockValue("name", "MPD"); - const char *_role = param.GetBlockValue("role", "music"); + const char *_role = block.GetBlockValue("role", "music"); role = _role != nullptr ? roar_str2role(_role) : ROAR_ROLE_MUSIC; } static AudioOutput * -roar_init(const config_param ¶m, Error &error) +roar_init(const ConfigBlock &block, Error &error) { RoarOutput *self = new RoarOutput(); - if (!self->Initialize(param, error)) { + if (!self->Initialize(block, error)) { delete self; return nullptr; } - self->Configure(param); + self->Configure(block); return *self; } static void -roar_finish(AudioOutput *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - delete self; -} - -static void roar_use_audio_format(struct roar_audio_info *info, AudioFormat &audio_format) { @@ -221,14 +216,6 @@ RoarOutput::Open(AudioFormat &audio_format, Error &error) return true; } -static bool -roar_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - - return self->Open(audio_format, error); -} - inline void RoarOutput::Close() { @@ -242,13 +229,6 @@ RoarOutput::Close() roar_disconnect(&con); } -static void -roar_close(AudioOutput *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - self->Close(); -} - inline void RoarOutput::Cancel() { @@ -277,14 +257,6 @@ RoarOutput::Cancel() alive = true; } -static void -roar_cancel(AudioOutput *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - self->Cancel(); -} - inline size_t RoarOutput::Play(const void *chunk, size_t size, Error &error) { @@ -302,14 +274,6 @@ RoarOutput::Play(const void *chunk, size_t size, Error &error) return nbytes; } -static size_t -roar_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - return self->Play(chunk, size, error); -} - static const char* roar_tag_convert(TagType type, bool *is_uuid) { @@ -407,26 +371,28 @@ RoarOutput::SendTag(const Tag &tag) } static void -roar_send_tag(AudioOutput *ao, const Tag *meta) +roar_send_tag(AudioOutput *ao, const Tag &meta) { RoarOutput *self = (RoarOutput *)ao; - self->SendTag(*meta); + self->SendTag(meta); } +typedef AudioOutputWrapper<RoarOutput> Wrapper; + const struct AudioOutputPlugin roar_output_plugin = { "roar", nullptr, roar_init, - roar_finish, + &Wrapper::Finish, nullptr, nullptr, - roar_open, - roar_close, + &Wrapper::Open, + &Wrapper::Close, nullptr, roar_send_tag, - roar_play, + &Wrapper::Play, nullptr, - roar_cancel, + &Wrapper::Cancel, nullptr, &roar_mixer_plugin, }; diff --git a/src/output/plugins/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx index 5f5a9246e..a9726d5c8 100644 --- a/src/output/plugins/RoarOutputPlugin.hxx +++ b/src/output/plugins/RoarOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx index b51f7ed82..339c4e491 100644 --- a/src/output/plugins/ShoutOutputPlugin.cxx +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "ShoutOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "encoder/EncoderInterface.hxx" #include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderList.hxx" #include "config/ConfigError.hxx" @@ -67,11 +68,11 @@ struct ShoutOutput final { shout_free(shout_conn); } - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); + bool Initialize(const ConfigBlock &block, Error &error) { + return base.Configure(block, error); } - bool Configure(const config_param ¶m, Error &error); + bool Configure(const ConfigBlock &block, Error &error); }; static int shout_init_count; @@ -91,18 +92,18 @@ shout_encoder_plugin_get(const char *name) gcc_pure static const char * -require_block_string(const config_param ¶m, const char *name) +require_block_string(const ConfigBlock &block, const char *name) { - const char *value = param.GetBlockValue(name); + const char *value = block.GetBlockValue(name); if (value == nullptr) FormatFatalError("no \"%s\" defined for shout device defined " - "at line %u\n", name, param.line); + "at line %d\n", name, block.line); return value; } inline bool -ShoutOutput::Configure(const config_param ¶m, Error &error) +ShoutOutput::Configure(const ConfigBlock &block, Error &error) { const AudioFormat audio_format = base.config_audio_format; @@ -112,22 +113,22 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) return false; } - const char *host = require_block_string(param, "host"); - const char *mount = require_block_string(param, "mount"); - unsigned port = param.GetBlockValue("port", 0u); + const char *host = require_block_string(block, "host"); + const char *mount = require_block_string(block, "mount"); + unsigned port = block.GetBlockValue("port", 0u); if (port == 0) { error.Set(config_domain, "shout port must be configured"); return false; } - const char *passwd = require_block_string(param, "password"); - const char *name = require_block_string(param, "name"); + const char *passwd = require_block_string(block, "password"); + const char *name = require_block_string(block, "name"); - bool is_public = param.GetBlockValue("public", false); + bool is_public = block.GetBlockValue("public", false); - const char *user = param.GetBlockValue("user", "source"); + const char *user = block.GetBlockValue("user", "source"); - const char *value = param.GetBlockValue("quality"); + const char *value = block.GetBlockValue("quality"); if (value != nullptr) { char *test; quality = strtod(value, &test); @@ -140,14 +141,14 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) return false; } - if (param.GetBlockValue("bitrate") != nullptr) { + if (block.GetBlockValue("bitrate") != nullptr) { error.Set(config_domain, "quality and bitrate are " "both defined"); return false; } } else { - value = param.GetBlockValue("bitrate"); + value = block.GetBlockValue("bitrate"); if (value == nullptr) { error.Set(config_domain, "neither bitrate nor quality defined"); @@ -164,7 +165,7 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) } } - const char *encoding = param.GetBlockValue("encoding", "ogg"); + const char *encoding = block.GetBlockValue("encoding", "ogg"); const auto encoder_plugin = shout_encoder_plugin_get(encoding); if (encoder_plugin == nullptr) { error.Format(config_domain, @@ -173,7 +174,7 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) return false; } - encoder = encoder_init(*encoder_plugin, param, error); + encoder = encoder_init(*encoder_plugin, block, error); if (encoder == nullptr) return false; @@ -184,7 +185,7 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) shout_format = SHOUT_FORMAT_OGG; unsigned protocol; - value = param.GetBlockValue("protocol"); + value = block.GetBlockValue("protocol"); if (value != nullptr) { if (0 == strcmp(value, "shoutcast") && 0 != strcmp(encoding, "mp3")) { @@ -225,21 +226,21 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) } /* optional paramters */ - timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT); + timeout = block.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT); - value = param.GetBlockValue("genre"); + value = block.GetBlockValue("genre"); if (value != nullptr && shout_set_genre(shout_conn, value)) { error.Set(shout_output_domain, shout_get_error(shout_conn)); return false; } - value = param.GetBlockValue("description"); + value = block.GetBlockValue("description"); if (value != nullptr && shout_set_description(shout_conn, value)) { error.Set(shout_output_domain, shout_get_error(shout_conn)); return false; } - value = param.GetBlockValue("url"); + value = block.GetBlockValue("url"); if (value != nullptr && shout_set_url(shout_conn, value)) { error.Set(shout_output_domain, shout_get_error(shout_conn)); return false; @@ -271,15 +272,15 @@ ShoutOutput::Configure(const config_param ¶m, Error &error) } static AudioOutput * -my_shout_init_driver(const config_param ¶m, Error &error) +my_shout_init_driver(const ConfigBlock &block, Error &error) { ShoutOutput *sd = new ShoutOutput(); - if (!sd->Initialize(param, error)) { + if (!sd->Initialize(block, error)) { delete sd; return nullptr; } - if (!sd->Configure(param, error)) { + if (!sd->Configure(block, error)) { delete sd; return nullptr; } @@ -345,7 +346,7 @@ static void close_shout_conn(ShoutOutput * sd) if (encoder_end(sd->encoder, IgnoreError())) write_page(sd, IgnoreError()); - encoder_close(sd->encoder); + sd->encoder->Close(); } if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && @@ -361,7 +362,7 @@ my_shout_finish_driver(AudioOutput *ao) { ShoutOutput *sd = (ShoutOutput *)ao; - encoder_finish(sd->encoder); + sd->encoder->Dispose(); delete sd; @@ -415,13 +416,13 @@ my_shout_open_device(AudioOutput *ao, AudioFormat &audio_format, if (!shout_connect(sd, error)) return false; - if (!encoder_open(sd->encoder, audio_format, error)) { + if (!sd->encoder->Open(audio_format, error)) { shout_close(sd->shout_conn); return false; } if (!write_page(sd, error)) { - encoder_close(sd->encoder); + sd->encoder->Close(); shout_close(sd->shout_conn); return false; } @@ -462,7 +463,7 @@ my_shout_pause(AudioOutput *ao) } static void -shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) +shout_tag_to_metadata(const Tag &tag, char *dest, size_t size) { char artist[size]; char title[size]; @@ -470,7 +471,7 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) artist[0] = 0; title[0] = 0; - for (const auto &item : *tag) { + for (const auto &item : tag) { switch (item.type) { case TAG_ARTIST: strncpy(artist, item.value, size); @@ -488,7 +489,7 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) } static void my_shout_set_tag(AudioOutput *ao, - const Tag *tag) + const Tag &tag) { ShoutOutput *sd = (ShoutOutput *)ao; diff --git a/src/output/plugins/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx index 9f706fc3b..b103b3bf0 100644 --- a/src/output/plugins/ShoutOutputPlugin.hxx +++ b/src/output/plugins/ShoutOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx index 30745f97c..18c92d361 100644 --- a/src/output/plugins/SolarisOutputPlugin.cxx +++ b/src/output/plugins/SolarisOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -60,8 +60,8 @@ struct SolarisOutput { SolarisOutput() :base(solaris_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + bool Initialize(const ConfigBlock &block, Error &error_r) { + return base.Configure(block, error_r); } }; @@ -75,15 +75,15 @@ solaris_output_test_default_device(void) } static AudioOutput * -solaris_output_init(const config_param ¶m, Error &error_r) +solaris_output_init(const ConfigBlock &block, Error &error_r) { SolarisOutput *so = new SolarisOutput(); - if (!so->Initialize(param, error_r)) { + if (!so->Initialize(block, error_r)) { delete so; return nullptr; } - so->device = param.GetBlockValue("device", "/dev/audio"); + so->device = block.GetBlockValue("device", "/dev/audio"); return &so->base; } diff --git a/src/output/plugins/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx index 3f9ede7a6..f6f32504a 100644 --- a/src/output/plugins/SolarisOutputPlugin.hxx +++ b/src/output/plugins/SolarisOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx index e5c5a6f0c..35efb0f93 100644 --- a/src/output/plugins/WinmmOutputPlugin.cxx +++ b/src/output/plugins/WinmmOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,9 +22,11 @@ #include "../OutputAPI.hxx" #include "pcm/PcmBuffer.hxx" #include "mixer/MixerList.hxx" +#include "fs/AllocatedPath.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "util/Macros.hxx" +#include "util/StringUtil.hxx" #include <stdlib.h> #include <string.h> @@ -56,6 +58,18 @@ struct WinmmOutput { static constexpr Domain winmm_output_domain("winmm_output"); +static void +SetWaveOutError(Error &error, MMRESULT result, const char *prefix) +{ + char buffer[256]; + if (waveOutGetErrorTextA(result, buffer, + ARRAY_SIZE(buffer)) == MMSYSERR_NOERROR) + error.Format(winmm_output_domain, int(result), + "%s: %s", prefix, buffer); + else + error.Set(winmm_output_domain, int(result), prefix); +} + HWAVEOUT winmm_output_get_handle(WinmmOutput &output) { @@ -83,13 +97,23 @@ get_device_id(const char *device_name, UINT *device_id, Error &error) char *endptr; UINT id = strtoul(device_name, &endptr, 0); if (endptr > device_name && *endptr == 0) { - if (id >= numdevs) - goto fail; + if (id >= numdevs) { + error.Format(winmm_output_domain, + "device \"%s\" is not found", + device_name); + return false; + } + *device_id = id; return true; } /* check for device name */ + const AllocatedPath device_name_fs = + AllocatedPath::FromUTF8(device_name, error); + if (device_name_fs.IsNull()) + return false; + for (UINT i = 0; i < numdevs; i++) { WAVEOUTCAPS caps; MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); @@ -97,28 +121,27 @@ get_device_id(const char *device_name, UINT *device_id, Error &error) continue; /* szPname is only 32 chars long, so it is often truncated. Use partial match to work around this. */ - if (strstr(device_name, caps.szPname) == device_name) { + if (StringStartsWith(device_name_fs.c_str(), caps.szPname)) { *device_id = i; return true; } } -fail: error.Format(winmm_output_domain, "device \"%s\" is not found", device_name); return false; } static AudioOutput * -winmm_output_init(const config_param ¶m, Error &error) +winmm_output_init(const ConfigBlock &block, Error &error) { WinmmOutput *wo = new WinmmOutput(); - if (!wo->base.Configure(param, error)) { + if (!wo->base.Configure(block, error)) { delete wo; return nullptr; } - const char *device = param.GetBlockValue("device"); + const char *device = block.GetBlockValue("device"); if (!get_device_id(device, &wo->device_id, error)) { delete wo; return nullptr; @@ -179,7 +202,7 @@ winmm_output_open(AudioOutput *ao, AudioFormat &audio_format, (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); if (result != MMSYSERR_NOERROR) { CloseHandle(wo->event); - error.Set(winmm_output_domain, "waveOutOpen() failed"); + SetWaveOutError(error, result, "waveOutOpen() failed"); return false; } @@ -225,8 +248,8 @@ winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); if (result != MMSYSERR_NOERROR) { - error.Set(winmm_output_domain, result, - "waveOutPrepareHeader() failed"); + SetWaveOutError(error, result, + "waveOutPrepareHeader() failed"); return false; } @@ -251,8 +274,8 @@ winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, if (result == MMSYSERR_NOERROR) return true; else if (result != WAVERR_STILLPLAYING) { - error.Set(winmm_output_domain, result, - "waveOutUnprepareHeader() failed"); + SetWaveOutError(error, result, + "waveOutUnprepareHeader() failed"); return false; } @@ -278,8 +301,7 @@ winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) if (result != MMSYSERR_NOERROR) { waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); - error.Set(winmm_output_domain, result, - "waveOutWrite() failed"); + SetWaveOutError(error, result, "waveOutWrite() failed"); return 0; } diff --git a/src/output/plugins/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx index 50fae4f2f..6c8fcc627 100644 --- a/src/output/plugins/WinmmOutputPlugin.hxx +++ b/src/output/plugins/WinmmOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx index 3797c3d26..99210c1fd 100644 --- a/src/output/plugins/httpd/HttpdClient.cxx +++ b/src/output/plugins/httpd/HttpdClient.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,7 +23,7 @@ #include "util/ASCII.hxx" #include "Page.hxx" #include "IcyMetaDataServer.hxx" -#include "system/SocketError.hxx" +#include "net/SocketError.hxx" #include "Log.hxx" #include <assert.h> diff --git a/src/output/plugins/httpd/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx index f94f05769..6646ddf4c 100644 --- a/src/output/plugins/httpd/HttpdClient.hxx +++ b/src/output/plugins/httpd/HttpdClient.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,8 @@ #include "event/BufferedSocket.hxx" #include "Compiler.h" +#include <boost/intrusive/list.hpp> + #include <queue> #include <list> @@ -31,7 +33,9 @@ class HttpdOutput; class Page; -class HttpdClient final : BufferedSocket { +class HttpdClient final + : BufferedSocket, + public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> { /** * The httpd output object this client is connected to. */ @@ -124,7 +128,7 @@ class HttpdClient final : BufferedSocket { public: /** * @param httpd the HTTP output device - * @param fd the socket file descriptor + * @param _fd the socket file descriptor */ HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop, bool _metadata_supported); diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx index 20ff15e42..01498dfcd 100644 --- a/src/output/plugins/httpd/HttpdInternal.hxx +++ b/src/output/plugins/httpd/HttpdInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H +#include "HttpdClient.hxx" #include "output/Internal.hxx" #include "output/Timer.hxx" #include "thread/Mutex.hxx" @@ -33,16 +34,10 @@ #include "util/Cast.hxx" #include "Compiler.h" -#ifdef _LIBCPP_VERSION -/* can't use incomplete template arguments with libc++ */ -#include "HttpdClient.hxx" -#endif - -#include <forward_list> #include <queue> #include <list> -struct config_param; +struct ConfigBlock; class Error; class EventLoop; class ServerSocket; @@ -135,7 +130,8 @@ private: * A linked list containing all clients which are currently * connected. */ - std::forward_list<HttpdClient> clients; + boost::intrusive::list<HttpdClient, + boost::intrusive::constant_time_size<true>> clients; /** * A temporary buffer for the httpd_output_read_page() @@ -147,13 +143,13 @@ private: * The maximum and current number of clients connected * at the same time. */ - unsigned clients_max, clients_cnt; + unsigned clients_max; public: HttpdOutput(EventLoop &_loop); ~HttpdOutput(); -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) constexpr #endif static HttpdOutput *Cast(AudioOutput *ao) { @@ -162,16 +158,16 @@ public: using DeferredMonitor::GetEventLoop; - bool Init(const config_param ¶m, Error &error); + bool Init(const ConfigBlock &block, Error &error); - bool Configure(const config_param ¶m, Error &error); + bool Configure(const ConfigBlock &block, Error &error); - AudioOutput *InitAndConfigure(const config_param ¶m, + AudioOutput *InitAndConfigure(const ConfigBlock &block, Error &error) { - if (!Init(param, error)) + if (!Init(block, error)) return nullptr; - if (!Configure(param, error)) + if (!Configure(block, error)) return nullptr; return &base; @@ -250,7 +246,7 @@ public: bool EncodeAndPlay(const void *chunk, size_t size, Error &error); - void SendTag(const Tag *tag); + void SendTag(const Tag &tag); size_t Play(const void *chunk, size_t size, Error &error); @@ -259,8 +255,7 @@ public: private: virtual void RunDeferred() override; - virtual void OnAccept(int fd, const sockaddr &address, - size_t address_length, int uid) override; + void OnAccept(int fd, SocketAddress address, int uid) override; }; extern const class Domain httpd_output_domain; diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx index e3ba7727d..765a72d92 100644 --- a/src/output/plugins/httpd/HttpdOutputPlugin.cxx +++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,9 +22,11 @@ #include "HttpdInternal.hxx" #include "HttpdClient.hxx" #include "output/OutputAPI.hxx" +#include "encoder/EncoderInterface.hxx" #include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderList.hxx" -#include "system/Resolver.hxx" +#include "net/SocketAddress.hxx" +#include "net/ToString.hxx" #include "Page.hxx" #include "IcyMetaDataServer.hxx" #include "system/fd_util.h" @@ -32,6 +34,7 @@ #include "event/Call.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "util/DeleteDisposer.hxx" #include "Log.hxx" #include <assert.h> @@ -63,7 +66,7 @@ HttpdOutput::~HttpdOutput() metadata->Unref(); if (encoder != nullptr) - encoder_finish(encoder); + encoder->Dispose(); } @@ -90,17 +93,17 @@ HttpdOutput::Unbind() } inline bool -HttpdOutput::Configure(const config_param ¶m, Error &error) +HttpdOutput::Configure(const ConfigBlock &block, Error &error) { /* read configuration */ - name = param.GetBlockValue("name", "Set name in config"); - genre = param.GetBlockValue("genre", "Set genre in config"); - website = param.GetBlockValue("website", "Set website in config"); + name = block.GetBlockValue("name", "Set name in config"); + genre = block.GetBlockValue("genre", "Set genre in config"); + website = block.GetBlockValue("website", "Set website in config"); - unsigned port = param.GetBlockValue("port", 8000u); + unsigned port = block.GetBlockValue("port", 8000u); const char *encoder_name = - param.GetBlockValue("encoder", "vorbis"); + block.GetBlockValue("encoder", "vorbis"); const auto encoder_plugin = encoder_plugin_get(encoder_name); if (encoder_plugin == nullptr) { error.Format(httpd_output_domain, @@ -108,11 +111,11 @@ HttpdOutput::Configure(const config_param ¶m, Error &error) return false; } - clients_max = param.GetBlockValue("max_clients", 0u); + clients_max = block.GetBlockValue("max_clients", 0u); /* set up bind_to_address */ - const char *bind_to_address = param.GetBlockValue("bind_to_address"); + const char *bind_to_address = block.GetBlockValue("bind_to_address"); bool success = bind_to_address != nullptr && strcmp(bind_to_address, "any") != 0 ? AddHost(bind_to_address, port, error) @@ -122,7 +125,7 @@ HttpdOutput::Configure(const config_param ¶m, Error &error) /* initialize encoder */ - encoder = encoder_init(*encoder_plugin, param, error); + encoder = encoder_init(*encoder_plugin, block, error); if (encoder == nullptr) return false; @@ -135,17 +138,17 @@ HttpdOutput::Configure(const config_param ¶m, Error &error) } inline bool -HttpdOutput::Init(const config_param ¶m, Error &error) +HttpdOutput::Init(const ConfigBlock &block, Error &error) { - return base.Configure(param, error); + return base.Configure(block, error); } static AudioOutput * -httpd_output_init(const config_param ¶m, Error &error) +httpd_output_init(const ConfigBlock &block, Error &error) { HttpdOutput *httpd = new HttpdOutput(io_thread_get()); - AudioOutput *result = httpd->InitAndConfigure(param, error); + AudioOutput *result = httpd->InitAndConfigure(block, error); if (result == nullptr) delete httpd; @@ -167,9 +170,9 @@ httpd_output_finish(AudioOutput *ao) inline void HttpdOutput::AddClient(int fd) { - clients.emplace_front(*this, fd, GetEventLoop(), - encoder->plugin.tag == nullptr); - ++clients_cnt; + auto *client = new HttpdClient(*this, fd, GetEventLoop(), + encoder->plugin.tag == nullptr); + clients.push_front(*client); /* pass metadata to client */ if (metadata != nullptr) @@ -200,16 +203,14 @@ HttpdOutput::RunDeferred() } void -HttpdOutput::OnAccept(int fd, const sockaddr &address, - size_t address_length, gcc_unused int uid) +HttpdOutput::OnAccept(int fd, SocketAddress address, gcc_unused int uid) { /* the listener socket has become readable - a client has connected */ #ifdef HAVE_LIBWRAP - if (address.sa_family != AF_UNIX) { - const auto hostaddr = sockaddr_to_string(&address, - address_length); + if (address.GetFamily() != AF_UNIX) { + const auto hostaddr = ToString(address); // TODO: shall we obtain the program name from argv[0]? const char *progname = "mpd"; @@ -229,14 +230,13 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address, } #else (void)address; - (void)address_length; #endif /* HAVE_WRAP */ const ScopeLock protect(mutex); if (fd >= 0) { /* can we allow additional client */ - if (open && (clients_max == 0 || clients_cnt < clients_max)) + if (open && (clients_max == 0 || clients.size() < clients_max)) AddClient(fd); else close_socket(fd); @@ -294,7 +294,7 @@ httpd_output_disable(AudioOutput *ao) inline bool HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error) { - if (!encoder_open(encoder, audio_format, error)) + if (!encoder->Open(audio_format, error)) return false; /* we have to remember the encoder header, i.e. the first @@ -320,7 +320,6 @@ HttpdOutput::Open(AudioFormat &audio_format, Error &error) /* initialize other attributes */ - clients_cnt = 0; timer = new Timer(audio_format); open = true; @@ -348,13 +347,13 @@ HttpdOutput::Close() delete timer; BlockingCall(GetEventLoop(), [this](){ - clients.clear(); + clients.clear_and_dispose(DeleteDisposer()); }); if (header != nullptr) header->Unref(); - encoder_close(encoder); + encoder->Close(); } static void @@ -369,17 +368,10 @@ httpd_output_close(AudioOutput *ao) void HttpdOutput::RemoveClient(HttpdClient &client) { - assert(clients_cnt > 0); + assert(!clients.empty()); - for (auto prev = clients.before_begin(), i = std::next(prev);; - prev = i, i = std::next(prev)) { - assert(i != clients.end()); - if (&*i == &client) { - clients.erase_after(prev); - clients_cnt--; - break; - } - } + clients.erase_and_dispose(clients.iterator_to(client), + DeleteDisposer()); } void @@ -499,10 +491,8 @@ httpd_output_pause(AudioOutput *ao) } inline void -HttpdOutput::SendTag(const Tag *tag) +HttpdOutput::SendTag(const Tag &tag) { - assert(tag != nullptr); - if (encoder->plugin.tag != nullptr) { /* embed encoder tags */ @@ -538,7 +528,7 @@ HttpdOutput::SendTag(const Tag *tag) TAG_NUM_OF_ITEM_TYPES }; - metadata = icy_server_metadata_page(*tag, &types[0]); + metadata = icy_server_metadata_page(tag, &types[0]); if (metadata != nullptr) { const ScopeLock protect(mutex); for (auto &client : clients) @@ -548,7 +538,7 @@ HttpdOutput::SendTag(const Tag *tag) } static void -httpd_output_tag(AudioOutput *ao, const Tag *tag) +httpd_output_tag(AudioOutput *ao, const Tag &tag) { HttpdOutput *httpd = HttpdOutput::Cast(ao); diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.hxx b/src/output/plugins/httpd/HttpdOutputPlugin.hxx index df99e2b43..8280d9fb2 100644 --- a/src/output/plugins/httpd/HttpdOutputPlugin.hxx +++ b/src/output/plugins/httpd/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx index 146df23d1..fe841ac11 100644 --- a/src/output/plugins/httpd/IcyMetaDataServer.cxx +++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,8 @@ #include "Page.hxx" #include "tag/Tag.hxx" #include "util/FormatString.hxx" - -#include <glib.h> +#include "util/StringUtil.hxx" +#include "util/Macros.hxx" #include <string.h> @@ -57,16 +57,13 @@ icy_server_metadata_header(const char *name, 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); + char *icy_metadata = FormatNew("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); - meta_length = strlen(icy_metadata); + size_t meta_length = strlen(icy_metadata); meta_length--; // subtract placeholder @@ -85,43 +82,30 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url) 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; + const char *tag_items[TAG_NUM_OF_ITEM_TYPES]; + int last_item = -1; while (*types != TAG_NUM_OF_ITEM_TYPES) { - const gchar *tag_item = tag.GetValue(*types++); + const char *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); + int item = 0; - position += length; + // Length + Metadata - "StreamTitle='';StreamUrl='';" = 4081 - 28 + char stream_title[(1 + 255 - 28) * 16]; + char *p = stream_title, *const end = stream_title + ARRAY_SIZE(stream_title); + stream_title[0] = '\0'; - if (item <= last_item) { - length = g_strlcpy(stream_title + position, - " - ", - sizeof(stream_title) - position); + while (p < end && item <= last_item) { + p = CopyString(p, tag_items[item++], end - p); - position += length; - } + if (item <= last_item) + p = CopyString(p, " - ", end - p); } - icy_string = icy_server_metadata_string(stream_title, ""); + char *icy_string = icy_server_metadata_string(stream_title, ""); if (icy_string == nullptr) return nullptr; diff --git a/src/output/plugins/httpd/IcyMetaDataServer.hxx b/src/output/plugins/httpd/IcyMetaDataServer.hxx index 773b46641..38415e5bd 100644 --- a/src/output/plugins/httpd/IcyMetaDataServer.hxx +++ b/src/output/plugins/httpd/IcyMetaDataServer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/Page.cxx b/src/output/plugins/httpd/Page.cxx index e22134bbc..ff7036645 100644 --- a/src/output/plugins/httpd/Page.cxx +++ b/src/output/plugins/httpd/Page.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/Page.hxx b/src/output/plugins/httpd/Page.hxx index 95f35d06a..88b7c2d85 100644 --- a/src/output/plugins/httpd/Page.hxx +++ b/src/output/plugins/httpd/Page.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx index 85fd9f2f2..1e23cd2cc 100644 --- a/src/output/plugins/sles/SlesOutputPlugin.cxx +++ b/src/output/plugins/sles/SlesOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "Play.hxx" #include "AndroidSimpleBufferQueue.hxx" #include "../../OutputAPI.hxx" +#include "../../Wrapper.hxx" #include "util/Macros.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -34,6 +35,8 @@ #include <SLES/OpenSLES_Android.h> class SlesOutput { + friend struct AudioOutputWrapper<SlesOutput>; + static constexpr unsigned N_BUFFERS = 3; static constexpr size_t BUFFER_SIZE = 65536; @@ -88,11 +91,13 @@ public: return &base; } - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); + bool Initialize(const ConfigBlock &block, Error &error) { + return base.Configure(block, error); } - bool Configure(const config_param ¶m, Error &error); + bool Configure(const ConfigBlock &block, Error &error); + + static SlesOutput *Create(const ConfigBlock &block, Error &error); bool Open(AudioFormat &audio_format, Error &error); void Close(); @@ -126,7 +131,7 @@ private: static constexpr Domain sles_domain("sles"); inline bool -SlesOutput::Configure(const config_param &, Error &) +SlesOutput::Configure(const ConfigBlock &, Error &) { return true; } @@ -441,99 +446,36 @@ sles_test_default_device() return true; } -static AudioOutput * -sles_output_init(const config_param ¶m, Error &error) +inline SlesOutput * +SlesOutput::Create(const ConfigBlock &block, Error &error) { SlesOutput *sles = new SlesOutput(); - if (!sles->Initialize(param, error) || - !sles->Configure(param, error)) { + if (!sles->Initialize(block, error) || + !sles->Configure(block, 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); + return sles; } -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(); -} +typedef AudioOutputWrapper<SlesOutput> Wrapper; const struct AudioOutputPlugin sles_output_plugin = { "sles", sles_test_default_device, - sles_output_init, - sles_output_finish, + &Wrapper::Init, + &Wrapper::Finish, nullptr, nullptr, - sles_output_open, - sles_output_close, - sles_output_delay, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - sles_output_play, - sles_output_drain, - sles_output_cancel, - sles_output_pause, + &Wrapper::Play, + &Wrapper::Drain, + &Wrapper::Cancel, + &Wrapper::Pause, nullptr, }; diff --git a/src/output/plugins/sles/SlesOutputPlugin.hxx b/src/output/plugins/sles/SlesOutputPlugin.hxx index 5424dec2e..5a7595c24 100644 --- a/src/output/plugins/sles/SlesOutputPlugin.hxx +++ b/src/output/plugins/sles/SlesOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify |