diff --git a/NEWS b/NEWS index 6709515f9..088f23c76 100644 --- a/NEWS +++ b/NEWS @@ -19,11 +19,15 @@ ver 0.24 (not yet released) * remove Haiku support ver 0.23.9 (not yet released) +* input + - cdio_paranoia: add options "mode" and "skip" * decoder - 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) ver 0.23.8 (2022/07/09) * storage diff --git a/doc/plugins.rst b/doc/plugins.rst index 055abd9f7..158a8be5a 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/meson.build b/meson.build index f1db099f4..d3c5a76fa 100644 --- a/meson.build +++ b/meson.build @@ -354,7 +354,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 d2cf82dc3..7134015bc 100644 --- a/src/Partition.cxx +++ b/src/Partition.cxx @@ -24,7 +24,6 @@ #include "config/PartitionConfig.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" @@ -220,7 +219,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 02dafc8e5..6f9de5931 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" @@ -79,6 +80,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 a49dec7d4..50c3f1369 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 6aafa22b6..3c7adefa4 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" @@ -324,7 +323,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); @@ -336,7 +335,9 @@ 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); + partition.EmitIdle(IDLE_MIXER); return CommandResult::OK; } @@ -345,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; @@ -359,8 +362,10 @@ handle_volume(Client &client, Request args, Response &r) else if (new_volume > 100) new_volume = 100; - if (new_volume != old_volume) - volume_level_change(outputs, new_volume); + if (new_volume != old_volume) { + mixer_memento.SetVolume(outputs, new_volume); + partition.EmitIdle(IDLE_MIXER); + } return CommandResult::OK; } diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index 66f805a86..ae1f6215e 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 1227ba983..9d8138288 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/filter/plugins/FfmpegFilter.cxx b/src/filter/plugins/FfmpegFilter.cxx index 98a1e377d..95c49c348 100644 --- a/src/filter/plugins/FfmpegFilter.cxx +++ b/src/filter/plugins/FfmpegFilter.cxx @@ -39,10 +39,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 } std::span @@ -53,7 +58,11 @@ FfmpegFilter::FilterPCM(std::span 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 700498643..148964261 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/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx index 63ead8775..110dd842d 100644 --- a/src/input/plugins/CdioParanoiaInputPlugin.cxx +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -45,6 +45,14 @@ #include +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; @@ -65,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); @@ -98,11 +104,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) { @@ -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 { diff --git a/src/lib/ffmpeg/DetectFilterFormat.cxx b/src/lib/ffmpeg/DetectFilterFormat.cxx index f6489d60c..50a5ae8a5 100644 --- a/src/lib/ffmpeg/DetectFilterFormat.cxx +++ b/src/lib/ffmpeg/DetectFilterFormat.cxx @@ -47,7 +47,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(); @@ -74,8 +78,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 diff --git a/src/mixer/Volume.cxx b/src/mixer/Memento.cxx similarity index 62% rename from src/mixer/Volume.cxx rename to src/mixer/Memento.cxx index 01f0ed561..c82675ee8 100644 --- a/src/mixer/Volume.cxx +++ b/src/mixer/Memento.cxx @@ -17,14 +17,11 @@ * 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 "util/Domain.hxx" -#include "system/PeriodClock.hxx" #include "io/BufferedOutputStream.hxx" -#include "Log.hxx" #include @@ -34,24 +31,8 @@ #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() 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))) @@ -62,8 +43,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); @@ -73,8 +54,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; @@ -83,19 +64,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume) } void -volume_level_change(MultipleOutputs &outputs, unsigned volume) +MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume) { assert(volume <= 100); volume_software_set = 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; @@ -106,21 +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); - else - FmtWarning(volume_domain, - "Can't parse software volume: {}", line); + SetSoftwareVolume(outputs, sv); + return true; } void -save_sw_volume_state(BufferedOutputStream &os) +MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const { os.Fmt(FMT_STRING(SW_VOLUME_STATE "{}\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..357e154dd --- /dev/null +++ b/src/mixer/Memento.hxx @@ -0,0 +1,75 @@ +/* + * 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. + * + * Note: the caller is responsible for emitting #IDLE_MIXER. + */ + 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 8e5623bd3..000000000 --- a/src/mixer/Volume.hxx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2003-2022 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 12a39000a..69d87a18d 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 79ffd1bd5..b91b94870 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 diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx index 5cab1ad87..069e8329f 100644 --- a/src/output/plugins/OSXOutputPlugin.cxx +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -47,6 +47,15 @@ #include #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 =