Merge branch 'v0.23.x'

This commit is contained in:
Max Kellermann 2022-08-08 23:46:22 +02:00
commit b789ffd2bf
19 changed files with 234 additions and 149 deletions

4
NEWS
View File

@ -19,11 +19,15 @@ ver 0.24 (not yet released)
* remove Haiku support * remove Haiku support
ver 0.23.9 (not yet released) ver 0.23.9 (not yet released)
* input
- cdio_paranoia: add options "mode" and "skip"
* decoder * decoder
- ffmpeg: support FFmpeg 5.1 - ffmpeg: support FFmpeg 5.1
* output * output
- pipewire: set app icon - pipewire: set app icon
* fix bogus volume levels with multiple partitions
* improve iconv detection * improve iconv detection
* macOS: fix macOS 10 build problem (0.23.8 regression)
ver 0.23.8 (2022/07/09) ver 0.23.8 (2022/07/09)
* storage * storage

View File

@ -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. - 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** * - **speed N**
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet. - 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 curl
---- ----

View File

@ -354,7 +354,7 @@ sources = [
'src/TagStream.cxx', 'src/TagStream.cxx',
'src/TagAny.cxx', 'src/TagAny.cxx',
'src/TimePrint.cxx', 'src/TimePrint.cxx',
'src/mixer/Volume.cxx', 'src/mixer/Memento.cxx',
'src/PlaylistFile.cxx', 'src/PlaylistFile.cxx',
] ]

View File

@ -24,7 +24,6 @@
#include "config/PartitionConfig.hxx" #include "config/PartitionConfig.hxx"
#include "lib/fmt/ExceptionFormatter.hxx" #include "lib/fmt/ExceptionFormatter.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "mixer/Volume.hxx"
#include "IdleFlags.hxx" #include "IdleFlags.hxx"
#include "client/Listener.hxx" #include "client/Listener.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
@ -220,7 +219,7 @@ Partition::OnBorderPause() noexcept
void void
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
{ {
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
/* notify clients */ /* notify clients */
EmitIdle(IDLE_MIXER); EmitIdle(IDLE_MIXER);

View File

@ -25,6 +25,7 @@
#include "queue/Listener.hxx" #include "queue/Listener.hxx"
#include "output/MultipleOutputs.hxx" #include "output/MultipleOutputs.hxx"
#include "mixer/Listener.hxx" #include "mixer/Listener.hxx"
#include "mixer/Memento.hxx"
#include "player/Control.hxx" #include "player/Control.hxx"
#include "player/Listener.hxx" #include "player/Listener.hxx"
#include "protocol/RangeArg.hxx" #include "protocol/RangeArg.hxx"
@ -79,6 +80,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
MultipleOutputs outputs; MultipleOutputs outputs;
MixerMemento mixer_memento;
PlayerControl pc; PlayerControl pc;
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;

View File

@ -27,7 +27,6 @@
#include "storage/StorageState.hxx" #include "storage/StorageState.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "mixer/Volume.hxx"
#include "SongLoader.hxx" #include "SongLoader.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx" #include "Log.hxx"
@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
void void
StateFile::RememberVersions() noexcept 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_output_version = audio_output_state_get_version();
prev_playlist_version = playlist_state_get_hash(partition.playlist, prev_playlist_version = playlist_state_get_hash(partition.playlist,
partition.pc); partition.pc);
@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
bool bool
StateFile::IsModified() const noexcept 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_output_version != audio_output_state_get_version() ||
prev_playlist_version != playlist_state_get_hash(partition.playlist, prev_playlist_version != playlist_state_get_hash(partition.playlist,
partition.pc) partition.pc)
@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
inline void inline void
StateFile::Write(BufferedOutputStream &os) StateFile::Write(BufferedOutputStream &os)
{ {
save_sw_volume_state(os); partition.mixer_memento.SaveSoftwareVolumeState(os);
audio_output_state_save(os, partition.outputs); audio_output_state_save(os, partition.outputs);
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
@ -125,7 +124,7 @@ try {
const char *line; const char *line;
while ((line = file.ReadLine()) != nullptr) { 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) || audio_output_state_read(line, partition.outputs) ||
playlist_state_restore(config, line, file, song_loader, playlist_state_restore(config, line, file, song_loader,
partition.playlist, partition.playlist,

View File

@ -33,7 +33,6 @@
#include "TimePrint.hxx" #include "TimePrint.hxx"
#include "decoder/DecoderPrint.hxx" #include "decoder/DecoderPrint.hxx"
#include "ls.hxx" #include "ls.hxx"
#include "mixer/Volume.hxx"
#include "time/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
@ -324,7 +323,7 @@ handle_getvol(Client &client, Request, Response &r)
{ {
auto &partition = client.GetPartition(); auto &partition = client.GetPartition();
const auto volume = volume_level_get(partition.outputs); const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0) if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume); r.Fmt(FMT_STRING("volume: {}\n"), volume);
@ -336,7 +335,9 @@ handle_setvol(Client &client, Request args, Response &)
{ {
unsigned level = args.ParseUnsigned(0, 100); 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; return CommandResult::OK;
} }
@ -345,9 +346,11 @@ handle_volume(Client &client, Request args, Response &r)
{ {
int relative = args.ParseInt(0, -100, 100); 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) { if (old_volume < 0) {
r.Error(ACK_ERROR_SYSTEM, "No mixer"); r.Error(ACK_ERROR_SYSTEM, "No mixer");
return CommandResult::ERROR; return CommandResult::ERROR;
@ -359,8 +362,10 @@ handle_volume(Client &client, Request args, Response &r)
else if (new_volume > 100) else if (new_volume > 100)
new_volume = 100; new_volume = 100;
if (new_volume != old_volume) if (new_volume != old_volume) {
volume_level_change(outputs, new_volume); mixer_memento.SetVolume(outputs, new_volume);
partition.EmitIdle(IDLE_MIXER);
}
return CommandResult::OK; return CommandResult::OK;
} }

View File

@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
assert(args.size() == 1); assert(args.size() == 1);
unsigned device = args.ParseUnsigned(0); 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"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
assert(args.size() == 1); assert(args.size() == 1);
unsigned device = args.ParseUnsigned(0); 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"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
assert(args.size() == 1); assert(args.size() == 1);
unsigned device = args.ParseUnsigned(0); 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"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }

View File

@ -25,7 +25,6 @@
#include "SingleMode.hxx" #include "SingleMode.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "client/Response.hxx" #include "client/Response.hxx"
#include "mixer/Volume.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "IdleFlags.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 &playlist = partition.playlist;
const auto volume = volume_level_get(partition.outputs); const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0) if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume); r.Fmt(FMT_STRING("volume: {}\n"), volume);

View File

@ -39,10 +39,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
buffer_sink(_buffer_sink), buffer_sink(_buffer_sink),
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)), in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
in_sample_rate(in_audio_format.sample_rate), in_sample_rate(in_audio_format.sample_rate),
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
in_channels(in_audio_format.channels), in_channels(in_audio_format.channels),
#endif
in_audio_frame_size(in_audio_format.GetFrameSize()), in_audio_frame_size(in_audio_format.GetFrameSize()),
out_audio_frame_size(_out_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<const std::byte> std::span<const std::byte>
@ -53,7 +58,11 @@ FfmpegFilter::FilterPCM(std::span<const std::byte> src)
frame.Unref(); frame.Unref();
frame->format = in_format; frame->format = in_format;
frame->sample_rate = in_sample_rate; 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; frame->channels = in_channels;
#endif
frame->nb_samples = src.size() / in_audio_frame_size; frame->nb_samples = src.size() / in_audio_frame_size;
frame.GetBuffer(); frame.GetBuffer();

View File

@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
FfmpegBuffer interleave_buffer; 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 in_audio_frame_size;
const size_t out_audio_frame_size; const size_t out_audio_frame_size;

View File

@ -45,6 +45,14 @@
#include <cdio/cd_types.h> #include <cdio/cd_types.h>
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 { class CdioParanoiaInputStream final : public InputStream {
cdrom_drive_t *const drv; cdrom_drive_t *const drv;
CdIo_t *const cdio; CdIo_t *const cdio;
@ -65,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream {
lsn_from(_lsn_from), lsn_from(_lsn_from),
buffer_lsn(-1) buffer_lsn(-1)
{ {
/* Set reading mode for full paranoia, but allow para.SetMode(mode_flags);
skipping sectors. */
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
/* seek to beginning of the track */ /* seek to beginning of the track */
para.Seek(lsn_from); para.Seek(lsn_from);
@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override; void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
}; };
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static unsigned speed = 0;
static void static void
input_cdio_init(EventLoop &, const ConfigBlock &block) input_cdio_init(EventLoop &, const ConfigBlock &block)
{ {
@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
value); value);
} }
speed = block.GetBlockValue("speed",0U); 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 { struct CdioUri {

View File

@ -47,7 +47,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
Frame frame; Frame frame;
frame->format = ToFfmpegSampleFormat(in_audio_format.format); frame->format = ToFfmpegSampleFormat(in_audio_format.format);
frame->sample_rate = in_audio_format.sample_rate; 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; frame->channels = in_audio_format.channels;
#endif
frame->nb_samples = 1; frame->nb_samples = 1;
frame.GetBuffer(); frame.GetBuffer();
@ -74,8 +78,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
if (sample_format == SampleFormat::UNDEFINED) if (sample_format == SampleFormat::UNDEFINED)
throw std::runtime_error("Unsupported FFmpeg sample format"); 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, return CheckAudioFormat(frame->sample_rate, sample_format,
frame->channels); out_channels);
} }
} // namespace Ffmpeg } // namespace Ffmpeg

View File

@ -17,14 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "Volume.hxx" #include "Memento.hxx"
#include "output/MultipleOutputs.hxx" #include "output/MultipleOutputs.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/Domain.hxx"
#include "system/PeriodClock.hxx"
#include "io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "Log.hxx"
#include <fmt/format.h> #include <fmt/format.h>
@ -34,24 +31,8 @@
#define SW_VOLUME_STATE "sw_volume: " #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 int
volume_level_get(const MultipleOutputs &outputs) noexcept MixerMemento::GetVolume(const MultipleOutputs &outputs) noexcept
{ {
if (last_hardware_volume >= 0 && if (last_hardware_volume >= 0 &&
!hardware_volume_clock.CheckUpdate(std::chrono::seconds(1))) !hardware_volume_clock.CheckUpdate(std::chrono::seconds(1)))
@ -62,8 +43,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept
return last_hardware_volume; return last_hardware_volume;
} }
static bool inline bool
software_volume_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume)
{ {
assert(volume <= 100); assert(volume <= 100);
@ -73,8 +54,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
return true; return true;
} }
static void inline void
hardware_volume_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetHardwareVolume(MultipleOutputs &outputs, unsigned volume)
{ {
/* reset the cache */ /* reset the cache */
last_hardware_volume = -1; last_hardware_volume = -1;
@ -83,19 +64,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
} }
void void
volume_level_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume)
{ {
assert(volume <= 100); assert(volume <= 100);
volume_software_set = volume; volume_software_set = volume;
idle_add(IDLE_MIXER); SetHardwareVolume(outputs, volume);
hardware_volume_change(outputs, volume);
} }
bool bool
read_sw_volume_state(const char *line, MultipleOutputs &outputs) MixerMemento::LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs)
{ {
char *end = nullptr; char *end = nullptr;
long int sv; long int sv;
@ -106,21 +85,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs)
sv = strtol(line, &end, 10); sv = strtol(line, &end, 10);
if (*end == 0 && sv >= 0 && sv <= 100) if (*end == 0 && sv >= 0 && sv <= 100)
software_volume_change(outputs, sv); SetSoftwareVolume(outputs, sv);
else
FmtWarning(volume_domain,
"Can't parse software volume: {}", line);
return true; return true;
} }
void void
save_sw_volume_state(BufferedOutputStream &os) MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const
{ {
os.Fmt(FMT_STRING(SW_VOLUME_STATE "{}\n"), volume_software_set); os.Fmt(FMT_STRING(SW_VOLUME_STATE "{}\n"), volume_software_set);
} }
unsigned
sw_volume_state_get_hash() noexcept
{
return volume_software_set;
}

75
src/mixer/Memento.hxx Normal file
View File

@ -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);
};

View File

@ -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

View File

@ -28,13 +28,15 @@
#include "MultipleOutputs.hxx" #include "MultipleOutputs.hxx"
#include "Client.hxx" #include "Client.hxx"
#include "mixer/MixerControl.hxx" #include "mixer/MixerControl.hxx"
#include "mixer/Volume.hxx" #include "mixer/Memento.hxx"
#include "Idle.hxx" #include "Idle.hxx"
extern unsigned audio_output_state_version; extern unsigned audio_output_state_version;
bool bool
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) audio_output_enable_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx)
{ {
if (idx >= outputs.Size()) if (idx >= outputs.Size())
return false; return false;
@ -46,7 +48,7 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
idle_add(IDLE_OUTPUT); idle_add(IDLE_OUTPUT);
if (ao.GetMixer() != nullptr) { if (ao.GetMixer() != nullptr) {
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
@ -58,7 +60,9 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
} }
bool bool
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) audio_output_disable_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx)
{ {
if (idx >= outputs.Size()) if (idx >= outputs.Size())
return false; return false;
@ -72,7 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
auto *mixer = ao.GetMixer(); auto *mixer = ao.GetMixer();
if (mixer != nullptr) { if (mixer != nullptr) {
mixer_close(mixer); mixer_close(mixer);
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
@ -84,7 +88,9 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
} }
bool bool
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx) audio_output_toggle_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx)
{ {
if (idx >= outputs.Size()) if (idx >= outputs.Size())
return false; return false;
@ -97,7 +103,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
auto *mixer = ao.GetMixer(); auto *mixer = ao.GetMixer();
if (mixer != nullptr) { if (mixer != nullptr) {
mixer_close(mixer); mixer_close(mixer);
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
} }

View File

@ -28,26 +28,33 @@
#define MPD_OUTPUT_COMMAND_HXX #define MPD_OUTPUT_COMMAND_HXX
class MultipleOutputs; class MultipleOutputs;
class MixerMemento;
/** /**
* Enables an audio output. Returns false if the specified output * Enables an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool 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 * Disables an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool 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 * Toggles an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool bool
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx); audio_output_toggle_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx);
#endif #endif

View File

@ -47,6 +47,15 @@
#include <memory> #include <memory>
#include <span> #include <span>
// 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 constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100;
static StringBuffer<64> static StringBuffer<64>
@ -160,13 +169,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block)
static constexpr AudioObjectPropertyAddress default_system_output_device{ static constexpr AudioObjectPropertyAddress default_system_output_device{
kAudioHardwarePropertyDefaultSystemOutputDevice, kAudioHardwarePropertyDefaultSystemOutputDevice,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
static constexpr AudioObjectPropertyAddress default_output_device{ static constexpr AudioObjectPropertyAddress default_output_device{
kAudioHardwarePropertyDefaultOutputDevice, kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
const auto &aopa = const auto &aopa =
@ -195,9 +204,9 @@ int
OSXOutput::GetVolume() OSXOutput::GetVolume()
{ {
static constexpr AudioObjectPropertyAddress aopa = { static constexpr AudioObjectPropertyAddress aopa = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume, KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id, const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
@ -211,9 +220,9 @@ OSXOutput::SetVolume(unsigned new_volume)
{ {
Float32 vol = new_volume / 100.0; Float32 vol = new_volume / 100.0;
static constexpr AudioObjectPropertyAddress aopa = { static constexpr AudioObjectPropertyAddress aopa = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume, KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
UInt32 size = sizeof(vol); UInt32 size = sizeof(vol);
OSStatus status = AudioObjectSetPropertyData(dev_id, OSStatus status = AudioObjectSetPropertyData(dev_id,
@ -366,25 +375,25 @@ osx_output_set_device_format(AudioDeviceID dev_id,
static constexpr AudioObjectPropertyAddress aopa_device_streams = { static constexpr AudioObjectPropertyAddress aopa_device_streams = {
kAudioDevicePropertyStreams, kAudioDevicePropertyStreams,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
static constexpr AudioObjectPropertyAddress aopa_stream_direction = { static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
kAudioStreamPropertyDirection, kAudioStreamPropertyDirection,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = { static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
kAudioStreamPropertyAvailablePhysicalFormats, kAudioStreamPropertyAvailablePhysicalFormats,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = { static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
kAudioStreamPropertyPhysicalFormat, kAudioStreamPropertyPhysicalFormat,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
OSStatus err; OSStatus err;
@ -484,7 +493,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept
static constexpr AudioObjectPropertyAddress aopa = { static constexpr AudioObjectPropertyAddress aopa = {
kAudioDevicePropertyHogMode, kAudioDevicePropertyHogMode,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
pid_t hog_pid; pid_t hog_pid;
@ -538,7 +547,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
static constexpr AudioObjectPropertyAddress aopa_name{ static constexpr AudioObjectPropertyAddress aopa_name{
kAudioObjectPropertyName, kAudioObjectPropertyName,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
char actual_name[256]; char actual_name[256];
@ -561,7 +570,7 @@ FindAudioDeviceByName(const char *name)
static constexpr AudioObjectPropertyAddress aopa_hw_devices{ static constexpr AudioObjectPropertyAddress aopa_hw_devices{
kAudioHardwarePropertyDevices, kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
const auto ids = const auto ids =