diff options
Diffstat (limited to 'src/mixer/plugins')
-rw-r--r-- | src/mixer/plugins/AlsaMixerPlugin.cxx | 357 | ||||
-rw-r--r-- | src/mixer/plugins/OssMixerPlugin.cxx | 203 | ||||
-rw-r--r-- | src/mixer/plugins/PulseMixerPlugin.cxx | 233 | ||||
-rw-r--r-- | src/mixer/plugins/PulseMixerPlugin.hxx | 36 | ||||
-rw-r--r-- | src/mixer/plugins/RoarMixerPlugin.cxx | 72 | ||||
-rw-r--r-- | src/mixer/plugins/SoftwareMixerPlugin.cxx | 144 | ||||
-rw-r--r-- | src/mixer/plugins/SoftwareMixerPlugin.hxx | 33 | ||||
-rw-r--r-- | src/mixer/plugins/WinmmMixerPlugin.cxx | 111 |
8 files changed, 1189 insertions, 0 deletions
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, +}; |