mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 22:07:36 +00:00
Services: Move to Userland/Services/
This commit is contained in:
parent
4055b03291
commit
c7ac7e6eaf
170 changed files with 4 additions and 4 deletions
6
Userland/Services/AudioServer/AudioClient.ipc
Normal file
6
Userland/Services/AudioServer/AudioClient.ipc
Normal file
|
@ -0,0 +1,6 @@
|
|||
endpoint AudioClient = 82
|
||||
{
|
||||
FinishedPlayingBuffer(i32 buffer_id) =|
|
||||
MutedStateChanged(bool muted) =|
|
||||
MainMixVolumeChanged(i32 volume) =|
|
||||
}
|
21
Userland/Services/AudioServer/AudioServer.ipc
Normal file
21
Userland/Services/AudioServer/AudioServer.ipc
Normal file
|
@ -0,0 +1,21 @@
|
|||
endpoint AudioServer = 85
|
||||
{
|
||||
// Basic protocol
|
||||
Greet() => (i32 client_id)
|
||||
|
||||
// Mixer functions
|
||||
SetMuted(bool muted) => ()
|
||||
GetMuted() => (bool muted)
|
||||
GetMainMixVolume() => (i32 volume)
|
||||
SetMainMixVolume(i32 volume) => ()
|
||||
|
||||
// Buffer playback
|
||||
EnqueueBuffer(i32 buffer_id, int sample_count) => (bool success)
|
||||
SetPaused(bool paused) => ()
|
||||
ClearBuffer(bool paused) => ()
|
||||
|
||||
//Buffer information
|
||||
GetRemainingSamples() => (int remaining_samples)
|
||||
GetPlayedSamples() => (int played_samples)
|
||||
GetPlayingBuffer() => (i32 buffer_id)
|
||||
}
|
13
Userland/Services/AudioServer/CMakeLists.txt
Normal file
13
Userland/Services/AudioServer/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
compile_ipc(AudioServer.ipc AudioServerEndpoint.h)
|
||||
compile_ipc(AudioClient.ipc AudioClientEndpoint.h)
|
||||
|
||||
set(SOURCES
|
||||
ClientConnection.cpp
|
||||
Mixer.cpp
|
||||
main.cpp
|
||||
AudioServerEndpoint.h
|
||||
AudioClientEndpoint.h
|
||||
)
|
||||
|
||||
serenity_bin(AudioServer)
|
||||
target_link_libraries(AudioServer LibCore LibThread LibIPC)
|
168
Userland/Services/AudioServer/ClientConnection.cpp
Normal file
168
Userland/Services/AudioServer/ClientConnection.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* 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 <AK/SharedBuffer.h>
|
||||
#include <AudioServer/AudioClientEndpoint.h>
|
||||
#include <LibAudio/Buffer.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace AudioServer {
|
||||
|
||||
static HashMap<int, RefPtr<ClientConnection>> s_connections;
|
||||
|
||||
void ClientConnection::for_each(Function<void(ClientConnection&)> callback)
|
||||
{
|
||||
NonnullRefPtrVector<ClientConnection> connections;
|
||||
for (auto& it : s_connections)
|
||||
connections.append(*it.value);
|
||||
for (auto& connection : connections)
|
||||
callback(connection);
|
||||
}
|
||||
|
||||
ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socket, int client_id, Mixer& mixer)
|
||||
: IPC::ClientConnection<AudioClientEndpoint, AudioServerEndpoint>(*this, move(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<BufferQueue>, int buffer_id)
|
||||
{
|
||||
post_message(Messages::AudioClient::FinishedPlayingBuffer(buffer_id));
|
||||
}
|
||||
|
||||
void ClientConnection::did_change_muted_state(Badge<Mixer>, bool muted)
|
||||
{
|
||||
post_message(Messages::AudioClient::MutedStateChanged(muted));
|
||||
}
|
||||
|
||||
void ClientConnection::did_change_main_mix_volume(Badge<Mixer>, int volume)
|
||||
{
|
||||
post_message(Messages::AudioClient::MainMixVolumeChanged(volume));
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::GreetResponse> ClientConnection::handle(const Messages::AudioServer::Greet&)
|
||||
{
|
||||
return make<Messages::AudioServer::GreetResponse>(client_id());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::GetMainMixVolumeResponse> ClientConnection::handle(const Messages::AudioServer::GetMainMixVolume&)
|
||||
{
|
||||
return make<Messages::AudioServer::GetMainMixVolumeResponse>(m_mixer.main_volume());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::SetMainMixVolumeResponse> ClientConnection::handle(const Messages::AudioServer::SetMainMixVolume& message)
|
||||
{
|
||||
m_mixer.set_main_volume(message.volume());
|
||||
return make<Messages::AudioServer::SetMainMixVolumeResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::EnqueueBufferResponse> 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<Messages::AudioServer::EnqueueBufferResponse>(false);
|
||||
|
||||
m_queue->enqueue(Audio::Buffer::create_with_shared_buffer(*shared_buffer, message.sample_count()));
|
||||
return make<Messages::AudioServer::EnqueueBufferResponse>(true);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::GetRemainingSamplesResponse> ClientConnection::handle(const Messages::AudioServer::GetRemainingSamples&)
|
||||
{
|
||||
int remaining = 0;
|
||||
if (m_queue)
|
||||
remaining = m_queue->get_remaining_samples();
|
||||
|
||||
return make<Messages::AudioServer::GetRemainingSamplesResponse>(remaining);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::GetPlayedSamplesResponse> ClientConnection::handle(const Messages::AudioServer::GetPlayedSamples&)
|
||||
{
|
||||
int played = 0;
|
||||
if (m_queue)
|
||||
played = m_queue->get_played_samples();
|
||||
|
||||
return make<Messages::AudioServer::GetPlayedSamplesResponse>(played);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::SetPausedResponse> ClientConnection::handle(const Messages::AudioServer::SetPaused& message)
|
||||
{
|
||||
if (m_queue)
|
||||
m_queue->set_paused(message.paused());
|
||||
return make<Messages::AudioServer::SetPausedResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::ClearBufferResponse> ClientConnection::handle(const Messages::AudioServer::ClearBuffer& message)
|
||||
{
|
||||
if (m_queue)
|
||||
m_queue->clear(message.paused());
|
||||
return make<Messages::AudioServer::ClearBufferResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::GetPlayingBufferResponse> ClientConnection::handle(const Messages::AudioServer::GetPlayingBuffer&)
|
||||
{
|
||||
int id = -1;
|
||||
if (m_queue)
|
||||
id = m_queue->get_playing_buffer();
|
||||
return make<Messages::AudioServer::GetPlayingBufferResponse>(id);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::GetMutedResponse> ClientConnection::handle(const Messages::AudioServer::GetMuted&)
|
||||
{
|
||||
return make<Messages::AudioServer::GetMutedResponse>(m_mixer.is_muted());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::AudioServer::SetMutedResponse> ClientConnection::handle(const Messages::AudioServer::SetMuted& message)
|
||||
{
|
||||
m_mixer.set_muted(message.muted());
|
||||
return make<Messages::AudioServer::SetMutedResponse>();
|
||||
}
|
||||
}
|
75
Userland/Services/AudioServer/ClientConnection.h
Normal file
75
Userland/Services/AudioServer/ClientConnection.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* 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 <AK/HashMap.h>
|
||||
#include <AudioServer/AudioClientEndpoint.h>
|
||||
#include <AudioServer/AudioServerEndpoint.h>
|
||||
#include <LibIPC/ClientConnection.h>
|
||||
|
||||
namespace Audio {
|
||||
class Buffer;
|
||||
}
|
||||
|
||||
namespace AudioServer {
|
||||
|
||||
class BufferQueue;
|
||||
class Mixer;
|
||||
|
||||
class ClientConnection final : public IPC::ClientConnection<AudioClientEndpoint, AudioServerEndpoint>
|
||||
, public AudioServerEndpoint {
|
||||
C_OBJECT(ClientConnection)
|
||||
public:
|
||||
explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id, Mixer& mixer);
|
||||
~ClientConnection() override;
|
||||
|
||||
void did_finish_playing_buffer(Badge<BufferQueue>, int buffer_id);
|
||||
void did_change_muted_state(Badge<Mixer>, bool muted);
|
||||
void did_change_main_mix_volume(Badge<Mixer>, int volume);
|
||||
|
||||
virtual void die() override;
|
||||
|
||||
static void for_each(Function<void(ClientConnection&)>);
|
||||
|
||||
private:
|
||||
virtual OwnPtr<Messages::AudioServer::GreetResponse> handle(const Messages::AudioServer::Greet&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::GetMainMixVolumeResponse> handle(const Messages::AudioServer::GetMainMixVolume&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::SetMainMixVolumeResponse> handle(const Messages::AudioServer::SetMainMixVolume&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::EnqueueBufferResponse> handle(const Messages::AudioServer::EnqueueBuffer&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::GetRemainingSamplesResponse> handle(const Messages::AudioServer::GetRemainingSamples&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::GetPlayedSamplesResponse> handle(const Messages::AudioServer::GetPlayedSamples&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::SetPausedResponse> handle(const Messages::AudioServer::SetPaused&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::ClearBufferResponse> handle(const Messages::AudioServer::ClearBuffer&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::GetPlayingBufferResponse> handle(const Messages::AudioServer::GetPlayingBuffer&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::GetMutedResponse> handle(const Messages::AudioServer::GetMuted&) override;
|
||||
virtual OwnPtr<Messages::AudioServer::SetMutedResponse> handle(const Messages::AudioServer::SetMuted&) override;
|
||||
|
||||
Mixer& m_mixer;
|
||||
RefPtr<BufferQueue> m_queue;
|
||||
};
|
||||
|
||||
}
|
166
Userland/Services/AudioServer/Mixer.cpp
Normal file
166
Userland/Services/AudioServer/Mixer.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* 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 <AK/Array.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AudioServer/ClientConnection.h>
|
||||
#include <AudioServer/Mixer.h>
|
||||
#include <pthread.h>
|
||||
#include <strings.h>
|
||||
|
||||
namespace AudioServer {
|
||||
|
||||
Mixer::Mixer()
|
||||
: m_device(Core::File::construct("/dev/audio", this))
|
||||
, m_sound_thread(LibThread::Thread::construct(
|
||||
[this] {
|
||||
mix();
|
||||
return 0;
|
||||
},
|
||||
"AudioServer[mixer]"))
|
||||
{
|
||||
if (!m_device->open(Core::IODevice::WriteOnly)) {
|
||||
dbgln("Can't open audio device: {}", 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<BufferQueue> Mixer::create_queue(ClientConnection& client)
|
||||
{
|
||||
auto queue = adopt(*new BufferQueue(client));
|
||||
pthread_mutex_lock(&m_pending_mutex);
|
||||
m_pending_mixing.append(*queue);
|
||||
m_added_queue = true;
|
||||
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() || m_added_queue) {
|
||||
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);
|
||||
m_added_queue = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_muted) {
|
||||
m_device->write(m_zero_filled_buffer, 4096);
|
||||
} else {
|
||||
Array<u8, 4096> buffer;
|
||||
OutputMemoryStream stream { buffer };
|
||||
|
||||
for (int i = 0; i < mixed_buffer_length; ++i) {
|
||||
auto& mixed_sample = mixed_buffer[i];
|
||||
|
||||
mixed_sample.scale(m_main_volume);
|
||||
mixed_sample.clip();
|
||||
|
||||
LittleEndian<i16> out_sample;
|
||||
out_sample = mixed_sample.left * NumericLimits<i16>::max();
|
||||
stream << out_sample;
|
||||
|
||||
out_sample = mixed_sample.right * NumericLimits<i16>::max();
|
||||
stream << out_sample;
|
||||
}
|
||||
|
||||
ASSERT(stream.is_end());
|
||||
ASSERT(!stream.has_any_error());
|
||||
m_device->write(stream.data(), stream.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::set_main_volume(int volume)
|
||||
{
|
||||
if (volume > 100)
|
||||
m_main_volume = 100;
|
||||
else
|
||||
m_main_volume = volume;
|
||||
ClientConnection::for_each([volume](ClientConnection& client) {
|
||||
client.did_change_main_mix_volume({}, volume);
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
void BufferQueue::enqueue(NonnullRefPtr<Audio::Buffer>&& buffer)
|
||||
{
|
||||
m_remaining_samples += buffer->sample_count();
|
||||
m_queue.enqueue(move(buffer));
|
||||
}
|
||||
}
|
144
Userland/Services/AudioServer/Mixer.h
Normal file
144
Userland/Services/AudioServer/Mixer.h
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* 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 <AK/Atomic.h>
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/NonnullRefPtrVector.h>
|
||||
#include <AK/Queue.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibAudio/Buffer.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibThread/Lock.h>
|
||||
#include <LibThread/Thread.h>
|
||||
|
||||
namespace AudioServer {
|
||||
|
||||
class ClientConnection;
|
||||
|
||||
class BufferQueue : public RefCounted<BufferQueue> {
|
||||
public:
|
||||
explicit BufferQueue(ClientConnection&);
|
||||
~BufferQueue() { }
|
||||
|
||||
bool is_full() const { return m_queue.size() >= 3; }
|
||||
void enqueue(NonnullRefPtr<Audio::Buffer>&&);
|
||||
|
||||
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<Audio::Buffer> m_current;
|
||||
Queue<NonnullRefPtr<Audio::Buffer>> m_queue;
|
||||
int m_position { 0 };
|
||||
int m_remaining_samples { 0 };
|
||||
int m_played_samples { 0 };
|
||||
bool m_paused { false };
|
||||
WeakPtr<ClientConnection> m_client;
|
||||
};
|
||||
|
||||
class Mixer : public Core::Object {
|
||||
C_OBJECT(Mixer)
|
||||
public:
|
||||
Mixer();
|
||||
virtual ~Mixer() override;
|
||||
|
||||
NonnullRefPtr<BufferQueue> create_queue(ClientConnection&);
|
||||
|
||||
int main_volume() const { return m_main_volume; }
|
||||
void set_main_volume(int volume);
|
||||
|
||||
bool is_muted() const { return m_muted; }
|
||||
void set_muted(bool);
|
||||
|
||||
private:
|
||||
Vector<NonnullRefPtr<BufferQueue>> m_pending_mixing;
|
||||
Atomic<bool> m_added_queue { false };
|
||||
pthread_mutex_t m_pending_mutex;
|
||||
pthread_cond_t m_pending_cond;
|
||||
|
||||
RefPtr<Core::File> m_device;
|
||||
|
||||
NonnullRefPtr<LibThread::Thread> m_sound_thread;
|
||||
|
||||
bool m_muted { false };
|
||||
int m_main_volume { 100 };
|
||||
|
||||
u8* m_zero_filled_buffer { nullptr };
|
||||
|
||||
void mix();
|
||||
};
|
||||
}
|
63
Userland/Services/AudioServer/main.cpp
Normal file
63
Userland/Services/AudioServer/main.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* 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 "Mixer.h"
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
if (pledge("stdio thread shared_buffer accept rpath wpath cpath unix fattr", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Core::EventLoop event_loop;
|
||||
AudioServer::Mixer mixer;
|
||||
|
||||
auto server = Core::LocalServer::construct();
|
||||
bool ok = server->take_over_from_system_server();
|
||||
ASSERT(ok);
|
||||
server->on_ready_to_accept = [&] {
|
||||
auto client_socket = server->accept();
|
||||
if (!client_socket) {
|
||||
dbgln("AudioServer: accept failed.");
|
||||
return;
|
||||
}
|
||||
static int s_next_client_id = 0;
|
||||
int client_id = ++s_next_client_id;
|
||||
IPC::new_client_connection<AudioServer::ClientConnection>(client_socket.release_nonnull(), client_id, mixer);
|
||||
};
|
||||
|
||||
if (pledge("stdio thread shared_buffer accept", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
unveil(nullptr, nullptr);
|
||||
|
||||
return event_loop.exec();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue