diff options
Diffstat (limited to '')
27 files changed, 2047 insertions, 1266 deletions
diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx deleted file mode 100644 index 4a4ca433c..000000000 --- a/src/mixer/AlsaMixerPlugin.cxx +++ /dev/null @@ -1,404 +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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "GlobalEvents.hxx" -#include "Main.hxx" -#include "event/MultiSocketMonitor.hxx" -#include "event/Loop.hxx" -#include "event/Call.hxx" -#include "util/ASCII.hxx" -#include "util/ReusableArray.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <algorithm> - -#include <alsa/asoundlib.h> - -#define VOLUME_MIXER_ALSA_DEFAULT "default" -#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" -static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0; - -class AlsaMixerMonitor final : private MultiSocketMonitor { - 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 - } - -private: -#ifndef USE_EPOLL - static gboolean InitAlsaMixerMonitor(gpointer data) { - AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data; - amm.InvalidateSockets(); - return false; - } -#endif - - virtual int PrepareSockets() override; - virtual void DispatchSockets() override; -}; - -class AlsaMixer final : public Mixer { - const char *device; - const char *control; - unsigned int index; - - snd_mixer_t *handle; - snd_mixer_elem_t *elem; - long volume_min; - long volume_max; - int volume_set; - - AlsaMixerMonitor *monitor; - -public: - AlsaMixer():Mixer(alsa_mixer_plugin) {} - - 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); -}; - -static constexpr Domain alsa_mixer_domain("alsa_mixer"); - -int -AlsaMixerMonitor::PrepareSockets() -{ - if (mixer == nullptr) - return -1; - - int count = snd_mixer_poll_descriptors_count(mixer); - if (count < 0) - count = 0; - - struct pollfd *pfds = pfd_buffer.Get(count); - - count = snd_mixer_poll_descriptors(mixer, pfds, count); - 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); - - return -1; -} - -void -AlsaMixerMonitor::DispatchSockets() -{ - assert(mixer != nullptr); - - int err = snd_mixer_handle_events(mixer); - if (err < 0) { - FormatError(alsa_mixer_domain, - "snd_mixer_handle_events() failed: %s", - snd_strerror(err)); - - if (err == -ENODEV) { - /* the sound device was unplugged; disable - this GSource */ - mixer = nullptr; - InvalidateSockets(); - return; - } - } -} - -/* - * libasound callbacks - * - */ - -static int -alsa_mixer_elem_callback(gcc_unused snd_mixer_elem_t *elem, unsigned mask) -{ - if (mask & SND_CTL_EVENT_MASK_VALUE) - GlobalEvents::Emit(GlobalEvents::MIXER); - - return 0; -} - -/* - * mixer_plugin methods - * - */ - -inline void -AlsaMixer::Configure(const config_param ¶m) -{ - device = param.GetBlockValue("mixer_device", - VOLUME_MIXER_ALSA_DEFAULT); - control = param.GetBlockValue("mixer_control", - VOLUME_MIXER_ALSA_CONTROL_DEFAULT); - index = param.GetBlockValue("mixer_index", - VOLUME_MIXER_ALSA_INDEX_DEFAULT); -} - -static Mixer * -alsa_mixer_init(gcc_unused void *ao, const config_param ¶m, - gcc_unused Error &error) -{ - AlsaMixer *am = new AlsaMixer(); - am->Configure(param); - - return am; -} - -static void -alsa_mixer_finish(Mixer *data) -{ - AlsaMixer *am = (AlsaMixer *)data; - - delete am; - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -gcc_pure -static snd_mixer_elem_t * -alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) -{ - for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); - elem != nullptr; elem = snd_mixer_elem_next(elem)) { - if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && - StringEqualsCaseASCII(snd_mixer_selem_get_name(elem), - name) && - snd_mixer_selem_get_index(elem) == idx) - return elem; - } - - return nullptr; -} - -inline bool -AlsaMixer::Setup(Error &error) -{ - int err; - - if ((err = snd_mixer_attach(handle, device)) < 0) { - error.Format(alsa_mixer_domain, err, - "failed to attach to %s: %s", - device, snd_strerror(err)); - return false; - } - - if ((err = snd_mixer_selem_register(handle, nullptr, - nullptr)) < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_selem_register() failed: %s", - snd_strerror(err)); - return false; - } - - if ((err = snd_mixer_load(handle)) < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_load() failed: %s\n", - snd_strerror(err)); - return false; - } - - elem = alsa_mixer_lookup_elem(handle, control, index); - if (elem == nullptr) { - error.Format(alsa_mixer_domain, 0, - "no such mixer control: %s", control); - return false; - } - - snd_mixer_selem_get_playback_volume_range(elem, &volume_min, - &volume_max); - - snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback); - - monitor = new AlsaMixerMonitor(*main_loop, handle); - - return true; -} - -inline bool -AlsaMixer::Open(Error &error) -{ - int err; - - volume_set = -1; - - err = snd_mixer_open(&handle, 0); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_open() failed: %s", snd_strerror(err)); - return false; - } - - if (!Setup(error)) { - snd_mixer_close(handle); - return false; - } - - return true; -} - -static bool -alsa_mixer_open(Mixer *data, Error &error) -{ - AlsaMixer *am = (AlsaMixer *)data; - - return am->Open(error); -} - -inline void -AlsaMixer::Close() -{ - assert(handle != nullptr); - - delete monitor; - - snd_mixer_elem_set_callback(elem, nullptr); - snd_mixer_close(handle); -} - -static void -alsa_mixer_close(Mixer *data) -{ - AlsaMixer *am = (AlsaMixer *)data; - am->Close(); -} - -inline int -AlsaMixer::GetVolume(Error &error) -{ - int err; - int ret; - long level; - - assert(handle != nullptr); - - err = snd_mixer_handle_events(handle); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_handle_events() failed: %s", - snd_strerror(err)); - return false; - } - - err = snd_mixer_selem_get_playback_volume(elem, - SND_MIXER_SCHN_FRONT_LEFT, - &level); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "failed to read ALSA volume: %s", - snd_strerror(err)); - return false; - } - - ret = ((volume_set / 100.0) * (volume_max - volume_min) - + volume_min) + 0.5; - if (volume_set > 0 && ret == level) { - ret = volume_set; - } else { - ret = (int)(100 * (((float)(level - volume_min)) / - (volume_max - volume_min)) + 0.5); - } - - 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) -{ - float vol; - long level; - int err; - - assert(handle != nullptr); - - vol = volume; - - volume_set = vol + 0.5; - - 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; - - err = snd_mixer_selem_set_playback_volume_all(elem, level); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "failed to set ALSA volume: %s", - snd_strerror(err)); - return false; - } - - 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 = { - 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/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/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx deleted file mode 100644 index 0a459bc97..000000000 --- a/src/mixer/OssMixerPlugin.cxx +++ /dev/null @@ -1,243 +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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "system/fd_util.h" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#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> - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include <soundcard.h> -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include <sys/soundcard.h> -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" - -class OssMixer : public Mixer { - const char *device; - const char *control; - - int device_fd; - int volume_control; - -public: - OssMixer():Mixer(oss_mixer_plugin) {} - - bool Configure(const config_param ¶m, Error &error); - bool Open(Error &error); - void Close(); - - int GetVolume(Error &error); - bool SetVolume(unsigned volume, Error &error); -}; - -static constexpr Domain oss_mixer_domain("oss_mixer"); - -static int -oss_find_mixer(const char *name) -{ - const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; - size_t name_length = strlen(name); - - for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { - if (StringEqualsCaseASCII(name, labels[i], name_length) && - (labels[i][name_length] == 0 || - labels[i][name_length] == ' ')) - return i; - } - return -1; -} - -inline bool -OssMixer::Configure(const config_param ¶m, Error &error) -{ - device = param.GetBlockValue("mixer_device", VOLUME_MIXER_OSS_DEFAULT); - control = param.GetBlockValue("mixer_control"); - - if (control != NULL) { - volume_control = oss_find_mixer(control); - if (volume_control < 0) { - error.Format(oss_mixer_domain, 0, - "no such mixer control: %s", control); - return false; - } - } else - volume_control = SOUND_MIXER_PCM; - - return true; -} - -static Mixer * -oss_mixer_init(gcc_unused void *ao, const config_param ¶m, - Error &error) -{ - OssMixer *om = new OssMixer(); - - if (!om->Configure(param, error)) { - delete om; - return nullptr; - } - - return om; -} - -static void -oss_mixer_finish(Mixer *data) -{ - OssMixer *om = (OssMixer *) data; - - delete om; -} - -void -OssMixer::Close() -{ - assert(device_fd >= 0); - - close(device_fd); -} - -static void -oss_mixer_close(Mixer *data) -{ - OssMixer *om = (OssMixer *) data; - om->Close(); -} - -inline bool -OssMixer::Open(Error &error) -{ - device_fd = open_cloexec(device, O_RDONLY, 0); - if (device_fd < 0) { - error.FormatErrno("failed to open %s", device); - return false; - } - - if (control) { - int devmask = 0; - - if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - error.SetErrno("READ_DEVMASK failed"); - Close(); - return false; - } - - if (((1 << volume_control) & devmask) == 0) { - error.Format(oss_mixer_domain, 0, - "mixer control \"%s\" not usable", - control); - Close(); - return false; - } - } - - return true; -} - -static bool -oss_mixer_open(Mixer *data, Error &error) -{ - OssMixer *om = (OssMixer *) data; - - return om->Open(error); -} - -inline int -OssMixer::GetVolume(Error &error) -{ - int left, right, level; - int ret; - - assert(device_fd >= 0); - - ret = ioctl(device_fd, MIXER_READ(volume_control), &level); - if (ret < 0) { - error.SetErrno("failed to read OSS volume"); - return false; - } - - left = level & 0xff; - right = (level & 0xff00) >> 8; - - if (left != right) { - FormatWarning(oss_mixer_domain, - "volume for left and right is not the same, \"%i\" and " - "\"%i\"\n", left, right); - } - - return left; -} - -static int -oss_mixer_get_volume(Mixer *mixer, Error &error) -{ - OssMixer *om = (OssMixer *)mixer; - return om->GetVolume(error); -} - -inline bool -OssMixer::SetVolume(unsigned volume, Error &error) -{ - int level; - int ret; - - assert(device_fd >= 0); - assert(volume <= 100); - - level = (volume << 8) + volume; - - ret = ioctl(device_fd, MIXER_WRITE(volume_control), &level); - if (ret < 0) { - error.SetErrno("failed to set OSS volume"); - return false; - } - - 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 = { - 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/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/PulseMixerPlugin.hxx b/src/mixer/PulseMixerPlugin.hxx deleted file mode 100644 index fa73e0f5e..000000000 --- a/src/mixer/PulseMixerPlugin.hxx +++ /dev/null @@ -1,39 +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. - */ - -#ifndef MPD_PULSE_MIXER_PLUGIN_HXX -#define MPD_PULSE_MIXER_PLUGIN_HXX - -#include <pulse/def.h> - -struct PulseMixer; -struct pa_context; -struct pa_stream; - -void -pulse_mixer_on_connect(PulseMixer *pm, struct pa_context *context); - -void -pulse_mixer_on_disconnect(PulseMixer *pm); - -void -pulse_mixer_on_change(PulseMixer *pm, - struct pa_context *context, struct pa_stream *stream); - -#endif 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/SoftwareMixerPlugin.cxx b/src/mixer/SoftwareMixerPlugin.cxx deleted file mode 100644 index 193e68f23..000000000 --- a/src/mixer/SoftwareMixerPlugin.cxx +++ /dev/null @@ -1,132 +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 "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 "util/Error.hxx" - -#include <assert.h> -#include <math.h> - -static Filter * -CreateVolumeFilter() -{ - Error error; - return filter_new(&volume_filter_plugin, config_param(), error); -} - -struct SoftwareMixer final : public Mixer { - Filter *filter; - - /** - * If this is true, then this object "owns" the #Filter - * instance (see above). It will be set to false by - * software_mixer_get_filter(); after that, the caller will be - * responsible for the #Filter. - */ - bool owns_filter; - - unsigned volume; - - SoftwareMixer() - :Mixer(software_mixer_plugin), - filter(CreateVolumeFilter()), - owns_filter(true), - volume(100) - { - assert(filter != nullptr); - } - - ~SoftwareMixer() { - if (owns_filter) - delete filter; - } -}; - -static Mixer * -software_mixer_init(gcc_unused void *ao, - 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; -} - -static bool -software_mixer_set_volume(Mixer *mixer, unsigned volume, - gcc_unused Error &error) -{ - SoftwareMixer *sm = (SoftwareMixer *)mixer; - - assert(volume <= 100); - - sm->volume = volume; - - if (volume >= 100) - volume = PCM_VOLUME_1; - else if (volume > 0) - volume = pcm_float_to_volume((exp(volume / 25.0) - 1) / - (54.5981500331F - 1)); - - volume_filter_set(sm->filter, volume); - return true; -} - -const struct mixer_plugin software_mixer_plugin = { - software_mixer_init, - software_mixer_finish, - nullptr, - nullptr, - software_mixer_get_volume, - software_mixer_set_volume, - true, -}; - -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; -} diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/SoftwareMixerPlugin.hxx deleted file mode 100644 index be59c08db..000000000 --- a/src/mixer/SoftwareMixerPlugin.hxx +++ /dev/null @@ -1,33 +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. - */ - -#ifndef MPD_SOFTWARE_MIXER_PLUGIN_HXX -#define MPD_SOFTWARE_MIXER_PLUGIN_HXX - -class Mixer; -class Filter; - -/** - * Returns the (volume) filter associated with this mixer. All users - * of this mixer plugin should install this filter. - */ -Filter * -software_mixer_get_filter(Mixer *mixer); - -#endif 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/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx deleted file mode 100644 index dbb43dce4..000000000 --- a/src/mixer/WinmmMixerPlugin.cxx +++ /dev/null @@ -1,114 +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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "output/WinmmOutputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <mmsystem.h> - -#include <assert.h> -#include <math.h> -#include <windows.h> - -struct WinmmMixer final : public Mixer { - WinmmOutput *output; - - WinmmMixer(WinmmOutput *_output) - :Mixer(winmm_mixer_plugin), - output(_output) { - } -}; - -static constexpr Domain winmm_mixer_domain("winmm_mixer"); - -static inline int -winmm_volume_decode(DWORD volume) -{ - return lround((volume & 0xFFFF) / 655.35); -} - -static inline DWORD -winmm_volume_encode(int volume) -{ - int value = lround(volume * 655.35); - return MAKELONG(value, value); -} - -static Mixer * -winmm_mixer_init(void *ao, 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; -} - -static int -winmm_mixer_get_volume(Mixer *mixer, Error &error) -{ - WinmmMixer *wm = (WinmmMixer *) mixer; - DWORD volume; - HWAVEOUT handle = winmm_output_get_handle(wm->output); - MMRESULT result = waveOutGetVolume(handle, &volume); - - if (result != MMSYSERR_NOERROR) { - error.Set(winmm_mixer_domain, "Failed to get winmm volume"); - return -1; - } - - return winmm_volume_decode(volume); -} - -static bool -winmm_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - WinmmMixer *wm = (WinmmMixer *) mixer; - DWORD value = winmm_volume_encode(volume); - HWAVEOUT handle = winmm_output_get_handle(wm->output); - MMRESULT result = waveOutSetVolume(handle, value); - - if (result != MMSYSERR_NOERROR) { - error.Set(winmm_mixer_domain, "Failed to set winmm volume"); - return false; - } - - return true; -} - -const struct mixer_plugin winmm_mixer_plugin = { - winmm_mixer_init, - winmm_mixer_finish, - nullptr, - nullptr, - winmm_mixer_get_volume, - winmm_mixer_set_volume, - false, -}; diff --git a/src/mixer/plugins/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx new file mode 100644 index 000000000..cd787182a --- /dev/null +++ b/src/mixer/plugins/AlsaMixerPlugin.cxx @@ -0,0 +1,357 @@ +/* + * 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 "mixer/MixerInternal.hxx" +#include "mixer/Listener.hxx" +#include "output/OutputAPI.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" +#include "event/Loop.hxx" +#include "util/ASCII.hxx" +#include "util/ReusableArray.hxx" +#include "util/Clamp.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <algorithm> + +#include <alsa/asoundlib.h> + +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" +static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0; + +class AlsaMixerMonitor final : MultiSocketMonitor, DeferredMonitor { + snd_mixer_t *mixer; + + ReusableArray<pollfd> pfd_buffer; + +public: + AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) + :MultiSocketMonitor(_loop), DeferredMonitor(_loop), + mixer(_mixer) { + DeferredMonitor::Schedule(); + } + +private: + virtual void RunDeferred() override { + InvalidateSockets(); + } + + 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; + + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + long volume_min; + long volume_max; + int volume_set; + + AlsaMixerMonitor *monitor; + +public: + 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); + + /* 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"); + +int +AlsaMixerMonitor::PrepareSockets() +{ + if (mixer == nullptr) { + ClearSocketList(); + return -1; + } + + int count = snd_mixer_poll_descriptors_count(mixer); + if (count < 0) + count = 0; + + struct pollfd *pfds = pfd_buffer.Get(count); + + count = snd_mixer_poll_descriptors(mixer, pfds, count); + if (count < 0) + count = 0; + + ReplaceSocketList(pfds, count); + return -1; +} + +void +AlsaMixerMonitor::DispatchSockets() +{ + assert(mixer != nullptr); + + int err = snd_mixer_handle_events(mixer); + if (err < 0) { + FormatError(alsa_mixer_domain, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); + + if (err == -ENODEV) { + /* the sound device was unplugged; disable + this GSource */ + mixer = nullptr; + InvalidateSockets(); + return; + } + } +} + +/* + * libasound callbacks + * + */ + +static int +alsa_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned mask) +{ + 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; +} + +/* + * mixer_plugin methods + * + */ + +inline void +AlsaMixer::Configure(const config_param ¶m) +{ + device = param.GetBlockValue("mixer_device", + VOLUME_MIXER_ALSA_DEFAULT); + control = param.GetBlockValue("mixer_control", + VOLUME_MIXER_ALSA_CONTROL_DEFAULT); + index = param.GetBlockValue("mixer_index", + VOLUME_MIXER_ALSA_INDEX_DEFAULT); +} + +static Mixer * +alsa_mixer_init(EventLoop &event_loop, gcc_unused AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + gcc_unused Error &error) +{ + AlsaMixer *am = new AlsaMixer(event_loop, listener); + am->Configure(param); + + return am; +} + +AlsaMixer::~AlsaMixer() +{ + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +gcc_pure +static snd_mixer_elem_t * +alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) +{ + for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); + elem != nullptr; elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && + StringEqualsCaseASCII(snd_mixer_selem_get_name(elem), + name) && + snd_mixer_selem_get_index(elem) == idx) + return elem; + } + + return nullptr; +} + +inline bool +AlsaMixer::Setup(Error &error) +{ + int err; + + if ((err = snd_mixer_attach(handle, device)) < 0) { + error.Format(alsa_mixer_domain, err, + "failed to attach to %s: %s", + device, snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_selem_register(handle, nullptr, + nullptr)) < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_selem_register() failed: %s", + snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_load(handle)) < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_load() failed: %s\n", + snd_strerror(err)); + return false; + } + + elem = alsa_mixer_lookup_elem(handle, control, index); + if (elem == nullptr) { + error.Format(alsa_mixer_domain, 0, + "no such mixer control: %s", control); + return false; + } + + 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(event_loop, handle); + + return true; +} + +inline bool +AlsaMixer::Open(Error &error) +{ + int err; + + volume_set = -1; + + err = snd_mixer_open(&handle, 0); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_open() failed: %s", snd_strerror(err)); + return false; + } + + if (!Setup(error)) { + snd_mixer_close(handle); + return false; + } + + return true; +} + +inline void +AlsaMixer::Close() +{ + assert(handle != nullptr); + + delete monitor; + + snd_mixer_elem_set_callback(elem, nullptr); + snd_mixer_close(handle); +} + +inline int +AlsaMixer::GetVolume(Error &error) +{ + int err; + int ret; + long level; + + assert(handle != nullptr); + + err = snd_mixer_handle_events(handle); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); + return false; + } + + err = snd_mixer_selem_get_playback_volume(elem, + SND_MIXER_SCHN_FRONT_LEFT, + &level); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "failed to read ALSA volume: %s", + snd_strerror(err)); + return false; + } + + ret = ((volume_set / 100.0) * (volume_max - volume_min) + + volume_min) + 0.5; + if (volume_set > 0 && ret == level) { + ret = volume_set; + } else { + ret = (int)(100 * (((float)(level - volume_min)) / + (volume_max - volume_min)) + 0.5); + } + + return ret; +} + +inline bool +AlsaMixer::SetVolume(unsigned volume, Error &error) +{ + float vol; + long level; + int err; + + assert(handle != nullptr); + + vol = volume; + + volume_set = vol + 0.5; + + level = (long)(((vol / 100.0) * (volume_max - volume_min) + + volume_min) + 0.5); + level = Clamp(level, volume_min, volume_max); + + err = snd_mixer_selem_set_playback_volume_all(elem, level); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "failed to set ALSA volume: %s", + snd_strerror(err)); + return false; + } + + return true; +} + +const MixerPlugin alsa_mixer_plugin = { + alsa_mixer_init, + true, +}; diff --git a/src/mixer/plugins/OssMixerPlugin.cxx b/src/mixer/plugins/OssMixerPlugin.cxx new file mode 100644 index 000000000..6615c7022 --- /dev/null +++ b/src/mixer/plugins/OssMixerPlugin.cxx @@ -0,0 +1,203 @@ +/* + * 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 "mixer/MixerInternal.hxx" +#include "config/ConfigData.hxx" +#include "system/fd_util.h" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" + +class OssMixer final : public Mixer { + const char *device; + const char *control; + + int device_fd; + int volume_control; + +public: + OssMixer(MixerListener &_listener) + :Mixer(oss_mixer_plugin, _listener) {} + + bool Configure(const config_param ¶m, 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"); + +static int +oss_find_mixer(const char *name) +{ + const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + size_t name_length = strlen(name); + + for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (StringEqualsCaseASCII(name, labels[i], name_length) && + (labels[i][name_length] == 0 || + labels[i][name_length] == ' ')) + return i; + } + return -1; +} + +inline bool +OssMixer::Configure(const config_param ¶m, Error &error) +{ + device = param.GetBlockValue("mixer_device", VOLUME_MIXER_OSS_DEFAULT); + control = param.GetBlockValue("mixer_control"); + + if (control != NULL) { + volume_control = oss_find_mixer(control); + if (volume_control < 0) { + error.Format(oss_mixer_domain, 0, + "no such mixer control: %s", control); + return false; + } + } else + volume_control = SOUND_MIXER_PCM; + + return true; +} + +static Mixer * +oss_mixer_init(gcc_unused EventLoop &event_loop, gcc_unused AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error) +{ + OssMixer *om = new OssMixer(listener); + + if (!om->Configure(param, error)) { + delete om; + return nullptr; + } + + return om; +} + +void +OssMixer::Close() +{ + assert(device_fd >= 0); + + close(device_fd); +} + +bool +OssMixer::Open(Error &error) +{ + device_fd = open_cloexec(device, O_RDONLY, 0); + if (device_fd < 0) { + error.FormatErrno("failed to open %s", device); + return false; + } + + if (control) { + int devmask = 0; + + if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + error.SetErrno("READ_DEVMASK failed"); + Close(); + return false; + } + + if (((1 << volume_control) & devmask) == 0) { + error.Format(oss_mixer_domain, 0, + "mixer control \"%s\" not usable", + control); + Close(); + return false; + } + } + + return true; +} + +int +OssMixer::GetVolume(Error &error) +{ + int left, right, level; + int ret; + + assert(device_fd >= 0); + + ret = ioctl(device_fd, MIXER_READ(volume_control), &level); + if (ret < 0) { + error.SetErrno("failed to read OSS volume"); + return false; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + FormatWarning(oss_mixer_domain, + "volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + + return left; +} + +bool +OssMixer::SetVolume(unsigned volume, Error &error) +{ + int level; + int ret; + + assert(device_fd >= 0); + assert(volume <= 100); + + level = (volume << 8) + volume; + + ret = ioctl(device_fd, MIXER_WRITE(volume_control), &level); + if (ret < 0) { + error.SetErrno("failed to set OSS volume"); + return false; + } + + return true; +} + +const MixerPlugin oss_mixer_plugin = { + oss_mixer_init, + 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/plugins/PulseMixerPlugin.hxx b/src/mixer/plugins/PulseMixerPlugin.hxx new file mode 100644 index 000000000..9b3a6daf1 --- /dev/null +++ b/src/mixer/plugins/PulseMixerPlugin.hxx @@ -0,0 +1,36 @@ +/* + * 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_PULSE_MIXER_PLUGIN_HXX +#define MPD_PULSE_MIXER_PLUGIN_HXX + +class PulseMixer; +struct pa_context; +struct pa_stream; + +void +pulse_mixer_on_connect(PulseMixer &pm, pa_context *context); + +void +pulse_mixer_on_disconnect(PulseMixer &pm); + +void +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/plugins/SoftwareMixerPlugin.cxx b/src/mixer/plugins/SoftwareMixerPlugin.cxx new file mode 100644 index 000000000..f14766002 --- /dev/null +++ b/src/mixer/plugins/SoftwareMixerPlugin.cxx @@ -0,0 +1,144 @@ +/* + * 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 "SoftwareMixerPlugin.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> +#include <math.h> + +static Filter * +CreateVolumeFilter() +{ + return filter_new(&volume_filter_plugin, config_param(), + IgnoreError()); +} + +class SoftwareMixer final : public Mixer { + Filter *filter; + + /** + * If this is true, then this object "owns" the #Filter + * instance (see above). It will be set to false by + * software_mixer_get_filter(); after that, the caller will be + * responsible for the #Filter. + */ + bool owns_filter; + + /** + * The current volume in percent (0..100). + */ + unsigned volume; + +public: + SoftwareMixer(MixerListener &_listener) + :Mixer(software_mixer_plugin, _listener), + filter(CreateVolumeFilter()), + owns_filter(true), + volume(100) + { + assert(filter != nullptr); + } + + 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 EventLoop &event_loop, + gcc_unused AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new SoftwareMixer(listener); +} + +gcc_const +static unsigned +PercentVolumeToSoftwareVolume(unsigned volume) +{ + assert(volume <= 100); + + if (volume >= 100) + return PCM_VOLUME_1; + else if (volume > 0) + return pcm_float_to_volume((exp(volume / 25.0) - 1) / + (54.5981500331F - 1)); + else + return 0; +} + +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 MixerPlugin software_mixer_plugin = { + software_mixer_init, + 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)); + return sm->GetFilter(); +} diff --git a/src/mixer/plugins/SoftwareMixerPlugin.hxx b/src/mixer/plugins/SoftwareMixerPlugin.hxx new file mode 100644 index 000000000..581d2ac17 --- /dev/null +++ b/src/mixer/plugins/SoftwareMixerPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOFTWARE_MIXER_PLUGIN_HXX +#define MPD_SOFTWARE_MIXER_PLUGIN_HXX + +class Mixer; +class Filter; + +/** + * Returns the (volume) filter associated with this mixer. All users + * of this mixer plugin should install this filter. + */ +Filter * +software_mixer_get_filter(Mixer *mixer); + +#endif diff --git a/src/mixer/plugins/WinmmMixerPlugin.cxx b/src/mixer/plugins/WinmmMixerPlugin.cxx new file mode 100644 index 000000000..e0436011a --- /dev/null +++ b/src/mixer/plugins/WinmmMixerPlugin.cxx @@ -0,0 +1,111 @@ +/* + * 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 "mixer/MixerInternal.hxx" +#include "output/OutputAPI.hxx" +#include "output/plugins/WinmmOutputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <mmsystem.h> + +#include <assert.h> +#include <math.h> +#include <windows.h> + +class WinmmMixer final : public Mixer { + WinmmOutput &output; + +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"); + +static inline int +winmm_volume_decode(DWORD volume) +{ + return lround((volume & 0xFFFF) / 655.35); +} + +static inline DWORD +winmm_volume_encode(int volume) +{ + int value = lround(volume * 655.35); + return MAKELONG(value, value); +} + +static Mixer * +winmm_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new WinmmMixer((WinmmOutput &)ao, listener); +} + +int +WinmmMixer::GetVolume(Error &error) +{ + DWORD volume; + HWAVEOUT handle = winmm_output_get_handle(output); + MMRESULT result = waveOutGetVolume(handle, &volume); + + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_mixer_domain, "Failed to get winmm volume"); + return -1; + } + + return winmm_volume_decode(volume); +} + +bool +WinmmMixer::SetVolume(unsigned volume, Error &error) +{ + DWORD value = winmm_volume_encode(volume); + HWAVEOUT handle = winmm_output_get_handle(output); + MMRESULT result = waveOutSetVolume(handle, value); + + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_mixer_domain, "Failed to set winmm volume"); + return false; + } + + return true; +} + +const MixerPlugin winmm_mixer_plugin = { + winmm_mixer_init, + false, +}; |