Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2025 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "startaudiocontroller.h"
23 :
24 : #include "global/realfn.h"
25 : #include "global/runtime.h"
26 : #include "global/async/processevents.h"
27 :
28 : #include "audio/common/audiosanitizer.h"
29 : #include "audio/common/rpc/rpcpacker.h"
30 : #include "audio/common/workmode.h"
31 : #include "audio/common/alignmentbuffer.h"
32 :
33 : #include "audio/engine/platform/general/generalaudioworker.h"
34 :
35 : #ifndef Q_OS_WASM
36 : #include "audio/engine/internal/enginecontroller.h"
37 : #endif
38 :
39 : #include "audio/devtools/inputlag.h"
40 :
41 : #include "log.h"
42 :
43 : using namespace muse;
44 : using namespace muse::audio;
45 : using namespace muse::audio::rpc;
46 :
47 0 : static void measureInputLag(const float* buf, const size_t size)
48 : {
49 0 : if (INPUT_LAG_TIMER_STARTED) {
50 0 : for (size_t i = 0; i < size; ++i) {
51 0 : if (!muse::RealIsNull(buf[i])) {
52 0 : STOP_INPUT_LAG_TIMER;
53 0 : return;
54 : }
55 : }
56 : }
57 : }
58 :
59 0 : StartAudioController::StartAudioController(std::shared_ptr<rpc::IRpcChannel> rpcChannel)
60 0 : : m_rpcChannel(rpcChannel)
61 : {
62 0 : }
63 :
64 : #ifndef Q_OS_WASM
65 0 : void StartAudioController::th_setupEngine()
66 : {
67 0 : LOGI() << "begin engine run";
68 0 : runtime::setThreadName("audio_engine");
69 0 : AudioSanitizer::setupEngineThread();
70 : ONLY_AUDIO_ENGINE_THREAD;
71 :
72 0 : m_rpcChannel->setupOnEngine();
73 :
74 0 : m_engineController = std::make_shared<engine::EngineController>(m_rpcChannel);
75 0 : m_engineController->onStartRunning();
76 :
77 0 : LOGI() << "audio engine running";
78 0 : }
79 :
80 : #endif
81 :
82 0 : void StartAudioController::init()
83 : {
84 0 : m_rpcChannel->onNotification(rpc::GLOBAL_CTX_ID, rpc::MsgCode::EngineRunning, [this](const rpc::Msg&) {
85 0 : soundFontController()->loadSoundFonts();
86 :
87 0 : m_isEngineRunning.set(true);
88 0 : });
89 :
90 0 : workmode::load();
91 :
92 : #ifndef Q_OS_WASM
93 0 : if (workmode::mode() == workmode::HybridMode) {
94 0 : m_worker = std::make_shared<engine::GeneralAudioWorker>();
95 0 : m_worker->run([this]() {
96 : static bool once = false;
97 0 : if (!once) {
98 0 : th_setupEngine();
99 0 : m_worker->setInterval(16 /*msec*/);
100 0 : once = true;
101 : }
102 :
103 0 : static const std::thread::id thisThId = std::this_thread::get_id();
104 :
105 0 : async::processMessages(thisThId);
106 0 : m_rpcChannel->process();
107 0 : });
108 : }
109 : #endif // not Q_OS_WASM
110 0 : }
111 :
112 0 : bool StartAudioController::isAudioStarted() const
113 : {
114 0 : return m_isAudioStarted.val;
115 : }
116 :
117 0 : async::Channel<bool> StartAudioController::isAudioStartedChanged() const
118 : {
119 0 : return m_isAudioStarted.ch;
120 : }
121 :
122 0 : void StartAudioController::startAudioProcessing(const IApplication::RunMode& mode)
123 : {
124 0 : IAudioDriver::Spec requiredSpec;
125 0 : requiredSpec.deviceId = configuration()->audioOutputDeviceId();
126 0 : requiredSpec.output = configuration()->desiredOutputSpec();
127 :
128 : #ifndef Q_OS_WASM
129 :
130 0 : m_requiredSamplesTotal = requiredSpec.output.samplesPerChannel * requiredSpec.output.audioChannelCount;
131 :
132 0 : audioDriverController()->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) {
133 0 : m_requiredSamplesTotal = spec.output.samplesPerChannel * spec.output.audioChannelCount;
134 0 : });
135 :
136 0 : bool shouldMeasureInputLag = configuration()->shouldMeasureInputLag();
137 0 : requiredSpec.callback = [this, shouldMeasureInputLag]
138 : (uint8_t* stream, int byteCount) {
139 0 : std::memset(stream, 0, byteCount);
140 : // driver metrics
141 0 : const size_t driverSamplesTotal = byteCount / sizeof(float);
142 0 : float* driverDest = reinterpret_cast<float*>(stream);
143 :
144 0 : const size_t requiredSamplesTotal = m_requiredSamplesTotal;
145 0 : const bool useAlignBuffer = driverSamplesTotal < requiredSamplesTotal;
146 :
147 : // process metrics
148 0 : size_t procSamplesTotal = 0;
149 0 : size_t procSamplesPerChannel = 0;
150 0 : float* procDest = 0;
151 :
152 0 : AlignmentBuffer* alignbuf = nullptr;
153 0 : if (useAlignBuffer) {
154 : // setup align buffer if need
155 0 : const size_t blocks = 2;
156 0 : const size_t capacity = requiredSamplesTotal * blocks;
157 0 : if (!m_alignmentBuffer || m_alignmentBuffer->capacity() != capacity) {
158 0 : m_alignmentBuffer = std::make_shared<AlignmentBuffer>(capacity);
159 : }
160 0 : alignbuf = m_alignmentBuffer.get(); // minor optimization and easier debugging
161 1 : static thread_local std::vector<float> proc_buf; // temp buffer
162 0 : if (proc_buf.size() != requiredSamplesTotal) {
163 0 : proc_buf.resize(requiredSamplesTotal);
164 : }
165 :
166 : // set proc metrics
167 0 : procSamplesTotal = requiredSamplesTotal;
168 0 : procSamplesPerChannel = procSamplesTotal / 2;
169 0 : procDest = &proc_buf[0];
170 : } else {
171 0 : procSamplesTotal = driverSamplesTotal;
172 0 : procSamplesPerChannel = procSamplesTotal / 2;
173 0 : procDest = driverDest;
174 : }
175 :
176 : // try fill data
177 0 : if (useAlignBuffer) {
178 0 : assert(alignbuf);
179 0 : if (alignbuf->availableRead() >= driverSamplesTotal) {
180 0 : alignbuf->read(driverDest, driverSamplesTotal);
181 0 : return;
182 : }
183 : }
184 :
185 : // process
186 0 : if (workmode::mode() == workmode::HybridMode) {
187 0 : m_engineController->process(procDest, (unsigned)procSamplesPerChannel);
188 0 : } else if (workmode::mode() == workmode::DriverMode) {
189 : static bool once = false;
190 0 : if (!once) {
191 0 : th_setupEngine();
192 0 : once = true;
193 : }
194 :
195 0 : m_rpcChannel->process();
196 0 : m_engineController->process(procDest, (unsigned)procSamplesPerChannel);
197 : } else {
198 0 : UNREACHABLE;
199 : }
200 :
201 : // write temp and fill driver dest
202 0 : if (useAlignBuffer) {
203 0 : assert(alignbuf);
204 0 : alignbuf->write(procDest, procSamplesTotal);
205 0 : alignbuf->read(driverDest, driverSamplesTotal);
206 : }
207 :
208 0 : if (shouldMeasureInputLag) {
209 0 : measureInputLag(procDest, procSamplesTotal);
210 : }
211 0 : };
212 :
213 : #endif // Q_OS_WASM
214 :
215 0 : IAudioDriver::Spec activeSpec;
216 0 : if (mode == IApplication::RunMode::GuiApp) {
217 0 : if (!audioDriverController()->open(requiredSpec, &activeSpec)) {
218 0 : return;
219 : }
220 : } else {
221 0 : activeSpec = requiredSpec;
222 : }
223 :
224 0 : AudioEngineConfig conf = configuration()->engineConfig();
225 0 : auto sendEngineInit = [this, activeSpec, conf]() {
226 0 : m_rpcChannel->send(rpc::make_request(rpc::GLOBAL_CTX_ID, MsgCode::EngineInit, RpcPacker::pack(activeSpec.output, conf)),
227 0 : [this](const Msg&) {
228 0 : m_isAudioStarted.set(true);
229 0 : });
230 0 : };
231 :
232 0 : if (m_isEngineRunning.val) {
233 0 : sendEngineInit();
234 : } else {
235 0 : m_isEngineRunning.ch.onReceive(this, [sendEngineInit](bool arg) {
236 0 : if (arg) {
237 0 : sendEngineInit();
238 : }
239 0 : });
240 : }
241 0 : }
242 :
243 0 : void StartAudioController::stopAudioProcessing()
244 : {
245 : #ifndef Q_OS_WASM
246 0 : bool isEngineDeinited = false;
247 :
248 : //! NOTE Stop waiting for the engine to start: below we process the RPC channel,
249 : //! and a still pending EngineRunning would send EngineInit in the middle of the shutdown
250 0 : m_isEngineRunning.ch.disconnect(this);
251 :
252 0 : m_rpcChannel->send(rpc::make_request(rpc::GLOBAL_CTX_ID, MsgCode::EngineDeinit), [this, &isEngineDeinited](const Msg&) {
253 0 : isEngineDeinited = true;
254 :
255 0 : if (m_isAudioStarted.val) {
256 0 : m_isAudioStarted.set(false);
257 : }
258 0 : });
259 :
260 0 : int attemptsLimit = 100;
261 0 : int attempt = 0;
262 :
263 : do {
264 0 : ++attempt;
265 :
266 : // Ensure that RPC process() is called at least once
267 0 : m_rpcChannel->process();
268 :
269 0 : if (isEngineDeinited) {
270 0 : break;
271 : }
272 :
273 0 : std::this_thread::yield();
274 : using namespace std::chrono_literals;
275 0 : std::this_thread::sleep_for(10ms);
276 0 : } while (!isEngineDeinited && attempt <= attemptsLimit);
277 :
278 0 : if (attempt >= attemptsLimit) {
279 0 : LOGE() << "Engine deinit failed because of number of attempts exceeded";
280 : }
281 :
282 0 : audioDriverController()->close();
283 :
284 0 : if (m_worker && m_worker->isRunning()) {
285 0 : m_worker->stop();
286 : }
287 :
288 : #endif
289 0 : }
|