AudioServer+Userland: Separate audio IPC into normal client and manager

This is a sensible separation of concerns that mirrors the WindowServer
IPC split. On the one hand, there is the "normal" audio interface, used
for clients that play audio, which is the primary service of
AudioServer. On the other hand, there is the management interface,
which, like the WindowManager endpoint, provides higher-level control
over clients and the server itself.

The reasoning for this split are manifold, as mentioned we are mirroring
the WindowServer split. Another indication to the sensibility of the
split is that no single audio client used the APIs of both interfaces.
Also, useless audio queues are no longer created for managing clients
(since those don't even exist, just like there's no window backing
bitmap for window managing clients), eliminating any bugs that may occur
there as they have in the past.

Implementation-wise, we just move all the APIs and implementations from
the old AudioServer into the AudioManagerServer (and respective clients,
of course). There is one point of duplication, namely the hardware
sample rate. This will be fixed in combination with per-client sample
rate, eliminating client-side resampling and the related update bugs.
For now, we keep one legacy API to simplify the transition.

The new AudioManagerServer also gains a hardware sample rate change
callback to have exact symmetry on the main server parameters (getter,
setter, and callback).
This commit is contained in:
kleines Filmröllchen 2023-06-24 12:56:51 +02:00 committed by Jelle Raaijmakers
parent c3f5b514c8
commit 03fac609ee
20 changed files with 250 additions and 87 deletions

View file

@ -58,7 +58,7 @@ Priority=low
KeepAlive=true
[AudioServer]
Socket=/tmp/session/%sid/portal/audio
Socket=/tmp/session/%sid/portal/audio,/tmp/session/%sid/portal/audiomanager
Priority=high
KeepAlive=true
SystemModes=text,graphical

View file

@ -8,7 +8,7 @@
*/
#include <AK/Array.h>
#include <LibAudio/ConnectionToServer.h>
#include <LibAudio/ConnectionToManagerServer.h>
#include <LibConfig/Client.h>
#include <LibCore/System.h>
#include <LibGUI/Application.h>
@ -45,14 +45,14 @@ public:
{ 0, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-zero.png"sv)) },
{ 0, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-muted.png"sv)) } }
};
auto audio_client = TRY(Audio::ConnectionToServer::try_create());
auto audio_client = TRY(Audio::ConnectionToManagerServer::try_create());
NonnullRefPtr<AudioWidget> audio_widget = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) AudioWidget(move(audio_client), move(volume_level_bitmaps))));
TRY(audio_widget->try_initialize_graphical_elements());
return audio_widget;
}
private:
AudioWidget(NonnullRefPtr<Audio::ConnectionToServer> audio_client, Array<VolumeBitmapPair, 5> volume_level_bitmaps)
AudioWidget(NonnullRefPtr<Audio::ConnectionToManagerServer> audio_client, Array<VolumeBitmapPair, 5> volume_level_bitmaps)
: m_audio_client(move(audio_client))
, m_volume_level_bitmaps(move(volume_level_bitmaps))
{
@ -217,7 +217,7 @@ private:
height);
}
NonnullRefPtr<Audio::ConnectionToServer> m_audio_client;
NonnullRefPtr<Audio::ConnectionToManagerServer> m_audio_client;
Array<VolumeBitmapPair, 5> m_volume_level_bitmaps;
bool m_show_percent { false };
bool m_audio_muted { false };
@ -236,7 +236,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
auto app = TRY(GUI::Application::create(arguments));
Config::pledge_domain("AudioApplet");
TRY(Core::System::unveil("/tmp/session/%sid/portal/audio", "rw"));
TRY(Core::System::unveil("/tmp/session/%sid/portal/audiomanager", "rw"));
TRY(Core::System::unveil("/res", "r"));
TRY(Core::System::unveil(nullptr, nullptr));

View file

@ -16,9 +16,12 @@ set(SOURCES
if (SERENITYOS)
list(APPEND SOURCES ConnectionToServer.cpp)
list(APPEND SOURCES ConnectionToManagerServer.cpp)
set(GENERATED_SOURCES
../../Services/AudioServer/AudioClientEndpoint.h
../../Services/AudioServer/AudioServerEndpoint.h
../../Services/AudioServer/AudioManagerClientEndpoint.h
../../Services/AudioServer/AudioManagerServerEndpoint.h
)
endif()

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ConnectionToManagerServer.h"
namespace Audio {
ConnectionToManagerServer::ConnectionToManagerServer(NonnullOwnPtr<Core::LocalSocket> socket)
: IPC::ConnectionToServer<AudioManagerClientEndpoint, AudioManagerServerEndpoint>(*this, move(socket))
{
}
ConnectionToManagerServer::~ConnectionToManagerServer()
{
die();
}
void ConnectionToManagerServer::die() { }
void ConnectionToManagerServer::main_mix_muted_state_changed(bool muted)
{
if (on_main_mix_muted_state_change)
on_main_mix_muted_state_change(muted);
}
void ConnectionToManagerServer::main_mix_volume_changed(double volume)
{
if (on_main_mix_volume_change)
on_main_mix_volume_change(volume);
}
void ConnectionToManagerServer::device_sample_rate_changed(u32 sample_rate)
{
if (on_device_sample_rate_change)
on_device_sample_rate_change(sample_rate);
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <LibIPC/ConnectionToServer.h>
#include <Userland/Services/AudioServer/AudioManagerClientEndpoint.h>
#include <Userland/Services/AudioServer/AudioManagerServerEndpoint.h>
namespace Audio {
class ConnectionToManagerServer final
: public IPC::ConnectionToServer<AudioManagerClientEndpoint, AudioManagerServerEndpoint>
, public AudioManagerClientEndpoint {
IPC_CLIENT_CONNECTION(ConnectionToManagerServer, "/tmp/session/%sid/portal/audiomanager"sv)
public:
virtual ~ConnectionToManagerServer() override;
virtual void die() override;
virtual void main_mix_volume_changed(double volume) override;
virtual void main_mix_muted_state_changed(bool muted) override;
virtual void device_sample_rate_changed(u32 sample_rate) override;
Function<void(bool muted)> on_main_mix_muted_state_change;
Function<void(double volume)> on_main_mix_volume_change;
Function<void(u32 sample_rate)> on_device_sample_rate_change;
private:
ConnectionToManagerServer(NonnullOwnPtr<Core::LocalSocket>);
};
}

View file

@ -142,18 +142,6 @@ size_t ConnectionToServer::remaining_buffers() const
return m_buffer->size() - m_buffer->weak_remaining_capacity();
}
void ConnectionToServer::main_mix_muted_state_changed(bool muted)
{
if (on_main_mix_muted_state_change)
on_main_mix_muted_state_change(muted);
}
void ConnectionToServer::main_mix_volume_changed(double volume)
{
if (on_main_mix_volume_change)
on_main_mix_volume_change(volume);
}
void ConnectionToServer::client_volume_changed(double volume)
{
if (on_client_volume_change)

View file

@ -57,15 +57,11 @@ public:
virtual void die() override;
Function<void(bool muted)> on_main_mix_muted_state_change;
Function<void(double volume)> on_main_mix_volume_change;
Function<void(double volume)> on_client_volume_change;
private:
ConnectionToServer(NonnullOwnPtr<Core::LocalSocket>);
virtual void main_mix_muted_state_changed(bool) override;
virtual void main_mix_volume_changed(double) override;
virtual void client_volume_changed(double) override;
// We use this to perform the audio enqueuing on the background thread's event loop

View file

@ -2,7 +2,5 @@
endpoint AudioClient
{
main_mix_muted_state_changed(bool muted) =|
main_mix_volume_changed(double volume) =|
client_volume_changed(double volume) =|
}

View file

@ -0,0 +1,6 @@
endpoint AudioManagerClient
{
main_mix_muted_state_changed(bool muted) =|
main_mix_volume_changed(double volume) =|
device_sample_rate_changed(u32 sample_rate) =|
}

View file

@ -0,0 +1,11 @@
endpoint AudioManagerServer
{
set_main_mix_muted(bool muted) => ()
is_main_mix_muted() => (bool muted)
get_main_mix_volume() => (double volume)
set_main_mix_volume(double volume) => ()
// Audio device
set_device_sample_rate(u32 sample_rate) => ()
get_device_sample_rate() => (u32 sample_rate)
}

View file

@ -3,18 +3,12 @@
endpoint AudioServer
{
// Mixer functions
set_main_mix_muted(bool muted) => ()
is_main_mix_muted() => (bool muted)
set_self_muted(bool muted) => ()
is_self_muted() => (bool muted)
get_main_mix_volume() => (double volume)
set_main_mix_volume(double volume) => ()
get_self_volume() => (double volume)
set_self_volume(double volume) => ()
// Audio device
set_sample_rate(u32 sample_rate) => ()
// FIXME: Decouple client sample rate from device sample rate, then remove this API
get_sample_rate() => (u32 sample_rate)
// Buffer playback

View file

@ -6,9 +6,12 @@ serenity_component(
compile_ipc(AudioServer.ipc AudioServerEndpoint.h)
compile_ipc(AudioClient.ipc AudioClientEndpoint.h)
compile_ipc(AudioManagerClient.ipc AudioManagerClientEndpoint.h)
compile_ipc(AudioManagerServer.ipc AudioManagerServerEndpoint.h)
set(SOURCES
ConnectionFromClient.cpp
ConnectionFromManagerClient.cpp
Mixer.cpp
main.cpp
)
@ -16,6 +19,8 @@ set(SOURCES
set(GENERATED_SOURCES
AudioServerEndpoint.h
AudioClientEndpoint.h
AudioManagerClientEndpoint.h
AudioManagerServerEndpoint.h
)
serenity_bin(AudioServer)

View file

@ -47,41 +47,16 @@ void ConnectionFromClient::set_buffer(Audio::AudioQueue const& buffer)
m_queue->set_buffer(make<Audio::AudioQueue>(move(const_cast<Audio::AudioQueue&>(buffer))));
}
void ConnectionFromClient::did_change_main_mix_muted_state(Badge<Mixer>, bool muted)
{
async_main_mix_muted_state_changed(muted);
}
void ConnectionFromClient::did_change_main_mix_volume(Badge<Mixer>, double volume)
{
async_main_mix_volume_changed(volume);
}
void ConnectionFromClient::did_change_client_volume(Badge<ClientAudioStream>, double volume)
{
async_client_volume_changed(volume);
}
Messages::AudioServer::GetMainMixVolumeResponse ConnectionFromClient::get_main_mix_volume()
{
return m_mixer.main_volume();
}
void ConnectionFromClient::set_main_mix_volume(double volume)
{
m_mixer.set_main_volume(volume);
}
Messages::AudioServer::GetSampleRateResponse ConnectionFromClient::get_sample_rate()
{
return { m_mixer.audiodevice_get_sample_rate() };
}
void ConnectionFromClient::set_sample_rate(u32 sample_rate)
{
m_mixer.audiodevice_set_sample_rate(sample_rate);
}
Messages::AudioServer::GetSelfVolumeResponse ConnectionFromClient::get_self_volume()
{
return m_queue->volume().target();
@ -111,16 +86,6 @@ void ConnectionFromClient::clear_buffer()
m_queue->clear();
}
Messages::AudioServer::IsMainMixMutedResponse ConnectionFromClient::is_main_mix_muted()
{
return m_mixer.is_muted();
}
void ConnectionFromClient::set_main_mix_muted(bool muted)
{
m_mixer.set_muted(muted);
}
Messages::AudioServer::IsSelfMutedResponse ConnectionFromClient::is_self_muted()
{
if (m_queue)

View file

@ -24,8 +24,6 @@ public:
~ConnectionFromClient() override = default;
void did_change_client_volume(Badge<ClientAudioStream>, double volume);
void did_change_main_mix_muted_state(Badge<Mixer>, bool muted);
void did_change_main_mix_volume(Badge<Mixer>, double volume);
virtual void die() override;
@ -34,19 +32,15 @@ public:
private:
explicit ConnectionFromClient(NonnullOwnPtr<Core::LocalSocket>, int client_id, Mixer& mixer);
virtual Messages::AudioServer::GetMainMixVolumeResponse get_main_mix_volume() override;
virtual void set_main_mix_volume(double) override;
virtual Messages::AudioServer::GetSelfVolumeResponse get_self_volume() override;
virtual void set_self_volume(double) override;
virtual void set_buffer(Audio::AudioQueue const&) override;
virtual void clear_buffer() override;
virtual void start_playback() override;
virtual void pause_playback() override;
virtual Messages::AudioServer::IsMainMixMutedResponse is_main_mix_muted() override;
virtual void set_main_mix_muted(bool) override;
virtual Messages::AudioServer::IsSelfMutedResponse is_self_muted() override;
virtual void set_self_muted(bool) override;
virtual void set_sample_rate(u32 sample_rate) override;
// FIXME: Decouple client sample rate from device sample rate, then remove this endpoint
virtual Messages::AudioServer::GetSampleRateResponse get_sample_rate() override;
Mixer& m_mixer;

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ConnectionFromManagerClient.h"
namespace AudioServer {
static HashMap<int, RefPtr<ConnectionFromManagerClient>> s_connections;
ConnectionFromManagerClient::ConnectionFromManagerClient(NonnullOwnPtr<Core::LocalSocket> client_socket, int client_id, Mixer& mixer)
: IPC::ConnectionFromClient<AudioManagerClientEndpoint, AudioManagerServerEndpoint>(*this, move(client_socket), client_id)
, m_mixer(mixer)
{
s_connections.set(client_id, *this);
}
void ConnectionFromManagerClient::die()
{
s_connections.remove(client_id());
}
void ConnectionFromManagerClient::for_each(Function<void(ConnectionFromManagerClient&)> callback)
{
Vector<NonnullRefPtr<ConnectionFromManagerClient>> connections;
for (auto& it : s_connections)
connections.append(*it.value);
for (auto& connection : connections)
callback(connection);
}
void ConnectionFromManagerClient::did_change_main_mix_muted_state(Badge<Mixer>, bool muted)
{
async_main_mix_muted_state_changed(muted);
}
void ConnectionFromManagerClient::did_change_main_mix_volume(Badge<Mixer>, double volume)
{
async_main_mix_volume_changed(volume);
}
Messages::AudioManagerServer::GetMainMixVolumeResponse ConnectionFromManagerClient::get_main_mix_volume()
{
return m_mixer.main_volume();
}
void ConnectionFromManagerClient::set_main_mix_volume(double volume)
{
m_mixer.set_main_volume(volume);
}
Messages::AudioManagerServer::GetDeviceSampleRateResponse ConnectionFromManagerClient::get_device_sample_rate()
{
return { m_mixer.audiodevice_get_sample_rate() };
}
void ConnectionFromManagerClient::set_device_sample_rate(u32 sample_rate)
{
m_mixer.audiodevice_set_sample_rate(sample_rate);
}
Messages::AudioManagerServer::IsMainMixMutedResponse ConnectionFromManagerClient::is_main_mix_muted()
{
return m_mixer.is_muted();
}
void ConnectionFromManagerClient::set_main_mix_muted(bool muted)
{
m_mixer.set_muted(muted);
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <AudioServer/AudioManagerClientEndpoint.h>
#include <AudioServer/AudioManagerServerEndpoint.h>
#include <AudioServer/Mixer.h>
#include <LibIPC/ConnectionFromClient.h>
namespace AudioServer {
class ConnectionFromManagerClient final : public IPC::ConnectionFromClient<AudioManagerClientEndpoint, AudioManagerServerEndpoint> {
C_OBJECT(ConnectionFromManagerClient)
public:
~ConnectionFromManagerClient() override = default;
virtual void die() override;
static void for_each(Function<void(ConnectionFromManagerClient&)>);
void did_change_main_mix_muted_state(Badge<Mixer>, bool muted);
void did_change_main_mix_volume(Badge<Mixer>, double volume);
private:
ConnectionFromManagerClient(NonnullOwnPtr<Core::LocalSocket> client_socket, int client_id, Mixer& mixer);
virtual Messages::AudioManagerServer::GetMainMixVolumeResponse get_main_mix_volume() override;
virtual void set_main_mix_volume(double) override;
virtual Messages::AudioManagerServer::IsMainMixMutedResponse is_main_mix_muted() override;
virtual void set_main_mix_muted(bool) override;
virtual void set_device_sample_rate(u32 sample_rate) override;
virtual Messages::AudioManagerServer::GetDeviceSampleRateResponse get_device_sample_rate() override;
Mixer& m_mixer;
};
}

View file

@ -11,6 +11,7 @@
#include <AK/MemoryStream.h>
#include <AK/NumericLimits.h>
#include <AudioServer/ConnectionFromClient.h>
#include <AudioServer/ConnectionFromManagerClient.h>
#include <AudioServer/Mixer.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/Timer.h>
@ -127,7 +128,7 @@ void Mixer::set_main_volume(double volume)
m_config->write_num_entry("Master", "Volume", static_cast<int>(volume * 100));
request_setting_sync();
ConnectionFromClient::for_each([&](ConnectionFromClient& client) {
ConnectionFromManagerClient::for_each([&](auto& client) {
client.did_change_main_mix_volume({}, main_volume());
});
}
@ -141,7 +142,7 @@ void Mixer::set_muted(bool muted)
m_config->write_bool_entry("Master", "Mute", m_muted);
request_setting_sync();
ConnectionFromClient::for_each([muted](ConnectionFromClient& client) {
ConnectionFromManagerClient::for_each([muted](auto& client) {
client.did_change_main_mix_muted_state({}, muted);
});
}

View file

@ -44,6 +44,10 @@ public:
bool get_next_sample(Audio::Sample& sample)
{
// Note: Even though we only check client state here, we will probably close the client much earlier.
if (!is_connected())
return false;
if (m_paused)
return false;
@ -52,11 +56,6 @@ public:
if (result.is_error()) {
if (result.error() == Audio::AudioQueue::QueueStatus::Empty) {
dbgln_if(AUDIO_DEBUG, "Audio client {} can't keep up!", m_client->client_id());
// Note: Even though we only check client state here, we will probably close the client much earlier.
if (!m_client->is_open()) {
dbgln("Client socket {} has closed, closing audio server connection.", m_client->client_id());
m_client->shutdown();
}
}
return false;

View file

@ -5,6 +5,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ConnectionFromClient.h"
#include "ConnectionFromManagerClient.h"
#include "Mixer.h"
#include <LibCore/ConfigFile.h>
#include <LibCore/LocalServer.h>
@ -23,7 +25,7 @@ ErrorOr<int> serenity_main(Main::Arguments)
Core::EventLoop event_loop;
auto mixer = TRY(AudioServer::Mixer::try_create(config));
auto server = TRY(Core::LocalServer::try_create());
TRY(server->take_over_from_system_server());
TRY(server->take_over_from_system_server("/tmp/session/%sid/portal/audio"));
server->on_accept = [&](NonnullOwnPtr<Core::LocalSocket> client_socket) {
static int s_next_client_id = 0;
@ -31,8 +33,16 @@ ErrorOr<int> serenity_main(Main::Arguments)
(void)IPC::new_client_connection<AudioServer::ConnectionFromClient>(move(client_socket), client_id, *mixer);
};
auto manager_server = TRY(Core::LocalServer::try_create());
TRY(manager_server->take_over_from_system_server("/tmp/session/%sid/portal/audiomanager"));
manager_server->on_accept = [&](NonnullOwnPtr<Core::LocalSocket> client_socket) {
static int s_next_client_id = 0;
int client_id = ++s_next_client_id;
(void)IPC::new_client_connection<AudioServer::ConnectionFromManagerClient>(move(client_socket), client_id, *mixer);
};
TRY(Core::System::pledge("stdio recvfd thread accept cpath rpath wpath"));
TRY(Core::System::unveil(nullptr, nullptr));
return event_loop.exec();
}

View file

@ -8,7 +8,7 @@
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibAudio/ConnectionToServer.h>
#include <LibAudio/ConnectionToManagerServer.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/System.h>
@ -27,8 +27,7 @@ enum AudioVariable : u32 {
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Core::EventLoop loop;
auto audio_client = TRY(Audio::ConnectionToServer::try_create());
audio_client->async_pause_playback();
auto audio_client = TRY(Audio::ConnectionToManagerServer::try_create());
StringView command;
Vector<StringView> command_arguments;
@ -85,7 +84,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
break;
}
case AudioVariable::SampleRate: {
u32 sample_rate = audio_client->get_sample_rate();
u32 sample_rate = audio_client->get_device_sample_rate();
if (human_mode)
outln("Sample rate: {:5d} Hz", sample_rate);
else
@ -155,7 +154,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
}
case AudioVariable::SampleRate: {
int& sample_rate = to_set.value.get<int>();
audio_client->set_sample_rate(sample_rate);
audio_client->set_device_sample_rate(sample_rate);
break;
}
}