From 15f4043a7a80f52c0fa05c4e69771e758464cd20 Mon Sep 17 00:00:00 2001 From: sppmacd Date: Fri, 12 Jun 2020 17:00:03 +0200 Subject: AudioServer: removed AS prefix from class and file names And moved everything to AudioServer namespace --- Services/AudioServer/ASClientConnection.cpp | 161 --------------------------- Services/AudioServer/ASClientConnection.h | 69 ------------ Services/AudioServer/ASMixer.cpp | 152 -------------------------- Services/AudioServer/ASMixer.h | 139 ----------------------- Services/AudioServer/CMakeLists.txt | 4 +- Services/AudioServer/ClientConnection.cpp | 164 ++++++++++++++++++++++++++++ Services/AudioServer/ClientConnection.h | 73 +++++++++++++ Services/AudioServer/Mixer.cpp | 155 ++++++++++++++++++++++++++ Services/AudioServer/Mixer.h | 142 ++++++++++++++++++++++++ Services/AudioServer/main.cpp | 6 +- 10 files changed, 539 insertions(+), 526 deletions(-) delete mode 100644 Services/AudioServer/ASClientConnection.cpp delete mode 100644 Services/AudioServer/ASClientConnection.h delete mode 100644 Services/AudioServer/ASMixer.cpp delete mode 100644 Services/AudioServer/ASMixer.h create mode 100644 Services/AudioServer/ClientConnection.cpp create mode 100644 Services/AudioServer/ClientConnection.h create mode 100644 Services/AudioServer/Mixer.cpp create mode 100644 Services/AudioServer/Mixer.h diff --git a/Services/AudioServer/ASClientConnection.cpp b/Services/AudioServer/ASClientConnection.cpp deleted file mode 100644 index c941ffcf2a..0000000000 --- a/Services/AudioServer/ASClientConnection.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "ASClientConnection.h" -#include "ASMixer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static HashMap> s_connections; - -void ASClientConnection::for_each(Function callback) -{ - NonnullRefPtrVector connections; - for (auto& it : s_connections) - connections.append(*it.value); - for (auto& connection : connections) - callback(connection); -} - -ASClientConnection::ASClientConnection(Core::LocalSocket& client_socket, int client_id, ASMixer& mixer) - : IPC::ClientConnection(*this, client_socket, client_id) - , m_mixer(mixer) -{ - s_connections.set(client_id, *this); -} - -ASClientConnection::~ASClientConnection() -{ -} - -void ASClientConnection::die() -{ - s_connections.remove(client_id()); -} - -void ASClientConnection::did_finish_playing_buffer(Badge, int buffer_id) -{ - post_message(Messages::AudioClient::FinishedPlayingBuffer(buffer_id)); -} - -void ASClientConnection::did_change_muted_state(Badge, bool muted) -{ - post_message(Messages::AudioClient::MutedStateChanged(muted)); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::Greet&) -{ - return make(client_id()); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::GetMainMixVolume&) -{ - return make(m_mixer.main_volume()); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::SetMainMixVolume& message) -{ - m_mixer.set_main_volume(message.volume()); - return make(); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::EnqueueBuffer& message) -{ - auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.buffer_id()); - if (!shared_buffer) { - // FIXME: The shared buffer should have been retrieved for us already. - // We don't want to do IPC error checking at this layer. - ASSERT_NOT_REACHED(); - } - - if (!m_queue) - m_queue = m_mixer.create_queue(*this); - - if (m_queue->is_full()) - return make(false); - - m_queue->enqueue(Audio::Buffer::create_with_shared_buffer(*shared_buffer, message.sample_count())); - return make(true); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::GetRemainingSamples&) -{ - int remaining = 0; - if (m_queue) - remaining = m_queue->get_remaining_samples(); - - return make(remaining); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::GetPlayedSamples&) -{ - int played = 0; - if (m_queue) - played = m_queue->get_played_samples(); - - return make(played); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::SetPaused& message) -{ - if (m_queue) - m_queue->set_paused(message.paused()); - return make(); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::ClearBuffer& message) -{ - if (m_queue) - m_queue->clear(message.paused()); - return make(); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::GetPlayingBuffer&) -{ - int id = -1; - if (m_queue) - id = m_queue->get_playing_buffer(); - return make(id); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::GetMuted&) -{ - return make(m_mixer.is_muted()); -} - -OwnPtr ASClientConnection::handle(const Messages::AudioServer::SetMuted& message) -{ - m_mixer.set_muted(message.muted()); - return make(); -} diff --git a/Services/AudioServer/ASClientConnection.h b/Services/AudioServer/ASClientConnection.h deleted file mode 100644 index c76c51eb36..0000000000 --- a/Services/AudioServer/ASClientConnection.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include -#include -#include - -namespace Audio { -class Buffer; -} - -class ASBufferQueue; -class ASMixer; - -class ASClientConnection final : public IPC::ClientConnection - , public AudioServerEndpoint { - C_OBJECT(ASClientConnection) -public: - explicit ASClientConnection(Core::LocalSocket&, int client_id, ASMixer& mixer); - ~ASClientConnection() override; - - void did_finish_playing_buffer(Badge, int buffer_id); - void did_change_muted_state(Badge, bool muted); - - virtual void die() override; - - static void for_each(Function); - -private: - virtual OwnPtr handle(const Messages::AudioServer::Greet&) override; - virtual OwnPtr handle(const Messages::AudioServer::GetMainMixVolume&) override; - virtual OwnPtr handle(const Messages::AudioServer::SetMainMixVolume&) override; - virtual OwnPtr handle(const Messages::AudioServer::EnqueueBuffer&) override; - virtual OwnPtr handle(const Messages::AudioServer::GetRemainingSamples&) override; - virtual OwnPtr handle(const Messages::AudioServer::GetPlayedSamples&) override; - virtual OwnPtr handle(const Messages::AudioServer::SetPaused&) override; - virtual OwnPtr handle(const Messages::AudioServer::ClearBuffer&) override; - virtual OwnPtr handle(const Messages::AudioServer::GetPlayingBuffer&) override; - virtual OwnPtr handle(const Messages::AudioServer::GetMuted&) override; - virtual OwnPtr handle(const Messages::AudioServer::SetMuted&) override; - - ASMixer& m_mixer; - RefPtr m_queue; -}; diff --git a/Services/AudioServer/ASMixer.cpp b/Services/AudioServer/ASMixer.cpp deleted file mode 100644 index 3d9ff00cc5..0000000000 --- a/Services/AudioServer/ASMixer.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -ASMixer::ASMixer() - : m_device(Core::File::construct("/dev/audio", this)) - , m_sound_thread( - [this] { - mix(); - return 0; - }, - "AudioServer[mixer]") -{ - if (!m_device->open(Core::IODevice::WriteOnly)) { - dbgprintf("Can't open audio device: %s\n", m_device->error_string()); - return; - } - - pthread_mutex_init(&m_pending_mutex, nullptr); - pthread_cond_init(&m_pending_cond, nullptr); - - m_zero_filled_buffer = (u8*)malloc(4096); - bzero(m_zero_filled_buffer, 4096); - m_sound_thread.start(); -} - -ASMixer::~ASMixer() -{ -} - -NonnullRefPtr ASMixer::create_queue(ASClientConnection& client) -{ - auto queue = adopt(*new ASBufferQueue(client)); - pthread_mutex_lock(&m_pending_mutex); - m_pending_mixing.append(*queue); - pthread_cond_signal(&m_pending_cond); - pthread_mutex_unlock(&m_pending_mutex); - return queue; -} - -void ASMixer::mix() -{ - decltype(m_pending_mixing) active_mix_queues; - - for (;;) { - if (active_mix_queues.is_empty()) { - pthread_mutex_lock(&m_pending_mutex); - pthread_cond_wait(&m_pending_cond, &m_pending_mutex); - active_mix_queues.append(move(m_pending_mixing)); - pthread_mutex_unlock(&m_pending_mutex); - } - - active_mix_queues.remove_all_matching([&](auto& entry) { return !entry->client(); }); - - Audio::Sample mixed_buffer[1024]; - auto mixed_buffer_length = (int)(sizeof(mixed_buffer) / sizeof(Audio::Sample)); - - // Mix the buffers together into the output - for (auto& queue : active_mix_queues) { - if (!queue->client()) { - queue->clear(); - continue; - } - - for (int i = 0; i < mixed_buffer_length; ++i) { - auto& mixed_sample = mixed_buffer[i]; - Audio::Sample sample; - if (!queue->get_next_sample(sample)) - break; - mixed_sample += sample; - } - } - - bool muted = m_muted; - - // output the mixed stuff to the device - u8 raw_buffer[4096]; - auto buffer = ByteBuffer::wrap(muted ? m_zero_filled_buffer : raw_buffer, sizeof(raw_buffer)); - - BufferStream stream(buffer); - if (!muted) { - for (int i = 0; i < mixed_buffer_length; ++i) { - auto& mixed_sample = mixed_buffer[i]; - - mixed_sample.scale(m_main_volume); - mixed_sample.clip(); - - i16 out_sample; - out_sample = mixed_sample.left * NumericLimits::max(); - stream << out_sample; - - ASSERT(!stream.at_end()); // we should have enough space for both channels in one buffer! - out_sample = mixed_sample.right * NumericLimits::max(); - stream << out_sample; - } - } - - if (stream.offset() != 0) { - buffer.trim(stream.offset()); - } - m_device->write(buffer); - } -} - -void ASMixer::set_muted(bool muted) -{ - if (m_muted == muted) - return; - m_muted = muted; - ASClientConnection::for_each([muted](ASClientConnection& client) { - client.did_change_muted_state({}, muted); - }); -} - -ASBufferQueue::ASBufferQueue(ASClientConnection& client) - : m_client(client.make_weak_ptr()) -{ -} - -void ASBufferQueue::enqueue(NonnullRefPtr&& buffer) -{ - m_remaining_samples += buffer->sample_count(); - m_queue.enqueue(move(buffer)); -} diff --git a/Services/AudioServer/ASMixer.h b/Services/AudioServer/ASMixer.h deleted file mode 100644 index e5ed5874b2..0000000000 --- a/Services/AudioServer/ASMixer.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "ASClientConnection.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class ASClientConnection; - -class ASBufferQueue : public RefCounted { -public: - explicit ASBufferQueue(ASClientConnection&); - ~ASBufferQueue() {} - - bool is_full() const { return m_queue.size() >= 3; } - void enqueue(NonnullRefPtr&&); - - bool get_next_sample(Audio::Sample& sample) - { - if (m_paused) - return false; - - while (!m_current && !m_queue.is_empty()) - m_current = m_queue.dequeue(); - - if (!m_current) - return false; - - sample = m_current->samples()[m_position++]; - --m_remaining_samples; - ++m_played_samples; - - if (m_position >= m_current->sample_count()) { - m_client->did_finish_playing_buffer({}, m_current->shbuf_id()); - m_current = nullptr; - m_position = 0; - } - return true; - } - - ASClientConnection* client() { return m_client.ptr(); } - - void clear(bool paused = false) - { - m_queue.clear(); - m_position = 0; - m_remaining_samples = 0; - m_played_samples = 0; - m_current = nullptr; - m_paused = paused; - } - - void set_paused(bool paused) - { - m_paused = paused; - } - - int get_remaining_samples() const { return m_remaining_samples; } - int get_played_samples() const { return m_played_samples; } - int get_playing_buffer() const - { - if (m_current) - return m_current->shbuf_id(); - return -1; - } - -private: - RefPtr m_current; - Queue> m_queue; - int m_position { 0 }; - int m_remaining_samples { 0 }; - int m_played_samples { 0 }; - bool m_paused { false }; - WeakPtr m_client; -}; - -class ASMixer : public Core::Object { - C_OBJECT(ASMixer) -public: - ASMixer(); - virtual ~ASMixer() override; - - NonnullRefPtr create_queue(ASClientConnection&); - - int main_volume() const { return m_main_volume; } - void set_main_volume(int volume) { m_main_volume = volume; } - - bool is_muted() const { return m_muted; } - void set_muted(bool); - -private: - Vector> m_pending_mixing; - pthread_mutex_t m_pending_mutex; - pthread_cond_t m_pending_cond; - - RefPtr m_device; - - LibThread::Thread m_sound_thread; - - bool m_muted { false }; - int m_main_volume { 100 }; - - u8* m_zero_filled_buffer { nullptr }; - - void mix(); -}; diff --git a/Services/AudioServer/CMakeLists.txt b/Services/AudioServer/CMakeLists.txt index 4ae91fb090..9c71197b62 100644 --- a/Services/AudioServer/CMakeLists.txt +++ b/Services/AudioServer/CMakeLists.txt @@ -2,8 +2,8 @@ compile_ipc(AudioServer.ipc AudioServerEndpoint.h) compile_ipc(AudioClient.ipc AudioClientEndpoint.h) set(SOURCES - ASClientConnection.cpp - ASMixer.cpp + ClientConnection.cpp + Mixer.cpp main.cpp AudioServerEndpoint.h AudioClientEndpoint.h diff --git a/Services/AudioServer/ClientConnection.cpp b/Services/AudioServer/ClientConnection.cpp new file mode 100644 index 0000000000..a7d5a3d540 --- /dev/null +++ b/Services/AudioServer/ClientConnection.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ClientConnection.h" +#include "Mixer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AudioServer { + +static HashMap> s_connections; + +void ClientConnection::for_each(Function callback) +{ + NonnullRefPtrVector connections; + for (auto& it : s_connections) + connections.append(*it.value); + for (auto& connection : connections) + callback(connection); +} + +ClientConnection::ClientConnection(Core::LocalSocket& client_socket, int client_id, Mixer& mixer) + : IPC::ClientConnection(*this, client_socket, client_id) + , m_mixer(mixer) +{ + s_connections.set(client_id, *this); +} + +ClientConnection::~ClientConnection() +{ +} + +void ClientConnection::die() +{ + s_connections.remove(client_id()); +} + +void ClientConnection::did_finish_playing_buffer(Badge, int buffer_id) +{ + post_message(Messages::AudioClient::FinishedPlayingBuffer(buffer_id)); +} + +void ClientConnection::did_change_muted_state(Badge, bool muted) +{ + post_message(Messages::AudioClient::MutedStateChanged(muted)); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::Greet&) +{ + return make(client_id()); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::GetMainMixVolume&) +{ + return make(m_mixer.main_volume()); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::SetMainMixVolume& message) +{ + m_mixer.set_main_volume(message.volume()); + return make(); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::EnqueueBuffer& message) +{ + auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.buffer_id()); + if (!shared_buffer) { + // FIXME: The shared buffer should have been retrieved for us already. + // We don't want to do IPC error checking at this layer. + ASSERT_NOT_REACHED(); + } + + if (!m_queue) + m_queue = m_mixer.create_queue(*this); + + if (m_queue->is_full()) + return make(false); + + m_queue->enqueue(Audio::Buffer::create_with_shared_buffer(*shared_buffer, message.sample_count())); + return make(true); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::GetRemainingSamples&) +{ + int remaining = 0; + if (m_queue) + remaining = m_queue->get_remaining_samples(); + + return make(remaining); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::GetPlayedSamples&) +{ + int played = 0; + if (m_queue) + played = m_queue->get_played_samples(); + + return make(played); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::SetPaused& message) +{ + if (m_queue) + m_queue->set_paused(message.paused()); + return make(); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::ClearBuffer& message) +{ + if (m_queue) + m_queue->clear(message.paused()); + return make(); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::GetPlayingBuffer&) +{ + int id = -1; + if (m_queue) + id = m_queue->get_playing_buffer(); + return make(id); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::GetMuted&) +{ + return make(m_mixer.is_muted()); +} + +OwnPtr ClientConnection::handle(const Messages::AudioServer::SetMuted& message) +{ + m_mixer.set_muted(message.muted()); + return make(); +} +} diff --git a/Services/AudioServer/ClientConnection.h b/Services/AudioServer/ClientConnection.h new file mode 100644 index 0000000000..2befe48ab8 --- /dev/null +++ b/Services/AudioServer/ClientConnection.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +namespace Audio { +class Buffer; +} + +namespace AudioServer { + +class BufferQueue; +class Mixer; + +class ClientConnection final : public IPC::ClientConnection + , public AudioServerEndpoint { + C_OBJECT(ClientConnection) +public: + explicit ClientConnection(Core::LocalSocket&, int client_id, Mixer& mixer); + ~ClientConnection() override; + + void did_finish_playing_buffer(Badge, int buffer_id); + void did_change_muted_state(Badge, bool muted); + + virtual void die() override; + + static void for_each(Function); + +private: + virtual OwnPtr handle(const Messages::AudioServer::Greet&) override; + virtual OwnPtr handle(const Messages::AudioServer::GetMainMixVolume&) override; + virtual OwnPtr handle(const Messages::AudioServer::SetMainMixVolume&) override; + virtual OwnPtr handle(const Messages::AudioServer::EnqueueBuffer&) override; + virtual OwnPtr handle(const Messages::AudioServer::GetRemainingSamples&) override; + virtual OwnPtr handle(const Messages::AudioServer::GetPlayedSamples&) override; + virtual OwnPtr handle(const Messages::AudioServer::SetPaused&) override; + virtual OwnPtr handle(const Messages::AudioServer::ClearBuffer&) override; + virtual OwnPtr handle(const Messages::AudioServer::GetPlayingBuffer&) override; + virtual OwnPtr handle(const Messages::AudioServer::GetMuted&) override; + virtual OwnPtr handle(const Messages::AudioServer::SetMuted&) override; + + Mixer& m_mixer; + RefPtr m_queue; +}; + +} diff --git a/Services/AudioServer/Mixer.cpp b/Services/AudioServer/Mixer.cpp new file mode 100644 index 0000000000..20daa529cf --- /dev/null +++ b/Services/AudioServer/Mixer.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +namespace AudioServer { + +Mixer::Mixer() + : m_device(Core::File::construct("/dev/audio", this)) + , m_sound_thread( + [this] { + mix(); + return 0; + }, + "AudioServer[mixer]") +{ + if (!m_device->open(Core::IODevice::WriteOnly)) { + dbgprintf("Can't open audio device: %s\n", m_device->error_string()); + return; + } + + pthread_mutex_init(&m_pending_mutex, nullptr); + pthread_cond_init(&m_pending_cond, nullptr); + + m_zero_filled_buffer = (u8*)malloc(4096); + bzero(m_zero_filled_buffer, 4096); + m_sound_thread.start(); +} + +Mixer::~Mixer() +{ +} + +NonnullRefPtr Mixer::create_queue(ClientConnection& client) +{ + auto queue = adopt(*new BufferQueue(client)); + pthread_mutex_lock(&m_pending_mutex); + m_pending_mixing.append(*queue); + pthread_cond_signal(&m_pending_cond); + pthread_mutex_unlock(&m_pending_mutex); + return queue; +} + +void Mixer::mix() +{ + decltype(m_pending_mixing) active_mix_queues; + + for (;;) { + if (active_mix_queues.is_empty()) { + pthread_mutex_lock(&m_pending_mutex); + pthread_cond_wait(&m_pending_cond, &m_pending_mutex); + active_mix_queues.append(move(m_pending_mixing)); + pthread_mutex_unlock(&m_pending_mutex); + } + + active_mix_queues.remove_all_matching([&](auto& entry) { return !entry->client(); }); + + Audio::Sample mixed_buffer[1024]; + auto mixed_buffer_length = (int)(sizeof(mixed_buffer) / sizeof(Audio::Sample)); + + // Mix the buffers together into the output + for (auto& queue : active_mix_queues) { + if (!queue->client()) { + queue->clear(); + continue; + } + + for (int i = 0; i < mixed_buffer_length; ++i) { + auto& mixed_sample = mixed_buffer[i]; + Audio::Sample sample; + if (!queue->get_next_sample(sample)) + break; + mixed_sample += sample; + } + } + + bool muted = m_muted; + + // output the mixed stuff to the device + u8 raw_buffer[4096]; + auto buffer = ByteBuffer::wrap(muted ? m_zero_filled_buffer : raw_buffer, sizeof(raw_buffer)); + + BufferStream stream(buffer); + if (!muted) { + for (int i = 0; i < mixed_buffer_length; ++i) { + auto& mixed_sample = mixed_buffer[i]; + + mixed_sample.scale(m_main_volume); + mixed_sample.clip(); + + i16 out_sample; + out_sample = mixed_sample.left * NumericLimits::max(); + stream << out_sample; + + ASSERT(!stream.at_end()); // we should have enough space for both channels in one buffer! + out_sample = mixed_sample.right * NumericLimits::max(); + stream << out_sample; + } + } + + if (stream.offset() != 0) { + buffer.trim(stream.offset()); + } + m_device->write(buffer); + } +} + +void Mixer::set_muted(bool muted) +{ + if (m_muted == muted) + return; + m_muted = muted; + ClientConnection::for_each([muted](ClientConnection& client) { + client.did_change_muted_state({}, muted); + }); +} + +BufferQueue::BufferQueue(ClientConnection& client) + : m_client(client.make_weak_ptr()) +{ +} + +void BufferQueue::enqueue(NonnullRefPtr&& buffer) +{ + m_remaining_samples += buffer->sample_count(); + m_queue.enqueue(move(buffer)); +} +} diff --git a/Services/AudioServer/Mixer.h b/Services/AudioServer/Mixer.h new file mode 100644 index 0000000000..bdfc4c8928 --- /dev/null +++ b/Services/AudioServer/Mixer.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ClientConnection.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AudioServer { + +class ClientConnection; + +class BufferQueue : public RefCounted { +public: + explicit BufferQueue(ClientConnection&); + ~BufferQueue() {} + + bool is_full() const { return m_queue.size() >= 3; } + void enqueue(NonnullRefPtr&&); + + bool get_next_sample(Audio::Sample& sample) + { + if (m_paused) + return false; + + while (!m_current && !m_queue.is_empty()) + m_current = m_queue.dequeue(); + + if (!m_current) + return false; + + sample = m_current->samples()[m_position++]; + --m_remaining_samples; + ++m_played_samples; + + if (m_position >= m_current->sample_count()) { + m_client->did_finish_playing_buffer({}, m_current->shbuf_id()); + m_current = nullptr; + m_position = 0; + } + return true; + } + + ClientConnection* client() { return m_client.ptr(); } + + void clear(bool paused = false) + { + m_queue.clear(); + m_position = 0; + m_remaining_samples = 0; + m_played_samples = 0; + m_current = nullptr; + m_paused = paused; + } + + void set_paused(bool paused) + { + m_paused = paused; + } + + int get_remaining_samples() const { return m_remaining_samples; } + int get_played_samples() const { return m_played_samples; } + int get_playing_buffer() const + { + if (m_current) + return m_current->shbuf_id(); + return -1; + } + +private: + RefPtr m_current; + Queue> m_queue; + int m_position{ 0 }; + int m_remaining_samples{ 0 }; + int m_played_samples{ 0 }; + bool m_paused{ false }; + WeakPtr m_client; +}; + +class Mixer : public Core::Object { + C_OBJECT(Mixer) +public: + Mixer(); + virtual ~Mixer() override; + + NonnullRefPtr create_queue(ClientConnection&); + + int main_volume() const { return m_main_volume; } + void set_main_volume(int volume) { m_main_volume = volume; } + + bool is_muted() const { return m_muted; } + void set_muted(bool); + +private: + Vector> m_pending_mixing; + pthread_mutex_t m_pending_mutex; + pthread_cond_t m_pending_cond; + + RefPtr m_device; + + LibThread::Thread m_sound_thread; + + bool m_muted{ false }; + int m_main_volume{ 100 }; + + u8* m_zero_filled_buffer{ nullptr }; + + void mix(); +}; +} diff --git a/Services/AudioServer/main.cpp b/Services/AudioServer/main.cpp index 5907dd77c6..6f960396cc 100644 --- a/Services/AudioServer/main.cpp +++ b/Services/AudioServer/main.cpp @@ -24,7 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "ASMixer.h" +#include "Mixer.h" #include #include @@ -36,7 +36,7 @@ int main(int, char**) } Core::EventLoop event_loop; - ASMixer mixer; + AudioServer::Mixer mixer; auto server = Core::LocalServer::construct(); bool ok = server->take_over_from_system_server(); @@ -49,7 +49,7 @@ int main(int, char**) } static int s_next_client_id = 0; int client_id = ++s_next_client_id; - IPC::new_client_connection(*client_socket, client_id, mixer); + IPC::new_client_connection(*client_socket, client_id, mixer); }; if (pledge("stdio thread shared_buffer accept", nullptr) < 0) { -- cgit v1.2.3