diff options
Diffstat (limited to 'src/mixer')
21 files changed, 1329 insertions, 548 deletions
diff --git a/src/mixer/Listener.hxx b/src/mixer/Listener.hxx new file mode 100644 index 000000000..6f48fbd4d --- /dev/null +++ b/src/mixer/Listener.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MIXER_LISTENER_HXX +#define MPD_MIXER_LISTENER_HXX + +class Mixer; + +/** + * An interface that listens on events from mixer plugins. The + * methods must be thread-safe and non-blocking. + */ +class MixerListener { +public: + virtual void OnMixerVolumeChanged(Mixer &mixer, int volume) = 0; +}; + +#endif diff --git a/src/mixer/MixerAll.cxx b/src/mixer/MixerAll.cxx new file mode 100644 index 000000000..5fef6a92f --- /dev/null +++ b/src/mixer/MixerAll.cxx @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output/MultipleOutputs.hxx" +#include "MixerControl.hxx" +#include "MixerInternal.hxx" +#include "MixerList.hxx" +#include "output/Internal.hxx" +#include "pcm/Volume.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +static constexpr Domain mixer_domain("mixer"); + +static int +output_mixer_get_volume(const AudioOutput &ao) +{ + if (!ao.enabled) + return -1; + + Mixer *mixer = ao.mixer; + if (mixer == nullptr) + return -1; + + Error error; + int volume = mixer_get_volume(mixer, error); + if (volume < 0 && error.IsDefined()) + FormatError(error, + "Failed to read mixer for '%s'", + ao.name); + + return volume; +} + +int +MultipleOutputs::GetVolume() const +{ + unsigned ok = 0; + int total = 0; + + for (auto ao : outputs) { + int volume = output_mixer_get_volume(*ao); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +static bool +output_mixer_set_volume(AudioOutput &ao, unsigned volume) +{ + assert(volume <= 100); + + if (!ao.enabled) + return false; + + Mixer *mixer = ao.mixer; + if (mixer == nullptr) + return false; + + Error error; + bool success = mixer_set_volume(mixer, volume, error); + if (!success && error.IsDefined()) + FormatError(error, + "Failed to set mixer for '%s'", + ao.name); + + return success; +} + +bool +MultipleOutputs::SetVolume(unsigned volume) +{ + assert(volume <= 100); + + bool success = false; + for (auto ao : outputs) + success = output_mixer_set_volume(*ao, volume) + || success; + + return success; +} + +static int +output_mixer_get_software_volume(const AudioOutput &ao) +{ + if (!ao.enabled) + return -1; + + Mixer *mixer = ao.mixer; + if (mixer == nullptr || !mixer->IsPlugin(software_mixer_plugin)) + return -1; + + return mixer_get_volume(mixer, IgnoreError()); +} + +int +MultipleOutputs::GetSoftwareVolume() const +{ + unsigned ok = 0; + int total = 0; + + for (auto ao : outputs) { + int volume = output_mixer_get_software_volume(*ao); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +void +MultipleOutputs::SetSoftwareVolume(unsigned volume) +{ + assert(volume <= PCM_VOLUME_1); + + for (auto ao : outputs) { + const auto mixer = ao->mixer; + + if (mixer != nullptr && + &mixer->plugin == &software_mixer_plugin) + mixer_set_volume(mixer, volume, IgnoreError()); + } +} diff --git a/src/mixer/MixerControl.cxx b/src/mixer/MixerControl.cxx new file mode 100644 index 000000000..6d08140db --- /dev/null +++ b/src/mixer/MixerControl.cxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MixerControl.hxx" +#include "MixerInternal.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +Mixer * +mixer_new(EventLoop &event_loop, + const MixerPlugin &plugin, AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error) +{ + Mixer *mixer = plugin.init(event_loop, ao, listener, param, error); + + assert(mixer == nullptr || mixer->IsPlugin(plugin)); + + return mixer; +} + +void +mixer_free(Mixer *mixer) +{ + assert(mixer != nullptr); + + /* mixers with the "global" flag set might still be open at + this point (see mixer_auto_close()) */ + mixer_close(mixer); + + delete mixer; +} + +bool +mixer_open(Mixer *mixer, Error &error) +{ + bool success; + + assert(mixer != nullptr); + + const ScopeLock protect(mixer->mutex); + + success = mixer->open || (mixer->open = mixer->Open(error)); + + mixer->failed = !success; + + return success; +} + +static void +mixer_close_internal(Mixer *mixer) +{ + assert(mixer != nullptr); + assert(mixer->open); + + mixer->Close(); + mixer->open = false; +} + +void +mixer_close(Mixer *mixer) +{ + assert(mixer != nullptr); + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) + mixer_close_internal(mixer); +} + +void +mixer_auto_close(Mixer *mixer) +{ + if (!mixer->plugin.global) + mixer_close(mixer); +} + +/* + * Close the mixer due to failure. The mutex must be locked before + * calling this function. + */ +static void +mixer_failed(Mixer *mixer) +{ + assert(mixer->open); + + mixer_close_internal(mixer); + + mixer->failed = true; +} + +int +mixer_get_volume(Mixer *mixer, Error &error) +{ + int volume; + + assert(mixer != nullptr); + + if (mixer->plugin.global && !mixer->failed && + !mixer_open(mixer, error)) + return -1; + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) { + volume = mixer->GetVolume(error); + if (volume < 0 && error.IsDefined()) + mixer_failed(mixer); + } else + volume = -1; + + return volume; +} + +bool +mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +{ + assert(mixer != nullptr); + assert(volume <= 100); + + if (mixer->plugin.global && !mixer->failed && + !mixer_open(mixer, error)) + return false; + + const ScopeLock protect(mixer->mutex); + + return mixer->open && mixer->SetVolume(volume, error); +} diff --git a/src/mixer/MixerControl.hxx b/src/mixer/MixerControl.hxx new file mode 100644 index 000000000..75255d98c --- /dev/null +++ b/src/mixer/MixerControl.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Functions which manipulate a #mixer object. + */ + +#ifndef MPD_MIXER_CONTROL_HXX +#define MPD_MIXER_CONTROL_HXX + +class Error; +class Mixer; +class EventLoop; +struct AudioOutput; +struct MixerPlugin; +class MixerListener; +struct config_param; + +Mixer * +mixer_new(EventLoop &event_loop, const MixerPlugin &plugin, AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error); + +void +mixer_free(Mixer *mixer); + +bool +mixer_open(Mixer *mixer, Error &error); + +void +mixer_close(Mixer *mixer); + +/** + * Close the mixer unless the plugin's "global" flag is set. This is + * called when the #AudioOutput is closed. + */ +void +mixer_auto_close(Mixer *mixer); + +int +mixer_get_volume(Mixer *mixer, Error &error); + +bool +mixer_set_volume(Mixer *mixer, unsigned volume, Error &error); + +#endif diff --git a/src/mixer/MixerInternal.hxx b/src/mixer/MixerInternal.hxx new file mode 100644 index 000000000..7b2cf2b32 --- /dev/null +++ b/src/mixer/MixerInternal.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MIXER_INTERNAL_HXX +#define MPD_MIXER_INTERNAL_HXX + +#include "MixerPlugin.hxx" +#include "MixerList.hxx" +#include "thread/Mutex.hxx" +#include "Compiler.h" + +class MixerListener; + +class Mixer { +public: + const MixerPlugin &plugin; + + MixerListener &listener; + + /** + * This mutex protects all of the mixer struct, including its + * implementation, so plugins don't have to deal with that. + */ + Mutex mutex; + + /** + * Is the mixer device currently open? + */ + bool open; + + /** + * Has this mixer failed, and should not be reopened + * automatically? + */ + bool failed; + +public: + explicit Mixer(const MixerPlugin &_plugin, MixerListener &_listener) + :plugin(_plugin), listener(_listener), + open(false), + failed(false) {} + + Mixer(const Mixer &) = delete; + + virtual ~Mixer() {} + + bool IsPlugin(const MixerPlugin &other) const { + return &plugin == &other; + } + + /** + * Open mixer device + * + * @return true on success, false on error + */ + virtual bool Open(Error &error) = 0; + + /** + * Close mixer device + */ + virtual void Close() = 0; + + /** + * Reads the current volume. + * + * @return the current volume (0..100 including) or -1 if + * unavailable or on error (error set, mixer will be closed) + */ + gcc_pure + virtual int GetVolume(Error &error) = 0; + + /** + * Sets the volume. + * + * @param volume the new volume (0..100 including) @return + * true on success, false on error + */ + virtual bool SetVolume(unsigned volume, Error &error) = 0; +}; + +#endif diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx new file mode 100644 index 000000000..e75b2e6ff --- /dev/null +++ b/src/mixer/MixerList.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header provides "extern" declarations for all mixer plugins. + */ + +#ifndef MPD_MIXER_LIST_HXX +#define MPD_MIXER_LIST_HXX + +struct MixerPlugin; + +extern const MixerPlugin software_mixer_plugin; +extern const MixerPlugin alsa_mixer_plugin; +extern const MixerPlugin oss_mixer_plugin; +extern const MixerPlugin roar_mixer_plugin; +extern const MixerPlugin pulse_mixer_plugin; +extern const MixerPlugin winmm_mixer_plugin; + +#endif diff --git a/src/mixer/MixerPlugin.hxx b/src/mixer/MixerPlugin.hxx new file mode 100644 index 000000000..02bae844e --- /dev/null +++ b/src/mixer/MixerPlugin.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the mixer_plugin class. It should not be + * included directly; use MixerInternal.hxx instead in mixer + * implementations. + */ + +#ifndef MPD_MIXER_PLUGIN_HXX +#define MPD_MIXER_PLUGIN_HXX + +struct config_param; +struct AudioOutput; +class Mixer; +class MixerListener; +class EventLoop; +class Error; + +struct MixerPlugin { + /** + * Alocates and configures a mixer device. + * + * @param ao the associated AudioOutput + * @param param the configuration section + * @param error_r location to store the error occurring, or + * nullptr to ignore errors + * @return a mixer object, or nullptr on error + */ + Mixer *(*init)(EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error); + + /** + * If true, then the mixer is automatically opened, even if + * its audio output is not open. If false, then the mixer is + * disabled as long as its audio output is closed. + */ + bool global; +}; + +#endif diff --git a/src/mixer/MixerType.cxx b/src/mixer/MixerType.cxx new file mode 100644 index 000000000..cd45db0d9 --- /dev/null +++ b/src/mixer/MixerType.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MixerType.hxx" + +#include <assert.h> +#include <string.h> + +enum mixer_type +mixer_type_parse(const char *input) +{ + assert(input != NULL); + + if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0) + return MIXER_TYPE_NONE; + else if (strcmp(input, "hardware") == 0) + return MIXER_TYPE_HARDWARE; + else if (strcmp(input, "software") == 0) + return MIXER_TYPE_SOFTWARE; + else + return MIXER_TYPE_UNKNOWN; +} diff --git a/src/mixer/MixerType.hxx b/src/mixer/MixerType.hxx new file mode 100644 index 000000000..bfa2637b7 --- /dev/null +++ b/src/mixer/MixerType.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MIXER_TYPE_HXX +#define MPD_MIXER_TYPE_HXX + +enum mixer_type { + /** parser error */ + MIXER_TYPE_UNKNOWN, + + /** mixer disabled */ + MIXER_TYPE_NONE, + + /** software mixer with pcm_volume() */ + MIXER_TYPE_SOFTWARE, + + /** hardware mixer (output's plugin) */ + MIXER_TYPE_HARDWARE, +}; + +/** + * Parses a "mixer_type" setting from the configuration file. + * + * @param input the configured string value; must not be NULL + * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could + * not be parsed + */ +enum mixer_type +mixer_type_parse(const char *input); + +#endif diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx deleted file mode 100644 index ff10256cb..000000000 --- a/src/mixer/PulseMixerPlugin.cxx +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "PulseMixerPlugin.hxx" -#include "MixerInternal.hxx" -#include "output/PulseOutputPlugin.hxx" -#include "GlobalEvents.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <pulse/thread-mainloop.h> -#include <pulse/context.h> -#include <pulse/introspect.h> -#include <pulse/stream.h> -#include <pulse/subscribe.h> -#include <pulse/error.h> - -#include <assert.h> -#include <string.h> - -struct PulseMixer final : public Mixer { - PulseOutput *output; - - bool online; - struct pa_cvolume volume; - - PulseMixer(PulseOutput *_output) - :Mixer(pulse_mixer_plugin), - output(_output), online(false) - { - } -}; - -static constexpr Domain pulse_mixer_domain("pulse_mixer"); - -static void -pulse_mixer_offline(PulseMixer *pm) -{ - if (!pm->online) - return; - - pm->online = false; - - GlobalEvents::Emit(GlobalEvents::MIXER); -} - -/** - * Callback invoked by pulse_mixer_update(). Receives the new mixer - * value. - */ -static void -pulse_mixer_volume_cb(gcc_unused pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) -{ - PulseMixer *pm = (PulseMixer *)userdata; - - if (eol) - return; - - if (i == nullptr) { - pulse_mixer_offline(pm); - return; - } - - pm->online = true; - pm->volume = i->volume; - - GlobalEvents::Emit(GlobalEvents::MIXER); -} - -static void -pulse_mixer_update(PulseMixer *pm, - struct pa_context *context, struct pa_stream *stream) -{ - pa_operation *o; - - assert(context != nullptr); - assert(stream != nullptr); - assert(pa_stream_get_state(stream) == PA_STREAM_READY); - - o = pa_context_get_sink_input_info(context, - pa_stream_get_index(stream), - pulse_mixer_volume_cb, pm); - if (o == nullptr) { - FormatError(pulse_mixer_domain, - "pa_context_get_sink_input_info() failed: %s", - pa_strerror(pa_context_errno(context))); - pulse_mixer_offline(pm); - return; - } - - pa_operation_unref(o); -} - -void -pulse_mixer_on_connect(gcc_unused PulseMixer *pm, - struct pa_context *context) -{ - pa_operation *o; - - assert(context != nullptr); - - o = pa_context_subscribe(context, - (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, - nullptr, nullptr); - if (o == nullptr) { - FormatError(pulse_mixer_domain, - "pa_context_subscribe() failed: %s", - pa_strerror(pa_context_errno(context))); - return; - } - - pa_operation_unref(o); -} - -void -pulse_mixer_on_disconnect(PulseMixer *pm) -{ - pulse_mixer_offline(pm); -} - -void -pulse_mixer_on_change(PulseMixer *pm, - struct pa_context *context, struct pa_stream *stream) -{ - pulse_mixer_update(pm, context, stream); -} - -static Mixer * -pulse_mixer_init(void *ao, gcc_unused const config_param ¶m, - Error &error) -{ - PulseOutput *po = (PulseOutput *)ao; - - if (ao == nullptr) { - error.Set(pulse_mixer_domain, - "The pulse mixer cannot work without the audio output"); - return nullptr; - } - - PulseMixer *pm = new PulseMixer(po); - - pulse_output_set_mixer(po, pm); - - return pm; -} - -static void -pulse_mixer_finish(Mixer *data) -{ - PulseMixer *pm = (PulseMixer *) data; - - pulse_output_clear_mixer(pm->output, pm); - - delete pm; -} - -static int -pulse_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) -{ - PulseMixer *pm = (PulseMixer *) mixer; - int ret; - - pulse_output_lock(pm->output); - - ret = pm->online - ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) - : -1; - - pulse_output_unlock(pm->output); - - return ret; -} - -static bool -pulse_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - PulseMixer *pm = (PulseMixer *) mixer; - struct pa_cvolume cvolume; - bool success; - - pulse_output_lock(pm->output); - - if (!pm->online) { - pulse_output_unlock(pm->output); - error.Set(pulse_mixer_domain, "disconnected"); - return false; - } - - pa_cvolume_set(&cvolume, pm->volume.channels, - (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); - success = pulse_output_set_volume(pm->output, &cvolume, error); - if (success) - pm->volume = cvolume; - - pulse_output_unlock(pm->output); - - return success; -} - -const struct mixer_plugin pulse_mixer_plugin = { - pulse_mixer_init, - pulse_mixer_finish, - nullptr, - nullptr, - pulse_mixer_get_volume, - pulse_mixer_set_volume, - false, -}; diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx deleted file mode 100644 index 6bd700551..000000000 --- a/src/mixer/RoarMixerPlugin.cxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * 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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "output/RoarOutputPlugin.hxx" - -struct RoarMixer final : public Mixer { - /** the base mixer class */ - RoarOutput *self; - - RoarMixer(RoarOutput *_output) - :Mixer(roar_mixer_plugin), - self(_output) {} -}; - -static Mixer * -roar_mixer_init(void *ao, gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new RoarMixer((RoarOutput *)ao); -} - -static void -roar_mixer_finish(Mixer *data) -{ - RoarMixer *self = (RoarMixer *) data; - - delete self; -} - -static int -roar_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) -{ - RoarMixer *self = (RoarMixer *)mixer; - return roar_output_get_volume(self->self); -} - -static bool -roar_mixer_set_volume(Mixer *mixer, unsigned volume, - gcc_unused Error &error) -{ - RoarMixer *self = (RoarMixer *)mixer; - return roar_output_set_volume(self->self, volume); -} - -const struct mixer_plugin roar_mixer_plugin = { - roar_mixer_init, - roar_mixer_finish, - nullptr, - nullptr, - roar_mixer_get_volume, - roar_mixer_set_volume, - false, -}; diff --git a/src/mixer/Volume.cxx b/src/mixer/Volume.cxx new file mode 100644 index 000000000..abb01fb40 --- /dev/null +++ b/src/mixer/Volume.cxx @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Volume.hxx" +#include "output/MultipleOutputs.hxx" +#include "Idle.hxx" +#include "util/StringUtil.hxx" +#include "util/Domain.hxx" +#include "system/PeriodClock.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdlib.h> + +#define SW_VOLUME_STATE "sw_volume: " + +static constexpr Domain volume_domain("volume"); + +static unsigned volume_software_set = 100; + +/** the cached hardware mixer value; invalid if negative */ +static int last_hardware_volume = -1; +/** the age of #last_hardware_volume */ +static PeriodClock hardware_volume_clock; + +void +InvalidateHardwareVolume() +{ + /* flush the hardware volume cache */ + last_hardware_volume = -1; +} + +int +volume_level_get(const MultipleOutputs &outputs) +{ + if (last_hardware_volume >= 0 && + !hardware_volume_clock.CheckUpdate(1000)) + /* throttle access to hardware mixers */ + return last_hardware_volume; + + last_hardware_volume = outputs.GetVolume(); + return last_hardware_volume; +} + +static bool +software_volume_change(MultipleOutputs &outputs, unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + outputs.SetSoftwareVolume(volume); + + return true; +} + +static bool +hardware_volume_change(MultipleOutputs &outputs, unsigned volume) +{ + /* reset the cache */ + last_hardware_volume = -1; + + return outputs.SetVolume(volume); +} + +bool +volume_level_change(MultipleOutputs &outputs, unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + + idle_add(IDLE_MIXER); + + return hardware_volume_change(outputs, volume); +} + +bool +read_sw_volume_state(const char *line, MultipleOutputs &outputs) +{ + char *end = nullptr; + long int sv; + + if (!StringStartsWith(line, SW_VOLUME_STATE)) + return false; + + line += sizeof(SW_VOLUME_STATE) - 1; + sv = strtol(line, &end, 10); + if (*end == 0 && sv >= 0 && sv <= 100) + software_volume_change(outputs, sv); + else + FormatWarning(volume_domain, + "Can't parse software volume: %s", line); + return true; +} + +void +save_sw_volume_state(BufferedOutputStream &os) +{ + os.Format(SW_VOLUME_STATE "%u\n", volume_software_set); +} + +unsigned +sw_volume_state_get_hash(void) +{ + return volume_software_set; +} diff --git a/src/mixer/Volume.hxx b/src/mixer/Volume.hxx new file mode 100644 index 000000000..d787a6415 --- /dev/null +++ b/src/mixer/Volume.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_VOLUME_HXX +#define MPD_VOLUME_HXX + +#include "Compiler.h" + +class MultipleOutputs; +class BufferedOutputStream; + +void +InvalidateHardwareVolume(); + +gcc_pure +int +volume_level_get(const MultipleOutputs &outputs); + +bool +volume_level_change(MultipleOutputs &outputs, unsigned volume); + +bool +read_sw_volume_state(const char *line, MultipleOutputs &outputs); + +void +save_sw_volume_state(BufferedOutputStream &os); + +/** + * Generates a hash number for the current state of the software + * volume control. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +gcc_pure +unsigned +sw_volume_state_get_hash(void); + +#endif diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx index 4a4ca433c..cd787182a 100644 --- a/src/mixer/AlsaMixerPlugin.cxx +++ b/src/mixer/plugins/AlsaMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,15 @@ */ #include "config.h" -#include "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "GlobalEvents.hxx" -#include "Main.hxx" +#include "mixer/MixerInternal.hxx" +#include "mixer/Listener.hxx" +#include "output/OutputAPI.hxx" #include "event/MultiSocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" #include "event/Loop.hxx" -#include "event/Call.hxx" #include "util/ASCII.hxx" #include "util/ReusableArray.hxx" +#include "util/Clamp.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -39,35 +39,30 @@ #define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0; -class AlsaMixerMonitor final : private MultiSocketMonitor { +class AlsaMixerMonitor final : MultiSocketMonitor, DeferredMonitor { snd_mixer_t *mixer; ReusableArray<pollfd> pfd_buffer; public: AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) - :MultiSocketMonitor(_loop), mixer(_mixer) { -#ifdef USE_EPOLL - _loop.AddCall([this](){ InvalidateSockets(); }); -#else - _loop.AddIdle(InitAlsaMixerMonitor, this); -#endif + :MultiSocketMonitor(_loop), DeferredMonitor(_loop), + mixer(_mixer) { + DeferredMonitor::Schedule(); } private: -#ifndef USE_EPOLL - static gboolean InitAlsaMixerMonitor(gpointer data) { - AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data; - amm.InvalidateSockets(); - return false; + virtual void RunDeferred() override { + InvalidateSockets(); } -#endif virtual int PrepareSockets() override; virtual void DispatchSockets() override; }; class AlsaMixer final : public Mixer { + EventLoop &event_loop; + const char *device; const char *control; unsigned int index; @@ -81,15 +76,20 @@ class AlsaMixer final : public Mixer { AlsaMixerMonitor *monitor; public: - AlsaMixer():Mixer(alsa_mixer_plugin) {} + AlsaMixer(EventLoop &_event_loop, MixerListener &_listener) + :Mixer(alsa_mixer_plugin, _listener), + event_loop(_event_loop) {} + + virtual ~AlsaMixer(); void Configure(const config_param ¶m); bool Setup(Error &error); - bool Open(Error &error); - void Close(); - int GetVolume(Error &error); - bool SetVolume(unsigned volume, Error &error); + /* virtual methods from class Mixer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; }; static constexpr Domain alsa_mixer_domain("alsa_mixer"); @@ -97,8 +97,10 @@ static constexpr Domain alsa_mixer_domain("alsa_mixer"); int AlsaMixerMonitor::PrepareSockets() { - if (mixer == nullptr) + if (mixer == nullptr) { + ClearSocketList(); return -1; + } int count = snd_mixer_poll_descriptors_count(mixer); if (count < 0) @@ -110,24 +112,7 @@ AlsaMixerMonitor::PrepareSockets() if (count < 0) count = 0; - struct pollfd *end = pfds + count; - - UpdateSocketList([pfds, end](int fd) -> unsigned { - auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){ - return pfd.fd == fd; - }); - if (i == end) - return 0; - - auto events = i->events; - i->events = 0; - return events; - }); - - for (auto i = pfds; i != end; ++i) - if (i->events != 0) - AddSocket(i->fd, i->events); - + ReplaceSocketList(pfds, count); return -1; } @@ -158,10 +143,15 @@ AlsaMixerMonitor::DispatchSockets() */ static int -alsa_mixer_elem_callback(gcc_unused snd_mixer_elem_t *elem, unsigned mask) +alsa_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned mask) { - if (mask & SND_CTL_EVENT_MASK_VALUE) - GlobalEvents::Emit(GlobalEvents::MIXER); + AlsaMixer &mixer = *(AlsaMixer *) + snd_mixer_elem_get_callback_private(elem); + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + int volume = mixer.GetVolume(IgnoreError()); + mixer.listener.OnMixerVolumeChanged(mixer, volume); + } return 0; } @@ -183,22 +173,19 @@ AlsaMixer::Configure(const config_param ¶m) } static Mixer * -alsa_mixer_init(gcc_unused void *ao, const config_param ¶m, +alsa_mixer_init(EventLoop &event_loop, gcc_unused AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, gcc_unused Error &error) { - AlsaMixer *am = new AlsaMixer(); + AlsaMixer *am = new AlsaMixer(event_loop, listener); am->Configure(param); return am; } -static void -alsa_mixer_finish(Mixer *data) +AlsaMixer::~AlsaMixer() { - AlsaMixer *am = (AlsaMixer *)data; - - delete am; - /* free libasound's config cache */ snd_config_update_free_global(); } @@ -256,9 +243,10 @@ AlsaMixer::Setup(Error &error) snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max); + snd_mixer_elem_set_callback_private(elem, this); snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback); - monitor = new AlsaMixerMonitor(*main_loop, handle); + monitor = new AlsaMixerMonitor(event_loop, handle); return true; } @@ -285,14 +273,6 @@ AlsaMixer::Open(Error &error) return true; } -static bool -alsa_mixer_open(Mixer *data, Error &error) -{ - AlsaMixer *am = (AlsaMixer *)data; - - return am->Open(error); -} - inline void AlsaMixer::Close() { @@ -304,13 +284,6 @@ AlsaMixer::Close() snd_mixer_close(handle); } -static void -alsa_mixer_close(Mixer *data) -{ - AlsaMixer *am = (AlsaMixer *)data; - am->Close(); -} - inline int AlsaMixer::GetVolume(Error &error) { @@ -350,13 +323,6 @@ AlsaMixer::GetVolume(Error &error) return ret; } -static int -alsa_mixer_get_volume(Mixer *mixer, Error &error) -{ - AlsaMixer *am = (AlsaMixer *)mixer; - return am->GetVolume(error); -} - inline bool AlsaMixer::SetVolume(unsigned volume, Error &error) { @@ -372,8 +338,7 @@ AlsaMixer::SetVolume(unsigned volume, Error &error) level = (long)(((vol / 100.0) * (volume_max - volume_min) + volume_min) + 0.5); - level = level > volume_max ? volume_max : level; - level = level < volume_min ? volume_min : level; + level = Clamp(level, volume_min, volume_max); err = snd_mixer_selem_set_playback_volume_all(elem, level); if (err < 0) { @@ -386,19 +351,7 @@ AlsaMixer::SetVolume(unsigned volume, Error &error) return true; } -static bool -alsa_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - AlsaMixer *am = (AlsaMixer *)mixer; - return am->SetVolume(volume, error); -} - -const struct mixer_plugin alsa_mixer_plugin = { +const MixerPlugin alsa_mixer_plugin = { alsa_mixer_init, - alsa_mixer_finish, - alsa_mixer_open, - alsa_mixer_close, - alsa_mixer_get_volume, - alsa_mixer_set_volume, true, }; diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/plugins/OssMixerPlugin.cxx index 0a459bc97..6615c7022 100644 --- a/src/mixer/OssMixerPlugin.cxx +++ b/src/mixer/plugins/OssMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "MixerInternal.hxx" -#include "OutputAPI.hxx" +#include "mixer/MixerInternal.hxx" +#include "config/ConfigData.hxx" #include "system/fd_util.h" #include "util/ASCII.hxx" #include "util/Error.hxx" @@ -28,10 +28,8 @@ #include <assert.h> #include <string.h> -#include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> -#include <errno.h> #include <stdlib.h> #include <unistd.h> @@ -43,7 +41,7 @@ #define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" -class OssMixer : public Mixer { +class OssMixer final : public Mixer { const char *device; const char *control; @@ -51,14 +49,16 @@ class OssMixer : public Mixer { int volume_control; public: - OssMixer():Mixer(oss_mixer_plugin) {} + OssMixer(MixerListener &_listener) + :Mixer(oss_mixer_plugin, _listener) {} bool Configure(const config_param ¶m, Error &error); - bool Open(Error &error); - void Close(); - int GetVolume(Error &error); - bool SetVolume(unsigned volume, Error &error); + /* virtual methods from class Mixer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; }; static constexpr Domain oss_mixer_domain("oss_mixer"); @@ -98,10 +98,12 @@ OssMixer::Configure(const config_param ¶m, Error &error) } static Mixer * -oss_mixer_init(gcc_unused void *ao, const config_param ¶m, +oss_mixer_init(gcc_unused EventLoop &event_loop, gcc_unused AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, Error &error) { - OssMixer *om = new OssMixer(); + OssMixer *om = new OssMixer(listener); if (!om->Configure(param, error)) { delete om; @@ -111,14 +113,6 @@ oss_mixer_init(gcc_unused void *ao, const config_param ¶m, return om; } -static void -oss_mixer_finish(Mixer *data) -{ - OssMixer *om = (OssMixer *) data; - - delete om; -} - void OssMixer::Close() { @@ -127,14 +121,7 @@ OssMixer::Close() close(device_fd); } -static void -oss_mixer_close(Mixer *data) -{ - OssMixer *om = (OssMixer *) data; - om->Close(); -} - -inline bool +bool OssMixer::Open(Error &error) { device_fd = open_cloexec(device, O_RDONLY, 0); @@ -164,15 +151,7 @@ OssMixer::Open(Error &error) return true; } -static bool -oss_mixer_open(Mixer *data, Error &error) -{ - OssMixer *om = (OssMixer *) data; - - return om->Open(error); -} - -inline int +int OssMixer::GetVolume(Error &error) { int left, right, level; @@ -198,14 +177,7 @@ OssMixer::GetVolume(Error &error) return left; } -static int -oss_mixer_get_volume(Mixer *mixer, Error &error) -{ - OssMixer *om = (OssMixer *)mixer; - return om->GetVolume(error); -} - -inline bool +bool OssMixer::SetVolume(unsigned volume, Error &error) { int level; @@ -225,19 +197,7 @@ OssMixer::SetVolume(unsigned volume, Error &error) return true; } -static bool -oss_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - OssMixer *om = (OssMixer *)mixer; - return om->SetVolume(volume, error); -} - -const struct mixer_plugin oss_mixer_plugin = { +const MixerPlugin oss_mixer_plugin = { oss_mixer_init, - oss_mixer_finish, - oss_mixer_open, - oss_mixer_close, - oss_mixer_get_volume, - oss_mixer_set_volume, true, }; diff --git a/src/mixer/plugins/PulseMixerPlugin.cxx b/src/mixer/plugins/PulseMixerPlugin.cxx new file mode 100644 index 000000000..c5f20723b --- /dev/null +++ b/src/mixer/plugins/PulseMixerPlugin.cxx @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PulseMixerPlugin.hxx" +#include "mixer/MixerInternal.hxx" +#include "mixer/Listener.hxx" +#include "output/plugins/PulseOutputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <pulse/context.h> +#include <pulse/introspect.h> +#include <pulse/stream.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> + +class PulseMixer final : public Mixer { + PulseOutput &output; + + bool online; + struct pa_cvolume volume; + +public: + PulseMixer(PulseOutput &_output, MixerListener &_listener) + :Mixer(pulse_mixer_plugin, _listener), + output(_output), online(false) + { + } + + virtual ~PulseMixer(); + + void Offline(); + void VolumeCallback(const pa_sink_input_info *i, int eol); + void Update(pa_context *context, pa_stream *stream); + int GetVolumeInternal(Error &error); + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static constexpr Domain pulse_mixer_domain("pulse_mixer"); + +void +PulseMixer::Offline() +{ + if (!online) + return; + + online = false; + + listener.OnMixerVolumeChanged(*this, -1); +} + +inline void +PulseMixer::VolumeCallback(const pa_sink_input_info *i, int eol) +{ + if (eol) + return; + + if (i == nullptr) { + Offline(); + return; + } + + online = true; + volume = i->volume; + + listener.OnMixerVolumeChanged(*this, GetVolumeInternal(IgnoreError())); +} + +/** + * Callback invoked by pulse_mixer_update(). Receives the new mixer + * value. + */ +static void +pulse_mixer_volume_cb(gcc_unused pa_context *context, const pa_sink_input_info *i, + int eol, void *userdata) +{ + PulseMixer *pm = (PulseMixer *)userdata; + pm->VolumeCallback(i, eol); +} + +inline void +PulseMixer::Update(pa_context *context, pa_stream *stream) +{ + assert(context != nullptr); + assert(stream != nullptr); + assert(pa_stream_get_state(stream) == PA_STREAM_READY); + + pa_operation *o = + pa_context_get_sink_input_info(context, + pa_stream_get_index(stream), + pulse_mixer_volume_cb, this); + if (o == nullptr) { + FormatError(pulse_mixer_domain, + "pa_context_get_sink_input_info() failed: %s", + pa_strerror(pa_context_errno(context))); + Offline(); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_connect(gcc_unused PulseMixer &pm, + struct pa_context *context) +{ + pa_operation *o; + + assert(context != nullptr); + + o = pa_context_subscribe(context, + (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, + nullptr, nullptr); + if (o == nullptr) { + FormatError(pulse_mixer_domain, + "pa_context_subscribe() failed: %s", + pa_strerror(pa_context_errno(context))); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_disconnect(PulseMixer &pm) +{ + pm.Offline(); +} + +void +pulse_mixer_on_change(PulseMixer &pm, + struct pa_context *context, struct pa_stream *stream) +{ + pm.Update(context, stream); +} + +static Mixer * +pulse_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + PulseOutput &po = (PulseOutput &)ao; + PulseMixer *pm = new PulseMixer(po, listener); + + pulse_output_set_mixer(po, *pm); + + return pm; +} + +PulseMixer::~PulseMixer() +{ + pulse_output_clear_mixer(output, *this); +} + +int +PulseMixer::GetVolume(gcc_unused Error &error) +{ + pulse_output_lock(output); + + int result = GetVolumeInternal(error); + pulse_output_unlock(output); + + return result; +} + +/** + * Pulse mainloop lock must be held by caller + */ +int +PulseMixer::GetVolumeInternal(gcc_unused Error &error) +{ + return online ? + (int)((100 * (pa_cvolume_avg(&volume) + 1)) / PA_VOLUME_NORM) + : -1; +} + +bool +PulseMixer::SetVolume(unsigned new_volume, Error &error) +{ + pulse_output_lock(output); + + if (!online) { + pulse_output_unlock(output); + error.Set(pulse_mixer_domain, "disconnected"); + return false; + } + + struct pa_cvolume cvolume; + pa_cvolume_set(&cvolume, volume.channels, + (pa_volume_t)new_volume * PA_VOLUME_NORM / 100 + 0.5); + bool success = pulse_output_set_volume(output, &cvolume, error); + if (success) + volume = cvolume; + + pulse_output_unlock(output); + return success; +} + +const MixerPlugin pulse_mixer_plugin = { + pulse_mixer_init, + false, +}; diff --git a/src/mixer/PulseMixerPlugin.hxx b/src/mixer/plugins/PulseMixerPlugin.hxx index fa73e0f5e..9b3a6daf1 100644 --- a/src/mixer/PulseMixerPlugin.hxx +++ b/src/mixer/plugins/PulseMixerPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,20 +20,17 @@ #ifndef MPD_PULSE_MIXER_PLUGIN_HXX #define MPD_PULSE_MIXER_PLUGIN_HXX -#include <pulse/def.h> - -struct PulseMixer; +class PulseMixer; struct pa_context; struct pa_stream; void -pulse_mixer_on_connect(PulseMixer *pm, struct pa_context *context); +pulse_mixer_on_connect(PulseMixer &pm, pa_context *context); void -pulse_mixer_on_disconnect(PulseMixer *pm); +pulse_mixer_on_disconnect(PulseMixer &pm); void -pulse_mixer_on_change(PulseMixer *pm, - struct pa_context *context, struct pa_stream *stream); +pulse_mixer_on_change(PulseMixer &pm, pa_context *context, pa_stream *stream); #endif diff --git a/src/mixer/plugins/RoarMixerPlugin.cxx b/src/mixer/plugins/RoarMixerPlugin.cxx new file mode 100644 index 000000000..8e198478d --- /dev/null +++ b/src/mixer/plugins/RoarMixerPlugin.cxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * 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 "mixer/MixerInternal.hxx" +#include "output/plugins/RoarOutputPlugin.hxx" +#include "Compiler.h" + +class RoarMixer final : public Mixer { + /** the base mixer class */ + RoarOutput &self; + +public: + RoarMixer(RoarOutput &_output, MixerListener &_listener) + :Mixer(roar_mixer_plugin, _listener), + self(_output) {} + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static Mixer * +roar_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new RoarMixer((RoarOutput &)ao, listener); +} + +int +RoarMixer::GetVolume(gcc_unused Error &error) +{ + return roar_output_get_volume(self); +} + +bool +RoarMixer::SetVolume(unsigned volume, gcc_unused Error &error) +{ + return roar_output_set_volume(self, volume); +} + +const MixerPlugin roar_mixer_plugin = { + roar_mixer_init, + false, +}; diff --git a/src/mixer/SoftwareMixerPlugin.cxx b/src/mixer/plugins/SoftwareMixerPlugin.cxx index 193e68f23..f14766002 100644 --- a/src/mixer/SoftwareMixerPlugin.cxx +++ b/src/mixer/plugins/SoftwareMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,13 +19,13 @@ #include "config.h" #include "SoftwareMixerPlugin.hxx" -#include "MixerInternal.hxx" -#include "FilterPlugin.hxx" -#include "FilterRegistry.hxx" -#include "FilterInternal.hxx" -#include "filter/VolumeFilterPlugin.hxx" -#include "pcm/PcmVolume.hxx" -#include "ConfigData.hxx" +#include "mixer/MixerInternal.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterRegistry.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/plugins/VolumeFilterPlugin.hxx" +#include "pcm/Volume.hxx" +#include "config/ConfigData.hxx" #include "util/Error.hxx" #include <assert.h> @@ -34,11 +34,11 @@ static Filter * CreateVolumeFilter() { - Error error; - return filter_new(&volume_filter_plugin, config_param(), error); + return filter_new(&volume_filter_plugin, config_param(), + IgnoreError()); } -struct SoftwareMixer final : public Mixer { +class SoftwareMixer final : public Mixer { Filter *filter; /** @@ -49,10 +49,14 @@ struct SoftwareMixer final : public Mixer { */ bool owns_filter; + /** + * The current volume in percent (0..100). + */ unsigned volume; - SoftwareMixer() - :Mixer(software_mixer_plugin), +public: + SoftwareMixer(MixerListener &_listener) + :Mixer(software_mixer_plugin, _listener), filter(CreateVolumeFilter()), owns_filter(true), volume(100) @@ -60,73 +64,81 @@ struct SoftwareMixer final : public Mixer { assert(filter != nullptr); } - ~SoftwareMixer() { + virtual ~SoftwareMixer() { if (owns_filter) delete filter; } + + Filter *GetFilter(); + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(gcc_unused Error &error) override { + return volume; + } + + virtual bool SetVolume(unsigned volume, Error &error) override; }; static Mixer * -software_mixer_init(gcc_unused void *ao, +software_mixer_init(gcc_unused EventLoop &event_loop, + gcc_unused AudioOutput &ao, + MixerListener &listener, gcc_unused const config_param ¶m, gcc_unused Error &error) { - return new SoftwareMixer(); -} - -static void -software_mixer_finish(Mixer *data) -{ - SoftwareMixer *sm = (SoftwareMixer *)data; - - delete sm; -} - -static int -software_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) -{ - SoftwareMixer *sm = (SoftwareMixer *)mixer; - - return sm->volume; + return new SoftwareMixer(listener); } -static bool -software_mixer_set_volume(Mixer *mixer, unsigned volume, - gcc_unused Error &error) +gcc_const +static unsigned +PercentVolumeToSoftwareVolume(unsigned volume) { - SoftwareMixer *sm = (SoftwareMixer *)mixer; - assert(volume <= 100); - sm->volume = volume; - if (volume >= 100) - volume = PCM_VOLUME_1; + return PCM_VOLUME_1; else if (volume > 0) - volume = pcm_float_to_volume((exp(volume / 25.0) - 1) / - (54.5981500331F - 1)); + return pcm_float_to_volume((exp(volume / 25.0) - 1) / + (54.5981500331F - 1)); + else + return 0; +} - volume_filter_set(sm->filter, volume); +bool +SoftwareMixer::SetVolume(unsigned new_volume, gcc_unused Error &error) +{ + assert(new_volume <= 100); + + volume = new_volume; + volume_filter_set(filter, PercentVolumeToSoftwareVolume(new_volume)); return true; } -const struct mixer_plugin software_mixer_plugin = { +const MixerPlugin software_mixer_plugin = { software_mixer_init, - software_mixer_finish, - nullptr, - nullptr, - software_mixer_get_volume, - software_mixer_set_volume, true, }; +inline Filter * +SoftwareMixer::GetFilter() +{ + assert(owns_filter); + + owns_filter = false; + return filter; +} + Filter * software_mixer_get_filter(Mixer *mixer) { SoftwareMixer *sm = (SoftwareMixer *)mixer; assert(sm->IsPlugin(software_mixer_plugin)); - assert(sm->owns_filter); - - sm->owns_filter = false; - return sm->filter; + return sm->GetFilter(); } diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/plugins/SoftwareMixerPlugin.hxx index be59c08db..581d2ac17 100644 --- a/src/mixer/SoftwareMixerPlugin.hxx +++ b/src/mixer/plugins/SoftwareMixerPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/plugins/WinmmMixerPlugin.cxx index dbb43dce4..e0436011a 100644 --- a/src/mixer/WinmmMixerPlugin.cxx +++ b/src/mixer/plugins/WinmmMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "output/WinmmOutputPlugin.hxx" +#include "mixer/MixerInternal.hxx" +#include "output/OutputAPI.hxx" +#include "output/plugins/WinmmOutputPlugin.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -30,13 +30,25 @@ #include <math.h> #include <windows.h> -struct WinmmMixer final : public Mixer { - WinmmOutput *output; +class WinmmMixer final : public Mixer { + WinmmOutput &output; - WinmmMixer(WinmmOutput *_output) - :Mixer(winmm_mixer_plugin), +public: + WinmmMixer(WinmmOutput &_output, MixerListener &_listener) + :Mixer(winmm_mixer_plugin, _listener), output(_output) { } + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; }; static constexpr Domain winmm_mixer_domain("winmm_mixer"); @@ -55,28 +67,19 @@ winmm_volume_encode(int volume) } static Mixer * -winmm_mixer_init(void *ao, gcc_unused const config_param ¶m, +winmm_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, gcc_unused Error &error) { - assert(ao != nullptr); - - return new WinmmMixer((WinmmOutput *)ao); -} - -static void -winmm_mixer_finish(Mixer *data) -{ - WinmmMixer *wm = (WinmmMixer *)data; - - delete wm; + return new WinmmMixer((WinmmOutput &)ao, listener); } -static int -winmm_mixer_get_volume(Mixer *mixer, Error &error) +int +WinmmMixer::GetVolume(Error &error) { - WinmmMixer *wm = (WinmmMixer *) mixer; DWORD volume; - HWAVEOUT handle = winmm_output_get_handle(wm->output); + HWAVEOUT handle = winmm_output_get_handle(output); MMRESULT result = waveOutGetVolume(handle, &volume); if (result != MMSYSERR_NOERROR) { @@ -87,12 +90,11 @@ winmm_mixer_get_volume(Mixer *mixer, Error &error) return winmm_volume_decode(volume); } -static bool -winmm_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +bool +WinmmMixer::SetVolume(unsigned volume, Error &error) { - WinmmMixer *wm = (WinmmMixer *) mixer; DWORD value = winmm_volume_encode(volume); - HWAVEOUT handle = winmm_output_get_handle(wm->output); + HWAVEOUT handle = winmm_output_get_handle(output); MMRESULT result = waveOutSetVolume(handle, value); if (result != MMSYSERR_NOERROR) { @@ -103,12 +105,7 @@ winmm_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) return true; } -const struct mixer_plugin winmm_mixer_plugin = { +const MixerPlugin winmm_mixer_plugin = { winmm_mixer_init, - winmm_mixer_finish, - nullptr, - nullptr, - winmm_mixer_get_volume, - winmm_mixer_set_volume, false, }; |