Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/Atomic.h>
9#include <AK/Format.h>
10#include <AK/OwnPtr.h>
11#include <AK/Time.h>
12#include <AK/Types.h>
13#include <LibAudio/ConnectionToServer.h>
14#include <LibAudio/Queue.h>
15#include <LibAudio/UserSampleQueue.h>
16#include <LibCore/Event.h>
17#include <LibThreading/Mutex.h>
18#include <sched.h>
19#include <time.h>
20
21namespace Audio {
22
23ConnectionToServer::ConnectionToServer(NonnullOwnPtr<Core::LocalSocket> socket)
24 : IPC::ConnectionToServer<AudioClientEndpoint, AudioServerEndpoint>(*this, move(socket))
25 , m_buffer(make<AudioQueue>(MUST(AudioQueue::create())))
26 , m_user_queue(make<UserSampleQueue>())
27 , m_background_audio_enqueuer(Threading::Thread::construct([this]() {
28 // All the background thread does is run an event loop.
29 Core::EventLoop enqueuer_loop;
30 m_enqueuer_loop = &enqueuer_loop;
31 enqueuer_loop.exec();
32 {
33 Threading::MutexLocker const locker(m_enqueuer_loop_destruction);
34 m_enqueuer_loop = nullptr;
35 }
36 return (intptr_t) nullptr;
37 }))
38{
39 async_pause_playback();
40 set_buffer(*m_buffer);
41}
42
43ConnectionToServer::~ConnectionToServer()
44{
45 die();
46}
47
48void ConnectionToServer::die()
49{
50 {
51 Threading::MutexLocker const locker(m_enqueuer_loop_destruction);
52 // We're sometimes getting here after the other thread has already exited and its event loop does no longer exist.
53 if (m_enqueuer_loop != nullptr) {
54 m_enqueuer_loop->wake();
55 m_enqueuer_loop->quit(0);
56 }
57 }
58 if (m_background_audio_enqueuer->is_started())
59 (void)m_background_audio_enqueuer->join();
60}
61
62ErrorOr<void> ConnectionToServer::async_enqueue(FixedArray<Sample>&& samples)
63{
64 if (!m_background_audio_enqueuer->is_started()) {
65 m_background_audio_enqueuer->start();
66 TRY(m_background_audio_enqueuer->set_priority(THREAD_PRIORITY_MAX));
67 }
68
69 update_good_sleep_time();
70 m_user_queue->append(move(samples));
71 // Wake the background thread to make sure it starts enqueuing audio.
72 m_enqueuer_loop->wake_once(*this, 0);
73 async_start_playback();
74
75 return {};
76}
77
78void ConnectionToServer::clear_client_buffer()
79{
80 m_user_queue->clear();
81}
82
83void ConnectionToServer::update_good_sleep_time()
84{
85 auto sample_rate = static_cast<double>(get_sample_rate());
86 auto buffer_play_time_ns = 1'000'000'000.0 / (sample_rate / static_cast<double>(AUDIO_BUFFER_SIZE));
87 // A factor of 1 should be good for now.
88 m_good_sleep_time = Time::from_nanoseconds(static_cast<unsigned>(buffer_play_time_ns)).to_timespec();
89}
90
91// Non-realtime audio writing loop
92void ConnectionToServer::custom_event(Core::CustomEvent&)
93{
94 Array<Sample, AUDIO_BUFFER_SIZE> next_chunk;
95 while (true) {
96 if (m_user_queue->is_empty()) {
97 dbgln("Reached end of provided audio data, going to sleep");
98 break;
99 }
100
101 auto available_samples = min(AUDIO_BUFFER_SIZE, m_user_queue->size());
102 for (size_t i = 0; i < available_samples; ++i)
103 next_chunk[i] = (*m_user_queue)[i];
104
105 m_user_queue->discard_samples(available_samples);
106
107 // FIXME: Could we receive interrupts in a good non-IPC way instead?
108 auto result = m_buffer->blocking_enqueue(next_chunk, [this]() {
109 nanosleep(&m_good_sleep_time, nullptr);
110 });
111 if (result.is_error())
112 dbgln("Error while writing samples to shared buffer: {}", result.error());
113 }
114}
115
116ErrorOr<void, AudioQueue::QueueStatus> ConnectionToServer::realtime_enqueue(Array<Sample, AUDIO_BUFFER_SIZE> samples)
117{
118 return m_buffer->enqueue(samples);
119}
120
121ErrorOr<void> ConnectionToServer::blocking_realtime_enqueue(Array<Sample, AUDIO_BUFFER_SIZE> samples, Function<void()> wait_function)
122{
123 return m_buffer->blocking_enqueue(samples, move(wait_function));
124}
125
126unsigned ConnectionToServer::total_played_samples() const
127{
128 return m_buffer->weak_tail() * AUDIO_BUFFER_SIZE;
129}
130
131unsigned ConnectionToServer::remaining_samples()
132{
133 return static_cast<unsigned>(m_user_queue->remaining_samples());
134}
135
136size_t ConnectionToServer::remaining_buffers() const
137{
138 return m_buffer->size() - m_buffer->weak_remaining_capacity();
139}
140
141void ConnectionToServer::main_mix_muted_state_changed(bool muted)
142{
143 if (on_main_mix_muted_state_change)
144 on_main_mix_muted_state_change(muted);
145}
146
147void ConnectionToServer::main_mix_volume_changed(double volume)
148{
149 if (on_main_mix_volume_change)
150 on_main_mix_volume_change(volume);
151}
152
153void ConnectionToServer::client_volume_changed(double volume)
154{
155 if (on_client_volume_change)
156 on_client_volume_change(volume);
157}
158
159}