From bbc088ae4ea19767c102ca740765a30b98ffa96b Mon Sep 17 00:00:00 2001 From: Dave Hocker Date: Mon, 1 Aug 2022 15:45:44 -0500 Subject: [PATCH 1/7] This PR provides forward and backward compatibility at macos SDK 12.0. At SDK 12.0, API function names were changed essentially replacing occurrences of the word Master/master with Main/main. This change was test built on two different systems. 1. macos 10.15.7 with Xcode 12.4 and clang 12.0.0 on x86_64 2. macos 12.5 with Xcode 13.4.1 and clang 13.1.6 on arm64 (Apple silicon M1) It should be noted that on macos 10.15.7 with Xcode 11.2 and clang 11.0, MPD will not build. The MPD documentation states that clang 11.0 is the minimum requirement, but clang 11.0 produces compile errors. Apparently the macos version of clang 11.0 is not fully compliant. --- NEWS | 1 + src/output/plugins/OSXOutputPlugin.cxx | 35 ++++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 704af63c1..dcd7869ff 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ ver 0.23.9 (not yet released) * output - pipewire: set app icon * improve iconv detection +* macOS: fix macOS 10 build problem (0.23.8 regression) ver 0.23.8 (2022/07/09) * storage diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx index 29b5e5c91..a02f3e9a4 100644 --- a/src/output/plugins/OSXOutputPlugin.cxx +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -47,6 +47,15 @@ #include +// Backward compatibility from OSX 12.0 API change +#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) + #define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMain + #define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMainVolume +#else + #define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMaster + #define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMasterVolume +#endif + static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100; static StringBuffer<64> @@ -160,13 +169,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block) static constexpr AudioObjectPropertyAddress default_system_output_device{ kAudioHardwarePropertyDefaultSystemOutputDevice, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain, + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM, }; static constexpr AudioObjectPropertyAddress default_output_device{ kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM }; const auto &aopa = @@ -195,9 +204,9 @@ int OSXOutput::GetVolume() { static constexpr AudioObjectPropertyAddress aopa = { - kAudioHardwareServiceDeviceProperty_VirtualMainVolume, + KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain, + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM, }; const auto vol = AudioObjectGetPropertyDataT(dev_id, @@ -211,9 +220,9 @@ OSXOutput::SetVolume(unsigned new_volume) { Float32 vol = new_volume / 100.0; static constexpr AudioObjectPropertyAddress aopa = { - kAudioHardwareServiceDeviceProperty_VirtualMainVolume, + KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM }; UInt32 size = sizeof(vol); OSStatus status = AudioObjectSetPropertyData(dev_id, @@ -366,25 +375,25 @@ osx_output_set_device_format(AudioDeviceID dev_id, static constexpr AudioObjectPropertyAddress aopa_device_streams = { kAudioDevicePropertyStreams, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM }; static constexpr AudioObjectPropertyAddress aopa_stream_direction = { kAudioStreamPropertyDirection, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM }; static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM }; static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM }; OSStatus err; @@ -484,7 +493,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept static constexpr AudioObjectPropertyAddress aopa = { kAudioDevicePropertyHogMode, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMain + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM }; pid_t hog_pid; @@ -538,7 +547,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept static constexpr AudioObjectPropertyAddress aopa_name{ kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain, + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM, }; char actual_name[256]; @@ -561,7 +570,7 @@ FindAudioDeviceByName(const char *name) static constexpr AudioObjectPropertyAddress aopa_hw_devices{ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain, + KAUDIO_OBJECT_PROPERTY_ELEMENT_MM, }; const auto ids = From 7c920ddebe172887ad835f90d3052c57d8730120 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 8 Aug 2022 21:31:49 +0200 Subject: [PATCH 2/7] filter/ffmpeg: fix FFmpeg 5.1 deprecation warnings --- src/filter/plugins/FfmpegFilter.cxx | 9 +++++++++ src/filter/plugins/FfmpegFilter.hxx | 8 +++++++- src/lib/ffmpeg/DetectFilterFormat.cxx | 12 +++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/filter/plugins/FfmpegFilter.cxx b/src/filter/plugins/FfmpegFilter.cxx index 7786407fe..324b88ec2 100644 --- a/src/filter/plugins/FfmpegFilter.cxx +++ b/src/filter/plugins/FfmpegFilter.cxx @@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format, buffer_sink(_buffer_sink), in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)), in_sample_rate(in_audio_format.sample_rate), +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100) in_channels(in_audio_format.channels), +#endif in_audio_frame_size(in_audio_format.GetFrameSize()), out_audio_frame_size(_out_audio_format.GetFrameSize()) { +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100) + av_channel_layout_default(&in_ch_layout, in_audio_format.channels); +#endif } ConstBuffer @@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer src) frame.Unref(); frame->format = in_format; frame->sample_rate = in_sample_rate; +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100) + frame->ch_layout = in_ch_layout; +#else frame->channels = in_channels; +#endif frame->nb_samples = src.size / in_audio_frame_size; frame.GetBuffer(); diff --git a/src/filter/plugins/FfmpegFilter.hxx b/src/filter/plugins/FfmpegFilter.hxx index e889eb672..6f7c3773c 100644 --- a/src/filter/plugins/FfmpegFilter.hxx +++ b/src/filter/plugins/FfmpegFilter.hxx @@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter { FfmpegBuffer interleave_buffer; - const int in_format, in_sample_rate, in_channels; + const int in_format, in_sample_rate; + +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100) + AVChannelLayout in_ch_layout; +#else + const int in_channels; +#endif const size_t in_audio_frame_size; const size_t out_audio_frame_size; diff --git a/src/lib/ffmpeg/DetectFilterFormat.cxx b/src/lib/ffmpeg/DetectFilterFormat.cxx index 7b44eabb4..650394b9f 100644 --- a/src/lib/ffmpeg/DetectFilterFormat.cxx +++ b/src/lib/ffmpeg/DetectFilterFormat.cxx @@ -48,7 +48,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format, Frame frame; frame->format = ToFfmpegSampleFormat(in_audio_format.format); frame->sample_rate = in_audio_format.sample_rate; +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100) + av_channel_layout_default(&frame->ch_layout, in_audio_format.channels); +#else frame->channels = in_audio_format.channels; +#endif frame->nb_samples = 1; frame.GetBuffer(); @@ -75,8 +79,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format, if (sample_format == SampleFormat::UNDEFINED) throw std::runtime_error("Unsupported FFmpeg sample format"); +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100) + const unsigned out_channels = frame->ch_layout.nb_channels; +#else + const unsigned out_channels = frame->channels; +#endif + return CheckAudioFormat(frame->sample_rate, sample_format, - frame->channels); + out_channels); } } // namespace Ffmpeg From d3b235bab51210768e270eb4439273eb266888cc Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 8 Aug 2022 22:38:21 +0200 Subject: [PATCH 3/7] input/CdioParanoia: move global variables up --- src/input/plugins/CdioParanoiaInputPlugin.cxx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx index a2e9c8211..5a7442f26 100644 --- a/src/input/plugins/CdioParanoiaInputPlugin.cxx +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -45,6 +45,11 @@ #include +static constexpr Domain cdio_domain("cdio"); + +static bool default_reverse_endian; +static unsigned speed = 0; + class CdioParanoiaInputStream final : public InputStream { cdrom_drive_t *const drv; CdIo_t *const cdio; @@ -98,11 +103,6 @@ class CdioParanoiaInputStream final : public InputStream { void Seek(std::unique_lock &lock, offset_type offset) override; }; -static constexpr Domain cdio_domain("cdio"); - -static bool default_reverse_endian; -static unsigned speed = 0; - static void input_cdio_init(EventLoop &, const ConfigBlock &block) { From dc07180e48df80178d0d774a675e3c66652d262d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 8 Aug 2022 22:47:06 +0200 Subject: [PATCH 4/7] input/CdioParanoia: add options "mode" and "skip" Closes https://github.com/MusicPlayerDaemon/MPD/issues/1529 --- NEWS | 2 ++ doc/plugins.rst | 5 ++++ src/input/plugins/CdioParanoiaInputPlugin.cxx | 27 ++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index dcd7869ff..1a9840bdf 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.23.9 (not yet released) +* input + - cdio_paranoia: add options "mode" and "skip" * decoder - ffmpeg: support FFmpeg 5.1 * output diff --git a/doc/plugins.rst b/doc/plugins.rst index 5216a9b4d..0aa36dac8 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]". - If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this. * - **speed N** - Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet. + * - **mode disable|overlap|full** + - Set the paranoia mode; ``disable`` means no fixups, ``overlap`` + performs overlapped reads, and ``full`` enables all options. + * - **skip yes|no** + - If set to ``no``, then never skip failed reads. curl ---- diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx index 5a7442f26..b60490ec0 100644 --- a/src/input/plugins/CdioParanoiaInputPlugin.cxx +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -50,6 +50,9 @@ static constexpr Domain cdio_domain("cdio"); static bool default_reverse_endian; static unsigned speed = 0; +/* Default to full paranoia, but allow skipping sectors. */ +static int mode_flags = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP; + class CdioParanoiaInputStream final : public InputStream { cdrom_drive_t *const drv; CdIo_t *const cdio; @@ -70,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream { lsn_from(_lsn_from), buffer_lsn(-1) { - /* Set reading mode for full paranoia, but allow - skipping sectors. */ - para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + para.SetMode(mode_flags); /* seek to beginning of the track */ para.Seek(lsn_from); @@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block) value); } speed = block.GetBlockValue("speed",0U); + + if (const auto *param = block.GetBlockParam("mode")) { + param->With([](const char *s){ + if (StringIsEqual(s, "disable")) + mode_flags = PARANOIA_MODE_DISABLE; + else if (StringIsEqual(s, "overlap")) + mode_flags = PARANOIA_MODE_OVERLAP; + else if (StringIsEqual(s, "full")) + mode_flags = PARANOIA_MODE_FULL; + else + throw std::invalid_argument{"Invalid paranoia mode"}; + }); + } + + if (const auto *param = block.GetBlockParam("skip")) { + if (param->GetBoolValue()) + mode_flags &= ~PARANOIA_MODE_NEVERSKIP; + else + mode_flags |= PARANOIA_MODE_NEVERSKIP; + } } struct CdioUri { From 615c301961c092e375ccd852b17d5a5f3bb5bbaa Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 8 Aug 2022 23:13:03 +0200 Subject: [PATCH 5/7] mixer/Volume: remove logging (mostly useless) --- src/mixer/Volume.cxx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/mixer/Volume.cxx b/src/mixer/Volume.cxx index 4afb97e5d..f86f4cbb3 100644 --- a/src/mixer/Volume.cxx +++ b/src/mixer/Volume.cxx @@ -21,10 +21,8 @@ #include "output/MultipleOutputs.hxx" #include "Idle.hxx" #include "util/StringCompare.hxx" -#include "util/Domain.hxx" #include "system/PeriodClock.hxx" #include "io/BufferedOutputStream.hxx" -#include "Log.hxx" #include @@ -32,8 +30,6 @@ #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 */ @@ -105,9 +101,7 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs) sv = strtol(line, &end, 10); if (*end == 0 && sv >= 0 && sv <= 100) software_volume_change(outputs, sv); - else - FmtWarning(volume_domain, - "Can't parse software volume: {}", line); + return true; } From 4b4f47002b2d6b35ce92a10d6c6124fa00c0d85c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 8 Aug 2022 23:15:09 +0200 Subject: [PATCH 6/7] mixer/Volume: refactor to class MixerMemento, per partition Eliminate global variables, convert them to MixerMemento fields. Closes https://github.com/MusicPlayerDaemon/MPD/issues/1583 --- NEWS | 1 + meson.build | 2 +- src/Partition.cxx | 3 +- src/Partition.hxx | 3 ++ src/StateFile.cxx | 9 ++-- src/command/OtherCommands.cxx | 14 ++--- src/command/OutputCommands.cxx | 18 +++++-- src/command/PlayerCommands.cxx | 3 +- src/mixer/{Volume.cxx => Memento.cxx} | 43 ++++------------ src/mixer/Memento.hxx | 73 +++++++++++++++++++++++++++ src/mixer/Volume.hxx | 55 -------------------- src/output/OutputCommand.cxx | 20 +++++--- src/output/OutputCommand.hxx | 13 +++-- 13 files changed, 141 insertions(+), 116 deletions(-) rename src/mixer/{Volume.cxx => Memento.cxx} (66%) create mode 100644 src/mixer/Memento.hxx delete mode 100644 src/mixer/Volume.hxx diff --git a/NEWS b/NEWS index 1a9840bdf..23f2c41ff 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ ver 0.23.9 (not yet released) - ffmpeg: support FFmpeg 5.1 * output - pipewire: set app icon +* fix bogus volume levels with multiple partitions * improve iconv detection * macOS: fix macOS 10 build problem (0.23.8 regression) diff --git a/meson.build b/meson.build index 51d6139a4..22f8c466c 100644 --- a/meson.build +++ b/meson.build @@ -352,7 +352,7 @@ sources = [ 'src/TagStream.cxx', 'src/TagAny.cxx', 'src/TimePrint.cxx', - 'src/mixer/Volume.cxx', + 'src/mixer/Memento.cxx', 'src/PlaylistFile.cxx', ] diff --git a/src/Partition.cxx b/src/Partition.cxx index cf3396652..e5d02feab 100644 --- a/src/Partition.cxx +++ b/src/Partition.cxx @@ -23,7 +23,6 @@ #include "Log.hxx" #include "lib/fmt/ExceptionFormatter.hxx" #include "song/DetachedSong.hxx" -#include "mixer/Volume.hxx" #include "IdleFlags.hxx" #include "client/Listener.hxx" #include "client/Client.hxx" @@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept void Partition::OnMixerVolumeChanged(Mixer &, int) noexcept { - InvalidateHardwareVolume(); + mixer_memento.InvalidateHardwareVolume(); /* notify clients */ EmitIdle(IDLE_MIXER); diff --git a/src/Partition.hxx b/src/Partition.hxx index b90a01101..a4136836d 100644 --- a/src/Partition.hxx +++ b/src/Partition.hxx @@ -25,6 +25,7 @@ #include "queue/Listener.hxx" #include "output/MultipleOutputs.hxx" #include "mixer/Listener.hxx" +#include "mixer/Memento.hxx" #include "player/Control.hxx" #include "player/Listener.hxx" #include "protocol/RangeArg.hxx" @@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { MultipleOutputs outputs; + MixerMemento mixer_memento; + PlayerControl pc; ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; diff --git a/src/StateFile.cxx b/src/StateFile.cxx index 46490b88d..5ad1e4d60 100644 --- a/src/StateFile.cxx +++ b/src/StateFile.cxx @@ -27,7 +27,6 @@ #include "storage/StorageState.hxx" #include "Partition.hxx" #include "Instance.hxx" -#include "mixer/Volume.hxx" #include "SongLoader.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config, void StateFile::RememberVersions() noexcept { - prev_volume_version = sw_volume_state_get_hash(); + prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash(); prev_output_version = audio_output_state_get_version(); prev_playlist_version = playlist_state_get_hash(partition.playlist, partition.pc); @@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept bool StateFile::IsModified() const noexcept { - return prev_volume_version != sw_volume_state_get_hash() || + return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() || prev_output_version != audio_output_state_get_version() || prev_playlist_version != playlist_state_get_hash(partition.playlist, partition.pc) @@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept inline void StateFile::Write(BufferedOutputStream &os) { - save_sw_volume_state(os); + partition.mixer_memento.SaveSoftwareVolumeState(os); audio_output_state_save(os, partition.outputs); #ifdef ENABLE_DATABASE @@ -125,7 +124,7 @@ try { const char *line; while ((line = file.ReadLine()) != nullptr) { - success = read_sw_volume_state(line, partition.outputs) || + success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) || audio_output_state_read(line, partition.outputs) || playlist_state_restore(config, line, file, song_loader, partition.playlist, diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 43a53f7c8..217bcaaec 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -33,7 +33,6 @@ #include "TimePrint.hxx" #include "decoder/DecoderPrint.hxx" #include "ls.hxx" -#include "mixer/Volume.hxx" #include "time/ChronoUtil.hxx" #include "util/UriUtil.hxx" #include "util/StringAPI.hxx" @@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r) { auto &partition = client.GetPartition(); - const auto volume = volume_level_get(partition.outputs); + const auto volume = partition.mixer_memento.GetVolume(partition.outputs); if (volume >= 0) r.Fmt(FMT_STRING("volume: {}\n"), volume); @@ -337,7 +336,8 @@ handle_setvol(Client &client, Request args, Response &) { unsigned level = args.ParseUnsigned(0, 100); - volume_level_change(client.GetPartition().outputs, level); + auto &partition = client.GetPartition(); + partition.mixer_memento.SetVolume(partition.outputs, level); return CommandResult::OK; } @@ -346,9 +346,11 @@ handle_volume(Client &client, Request args, Response &r) { int relative = args.ParseInt(0, -100, 100); - auto &outputs = client.GetPartition().outputs; + auto &partition = client.GetPartition(); + auto &outputs = partition.outputs; + auto &mixer_memento = partition.mixer_memento; - const int old_volume = volume_level_get(outputs); + const int old_volume = mixer_memento.GetVolume(outputs); if (old_volume < 0) { r.Error(ACK_ERROR_SYSTEM, "No mixer"); return CommandResult::ERROR; @@ -361,7 +363,7 @@ handle_volume(Client &client, Request args, Response &r) new_volume = 100; if (new_volume != old_volume) - volume_level_change(outputs, new_volume); + mixer_memento.SetVolume(outputs, new_volume); return CommandResult::OK; } diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index cb8557f79..46456d03c 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r) assert(args.size == 1); unsigned device = args.ParseUnsigned(0); - if (!audio_output_enable_index(client.GetPartition().outputs, device)) { + auto &partition = client.GetPartition(); + + if (!audio_output_enable_index(partition.outputs, + partition.mixer_memento, + device)) { r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; } @@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r) assert(args.size == 1); unsigned device = args.ParseUnsigned(0); - if (!audio_output_disable_index(client.GetPartition().outputs, device)) { + auto &partition = client.GetPartition(); + + if (!audio_output_disable_index(partition.outputs, + partition.mixer_memento, + device)) { r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; } @@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r) assert(args.size == 1); unsigned device = args.ParseUnsigned(0); - if (!audio_output_toggle_index(client.GetPartition().outputs, device)) { + auto &partition = client.GetPartition(); + + if (!audio_output_toggle_index(partition.outputs, + partition.mixer_memento, + device)) { r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; } diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index ca84468b7..d72949af6 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -25,7 +25,6 @@ #include "SingleMode.hxx" #include "client/Client.hxx" #include "client/Response.hxx" -#include "mixer/Volume.hxx" #include "Partition.hxx" #include "Instance.hxx" #include "IdleFlags.hxx" @@ -131,7 +130,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r) const auto &playlist = partition.playlist; - const auto volume = volume_level_get(partition.outputs); + const auto volume = partition.mixer_memento.GetVolume(partition.outputs); if (volume >= 0) r.Fmt(FMT_STRING("volume: {}\n"), volume); diff --git a/src/mixer/Volume.cxx b/src/mixer/Memento.cxx similarity index 66% rename from src/mixer/Volume.cxx rename to src/mixer/Memento.cxx index f86f4cbb3..c2ec128ab 100644 --- a/src/mixer/Volume.cxx +++ b/src/mixer/Memento.cxx @@ -17,11 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "Volume.hxx" +#include "Memento.hxx" #include "output/MultipleOutputs.hxx" #include "Idle.hxx" #include "util/StringCompare.hxx" -#include "system/PeriodClock.hxx" #include "io/BufferedOutputStream.hxx" #include @@ -30,22 +29,8 @@ #define SW_VOLUME_STATE "sw_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() noexcept -{ - /* flush the hardware volume cache */ - last_hardware_volume = -1; -} - int -volume_level_get(const MultipleOutputs &outputs) noexcept +MixerMemento::GetVolume(const MultipleOutputs &outputs) noexcept { if (last_hardware_volume >= 0 && !hardware_volume_clock.CheckUpdate(std::chrono::seconds(1))) @@ -56,8 +41,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept return last_hardware_volume; } -static bool -software_volume_change(MultipleOutputs &outputs, unsigned volume) +inline bool +MixerMemento::SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume) { assert(volume <= 100); @@ -67,8 +52,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume) return true; } -static void -hardware_volume_change(MultipleOutputs &outputs, unsigned volume) +inline void +MixerMemento::SetHardwareVolume(MultipleOutputs &outputs, unsigned volume) { /* reset the cache */ last_hardware_volume = -1; @@ -77,7 +62,7 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume) } void -volume_level_change(MultipleOutputs &outputs, unsigned volume) +MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume) { assert(volume <= 100); @@ -85,11 +70,11 @@ volume_level_change(MultipleOutputs &outputs, unsigned volume) idle_add(IDLE_MIXER); - hardware_volume_change(outputs, volume); + SetHardwareVolume(outputs, volume); } bool -read_sw_volume_state(const char *line, MultipleOutputs &outputs) +MixerMemento::LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs) { char *end = nullptr; long int sv; @@ -100,19 +85,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs) sv = strtol(line, &end, 10); if (*end == 0 && sv >= 0 && sv <= 100) - software_volume_change(outputs, sv); + SetSoftwareVolume(outputs, sv); return true; } void -save_sw_volume_state(BufferedOutputStream &os) +MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const { os.Format(SW_VOLUME_STATE "%u\n", volume_software_set); } - -unsigned -sw_volume_state_get_hash() noexcept -{ - return volume_software_set; -} diff --git a/src/mixer/Memento.hxx b/src/mixer/Memento.hxx new file mode 100644 index 000000000..32d23fdc9 --- /dev/null +++ b/src/mixer/Memento.hxx @@ -0,0 +1,73 @@ +/* + * Copyright 2003-2021 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. + */ + +#pragma once + +#include "system/PeriodClock.hxx" + +class MultipleOutputs; +class BufferedOutputStream; + +/** + * Cache for hardware/software volume levels. + */ +class MixerMemento { + unsigned volume_software_set = 100; + + /** the cached hardware mixer value; invalid if negative */ + int last_hardware_volume = -1; + + /** the age of #last_hardware_volume */ + PeriodClock hardware_volume_clock; + +public: + /** + * Flush the hardware volume cache. + */ + void InvalidateHardwareVolume() noexcept { + last_hardware_volume = -1; + } + + [[gnu::pure]] + int GetVolume(const MultipleOutputs &outputs) noexcept; + + /** + * Throws on error. + */ + void SetVolume(MultipleOutputs &outputs, unsigned volume); + + bool LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs); + + void SaveSoftwareVolumeState(BufferedOutputStream &os) const; + + /** + * 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. + */ + [[gnu::pure]] + unsigned GetSoftwareVolumeStateHash() const noexcept { + return volume_software_set; + } + +private: + bool SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume); + void SetHardwareVolume(MultipleOutputs &outputs, unsigned volume); +}; diff --git a/src/mixer/Volume.hxx b/src/mixer/Volume.hxx deleted file mode 100644 index b245fe7fe..000000000 --- a/src/mixer/Volume.hxx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2003-2021 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 - -class MultipleOutputs; -class BufferedOutputStream; - -void -InvalidateHardwareVolume() noexcept; - -[[gnu::pure]] -int -volume_level_get(const MultipleOutputs &outputs) noexcept; - -/** - * Throws on error. - */ -void -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. - */ -[[gnu::pure]] -unsigned -sw_volume_state_get_hash() noexcept; - -#endif diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx index 5772072cb..88c51df5d 100644 --- a/src/output/OutputCommand.cxx +++ b/src/output/OutputCommand.cxx @@ -28,13 +28,15 @@ #include "MultipleOutputs.hxx" #include "Client.hxx" #include "mixer/MixerControl.hxx" -#include "mixer/Volume.hxx" +#include "mixer/Memento.hxx" #include "Idle.hxx" extern unsigned audio_output_state_version; bool -audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) +audio_output_enable_index(MultipleOutputs &outputs, + MixerMemento &mixer_memento, + unsigned idx) { if (idx >= outputs.Size()) return false; @@ -46,7 +48,7 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) idle_add(IDLE_OUTPUT); if (ao.GetMixer() != nullptr) { - InvalidateHardwareVolume(); + mixer_memento.InvalidateHardwareVolume(); idle_add(IDLE_MIXER); } @@ -58,7 +60,9 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) } bool -audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) +audio_output_disable_index(MultipleOutputs &outputs, + MixerMemento &mixer_memento, + unsigned idx) { if (idx >= outputs.Size()) return false; @@ -72,7 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) auto *mixer = ao.GetMixer(); if (mixer != nullptr) { mixer_close(mixer); - InvalidateHardwareVolume(); + mixer_memento.InvalidateHardwareVolume(); idle_add(IDLE_MIXER); } @@ -84,7 +88,9 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) } bool -audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx) +audio_output_toggle_index(MultipleOutputs &outputs, + MixerMemento &mixer_memento, + unsigned idx) { if (idx >= outputs.Size()) return false; @@ -97,7 +103,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx) auto *mixer = ao.GetMixer(); if (mixer != nullptr) { mixer_close(mixer); - InvalidateHardwareVolume(); + mixer_memento.InvalidateHardwareVolume(); idle_add(IDLE_MIXER); } } diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx index 19c18215f..1b586f34f 100644 --- a/src/output/OutputCommand.hxx +++ b/src/output/OutputCommand.hxx @@ -28,26 +28,33 @@ #define MPD_OUTPUT_COMMAND_HXX class MultipleOutputs; +class MixerMemento; /** * Enables an audio output. Returns false if the specified output * does not exist. */ bool -audio_output_enable_index(MultipleOutputs &outputs, unsigned idx); +audio_output_enable_index(MultipleOutputs &outputs, + MixerMemento &mixer_memento, + unsigned idx); /** * Disables an audio output. Returns false if the specified output * does not exist. */ bool -audio_output_disable_index(MultipleOutputs &outputs, unsigned idx); +audio_output_disable_index(MultipleOutputs &outputs, + MixerMemento &mixer_memento, + unsigned idx); /** * Toggles an audio output. Returns false if the specified output * does not exist. */ bool -audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx); +audio_output_toggle_index(MultipleOutputs &outputs, + MixerMemento &mixer_memento, + unsigned idx); #endif From 69f741e8a636d2fac87c4af4bb2d75454a4195cc Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 8 Aug 2022 23:31:25 +0200 Subject: [PATCH 7/7] mixer/Memento: move IDLE_MIXER out of SetVolume() Make this idle event per-partition. --- src/command/OtherCommands.cxx | 5 ++++- src/mixer/Memento.cxx | 2 -- src/mixer/Memento.hxx | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 217bcaaec..7f68cf83f 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -338,6 +338,7 @@ handle_setvol(Client &client, Request args, Response &) auto &partition = client.GetPartition(); partition.mixer_memento.SetVolume(partition.outputs, level); + partition.EmitIdle(IDLE_MIXER); return CommandResult::OK; } @@ -362,8 +363,10 @@ handle_volume(Client &client, Request args, Response &r) else if (new_volume > 100) new_volume = 100; - if (new_volume != old_volume) + if (new_volume != old_volume) { mixer_memento.SetVolume(outputs, new_volume); + partition.EmitIdle(IDLE_MIXER); + } return CommandResult::OK; } diff --git a/src/mixer/Memento.cxx b/src/mixer/Memento.cxx index c2ec128ab..f8d943426 100644 --- a/src/mixer/Memento.cxx +++ b/src/mixer/Memento.cxx @@ -68,8 +68,6 @@ MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume) volume_software_set = volume; - idle_add(IDLE_MIXER); - SetHardwareVolume(outputs, volume); } diff --git a/src/mixer/Memento.hxx b/src/mixer/Memento.hxx index 32d23fdc9..357e154dd 100644 --- a/src/mixer/Memento.hxx +++ b/src/mixer/Memento.hxx @@ -49,6 +49,8 @@ public: /** * Throws on error. + * + * Note: the caller is responsible for emitting #IDLE_MIXER. */ void SetVolume(MultipleOutputs &outputs, unsigned volume);