Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions src/Instance.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,6 @@ Instance::DeletePartition(Partition &partition) noexcept
}
}

std::pair<Partition *, std::size_t>
Instance::FindOutput(std::string_view name,
Partition &excluding_partition) noexcept
{
for (auto &partition : partitions) {
if (&partition == &excluding_partition)
continue;

const auto idx = partition.outputs.FindIndexByName(name);
if (idx >= 0 && !partition.outputs.Get(idx).IsDummy())
return {&partition, idx};
}

return {};
}

#ifdef ENABLE_DATABASE

const Database &
Expand Down
14 changes: 3 additions & 11 deletions src/Instance.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define MPD_INSTANCE_HXX

#include "config.h"
#include "output/AllOutputs.hxx"
#include "db/Features.hxx" // for ENABLE_DATABASE
#include "event/Loop.hxx"
#include "event/Thread.hxx"
Expand Down Expand Up @@ -123,6 +124,8 @@ struct Instance final

std::unique_ptr<ClientList> client_list;

AllOutputs outputs;

std::list<Partition> partitions;

std::unique_ptr<StateFile> state_file;
Expand Down Expand Up @@ -177,17 +180,6 @@ struct Instance final

void BeginShutdownPartitions() noexcept;

/**
* Returns the (non-dummy) audio output device with the
* specified name. Returns nullptr if the name does not
* exist.
*
* @param excluding_partition ignore this partition
*/
[[gnu::pure]]
std::pair<Partition *, std::size_t> FindOutput(std::string_view name,
Partition &excluding_partition) noexcept;

#ifdef ENABLE_DATABASE
/**
* Returns the global #Database instance. May return nullptr
Expand Down
11 changes: 7 additions & 4 deletions src/Main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,14 @@ MainConfigured(const CommandLineOptions &options,

command_init();

instance.outputs.Configure(instance.io_thread.GetEventLoop(),
instance.rtio_thread.GetEventLoop(),
raw_config,
partition_config.player.replay_gain);

instance.partitions.front().outputs.AcquireAll(instance.partitions.front().replay_gain_mode);

for (auto &partition : instance.partitions) {
partition.outputs.Configure(instance.io_thread.GetEventLoop(),
instance.rtio_thread.GetEventLoop(),
raw_config,
partition_config.player.replay_gain);
partition.UpdateEffectiveReplayGainMode();
}

Expand Down
2 changes: 1 addition & 1 deletion src/Partition.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Partition::Partition(Instance &_instance,
idle_monitor(instance.event_loop, BIND_THIS_METHOD(OnIdleMonitor)),
global_events(instance.event_loop, BIND_THIS_METHOD(OnGlobalEvent)),
playlist(config.queue.max_length, *this),
outputs(pc, *this),
outputs(instance.outputs, pc, *this),
pc(*this, outputs,
instance.input_cache.get(),
config.player)
Expand Down
47 changes: 23 additions & 24 deletions src/command/PartitionCommands.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ handle_delpartition(Client &client, Request request, Response &response)
return CommandResult::ERROR;
}

if (!partition->outputs.IsDummy()) {
if (!partition->outputs.empty()) {
response.Error(ACK_ERROR_UNKNOWN,
"partition still has outputs");
return CommandResult::ERROR;
Expand All @@ -132,41 +132,40 @@ handle_delpartition(Client &client, Request request, Response &response)
return CommandResult::OK;
}

static Partition *
FindOwningPartition(AudioOutputControl &ao) noexcept
{
return static_cast<Partition *>(ao.GetMixerListener());
}

CommandResult
handle_moveoutput(Client &client, Request request, Response &response)
{
const std::string_view output_name = request[0];

auto &dest_partition = client.GetPartition();
const auto existing_output_index = dest_partition.outputs.FindIndexByName(output_name);
if (existing_output_index >= 0 && !dest_partition.outputs.Get(existing_output_index).IsDummy())
/* this output is already in the specified partition,
so nothing needs to be done */
return CommandResult::OK;

/* find the partition which owns this output currently */
auto &instance = client.GetInstance();

const auto [src_partition, src_index] = instance.FindOutput(output_name, dest_partition);
if (src_partition == nullptr) {
auto *ao = instance.outputs.FindByName(output_name);
if (ao == nullptr) {
response.Error(ACK_ERROR_NO_EXIST, "No such output");
return CommandResult::ERROR;
}

const bool was_enabled = src_partition->outputs.Get(src_index).IsEnabled();
auto &dest_partition = client.GetPartition();

/* find the partition which owns this output currently */
auto *src_partition = FindOwningPartition(*ao);
if (src_partition == &dest_partition)
/* this output is already in the specified partition,
so nothing needs to be done */
return CommandResult::OK;

const bool was_enabled = ao->IsEnabled();

auto output = src_partition->outputs.ReplaceWithDummy(src_index);
if (src_partition != nullptr)
src_partition->outputs.ReleaseOwnership(*ao);

if (existing_output_index >= 0)
/* move the output back where it once was */
dest_partition.outputs.ReplaceDummy(existing_output_index, std::move(output),
was_enabled,
dest_partition.replay_gain_mode);
else
/* copy the AudioOutputControl and add it to the output list */
dest_partition.outputs.Add(std::move(output),
was_enabled,
dest_partition.replay_gain_mode);
dest_partition.outputs.AcquireOwnership(*ao, was_enabled,
dest_partition.replay_gain_mode);

instance.EmitIdle(IDLE_OUTPUT);
return CommandResult::OK;
Expand Down
18 changes: 12 additions & 6 deletions src/mixer/All.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ output_mixer_get_volume(const AudioOutputControl &ao) noexcept
int
MultipleOutputs::GetVolume() const noexcept
{
const std::lock_guard lock{mutex};

unsigned ok = 0;
int total = 0;

for (const auto &ao : outputs) {
int volume = output_mixer_get_volume(*ao);
int volume = output_mixer_get_volume(ao);
if (volume >= 0) {
total += volume;
++ok;
Expand Down Expand Up @@ -104,9 +106,10 @@ MultipleOutputs::SetVolume(unsigned volume)
SetVolumeResult result = SetVolumeResult::NO_MIXER;
std::exception_ptr error;

for (const auto &ao : outputs) {
const std::lock_guard lock{mutex};
for (auto &ao : outputs) {
try {
auto r = output_mixer_set_volume(*ao, volume);
auto r = output_mixer_set_volume(ao, volume);
if (r > result)
result = r;
} catch (...) {
Expand Down Expand Up @@ -149,11 +152,13 @@ output_mixer_get_software_volume(const AudioOutputControl &ao) noexcept
int
MultipleOutputs::GetSoftwareVolume() const noexcept
{
const std::lock_guard lock{mutex};

unsigned ok = 0;
int total = 0;

for (const auto &ao : outputs) {
int volume = output_mixer_get_software_volume(*ao);
for (auto &ao : outputs) {
int volume = output_mixer_get_software_volume(ao);
if (volume >= 0) {
total += volume;
++ok;
Expand All @@ -171,8 +176,9 @@ MultipleOutputs::SetSoftwareVolume(unsigned volume) noexcept
{
assert(volume <= PCM_VOLUME_1);

const std::lock_guard lock{mutex};
for (const auto &ao : outputs) {
auto *mixer = ao->GetMixer();
auto *mixer = ao.GetMixer();

if (mixer != nullptr &&
(mixer->IsPlugin(software_mixer_plugin) ||
Expand Down
95 changes: 95 additions & 0 deletions src/output/AllOutputs.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project

#include "AllOutputs.hxx"
#include "Control.hxx"
#include "Defaults.hxx"
#include "Filtered.hxx"
#include "filter/Factory.hxx"
#include "config/Block.hxx"
#include "config/Data.hxx"
#include "config/Option.hxx"
#include "lib/fmt/RuntimeError.hxx"

AllOutputs::AllOutputs() noexcept = default;

AllOutputs::~AllOutputs() noexcept
{
/* parallel destruction */
for (const auto &i : outputs)
i->BeginDestroy();
}

static std::unique_ptr<FilteredAudioOutput>
LoadOutput(EventLoop &event_loop, EventLoop &rt_event_loop,
const ReplayGainConfig &replay_gain_config,
const ConfigBlock &block,
const AudioOutputDefaults &defaults,
FilterFactory *filter_factory)
try {
return audio_output_new(event_loop, rt_event_loop, replay_gain_config, block,
defaults,
filter_factory);
} catch (...) {
if (block.line > 0)
std::throw_with_nested(FmtRuntimeError("Failed to configure output in line {}",
block.line));
else
throw;
}

static std::unique_ptr<AudioOutputControl>
LoadOutputControl(EventLoop &event_loop, EventLoop &rt_event_loop,
const ReplayGainConfig &replay_gain_config,
const ConfigBlock &block,
const AudioOutputDefaults &defaults,
FilterFactory *filter_factory)
{
auto output = LoadOutput(event_loop, rt_event_loop,
replay_gain_config,
block, defaults, filter_factory);
return std::make_unique<AudioOutputControl>(std::move(output),
block);
}

void
AllOutputs::Configure(EventLoop &event_loop, EventLoop &rt_event_loop,
const ConfigData &config,
const ReplayGainConfig &replay_gain_config)
{
const AudioOutputDefaults defaults(config);
FilterFactory filter_factory(config);

config.WithEach(ConfigBlockOption::AUDIO_OUTPUT, [&, this](const auto &block){
auto output = LoadOutputControl(event_loop, rt_event_loop,
replay_gain_config,
block, defaults,
&filter_factory);
if (HasName(output->GetName()))
throw FmtRuntimeError("output devices with identical "
"names: {}",
output->GetName());

outputs.emplace_back(std::move(output));
});

if (outputs.empty()) {
/* auto-detect device */
const ConfigBlock empty;
outputs.emplace_back(LoadOutputControl(event_loop,
rt_event_loop,
replay_gain_config,
empty, defaults,
nullptr));
}
}

AudioOutputControl *
AllOutputs::FindByName(const std::string_view name) noexcept
{
for (const auto &i : outputs)
if (name == i->GetName())
return i.get();

return nullptr;
}
70 changes: 70 additions & 0 deletions src/output/AllOutputs.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project

#pragma once

#include <cassert>
#include <memory>
#include <vector>

struct ConfigData;
struct ReplayGainConfig;
class EventLoop;
class AudioOutputControl;

/**
* Manages the global list of #AudioOutputControl instances.
*
* This class only owns those objects, but #MultipleOutputs is the one
* that will drive them.
*/
class AllOutputs final {
std::vector<std::unique_ptr<AudioOutputControl>> outputs;

public:
AllOutputs() noexcept;
~AllOutputs() noexcept;

void Configure(EventLoop &event_loop, EventLoop &rt_event_loop,
const ConfigData &config,
const ReplayGainConfig &replay_gain_config);

/**
* Returns the total number of audio output devices, including
* those which are disabled right now.
*/
[[gnu::pure]]
std::size_t Size() const noexcept {
return outputs.size();
}

/**
* Returns the "i"th audio output device.
*/
const AudioOutputControl &Get(std::size_t i) const noexcept {
assert(i < Size());

return *outputs[i];
}

AudioOutputControl &Get(std::size_t i) noexcept {
assert(i < Size());

return *outputs[i];
}

/**
* Returns the audio output device with the specified name.
* Returns nullptr if the name does not exist.
*/
[[gnu::pure]]
AudioOutputControl *FindByName(std::string_view name) noexcept;

/**
* Does an audio output device with this name exist?
*/
[[gnu::pure]]
bool HasName(std::string_view name) noexcept {
return FindByName(name) != nullptr;
}
};
Loading
Loading