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 :
23 : #include "audiodrivercontroller.h"
24 :
25 : #include "common/audiotaskscheduler.h"
26 : #include "common/iaudiotaskscheduler.h"
27 : #include "global/async/async.h"
28 :
29 : #include "log.h"
30 :
31 : using namespace muse;
32 : using namespace muse::audio;
33 : using namespace muse::audio::rpc;
34 :
35 24 : AudioDriverController::AudioDriverController()
36 48 : : m_audioTaskScheduler(std::make_shared<AudioTaskScheduler>())
37 : {
38 48 : }
39 :
40 1 : std::vector<std::string> AudioDriverController::availableAudioDrivers() const
41 : {
42 1 : return audioDriverFactory()->availableAudioDrivers();
43 : }
44 :
45 27 : void AudioDriverController::setNewDriver(IAudioDriverPtr newDriver)
46 : {
47 27 : if (m_audioDriver) {
48 : // unsubscribe
49 4 : m_audioDriver->availableOutputDevicesChanged().disconnect(this);
50 4 : m_audioDriver->activeSpecChanged().disconnect(this);
51 : }
52 :
53 27 : m_audioDriver = newDriver;
54 :
55 27 : if (m_audioDriver) {
56 : // subscribe
57 27 : m_audioDriver->availableOutputDevicesChanged().onNotify(this, [this]() {
58 6 : async::Async::call(this, [this]() {
59 6 : LOGI() << "Available output devices changed, checking connection...";
60 6 : handleOutputDeviceChange();
61 6 : m_availableOutputDevicesChanged.notify();
62 6 : });
63 6 : });
64 :
65 27 : m_audioDriver->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) {
66 34 : if (spec.isValid()) {
67 34 : m_lastValidSpec = spec;
68 : } else {
69 0 : LOGW() << "Driver reported an invalid spec";
70 : }
71 :
72 34 : m_activeSpecChanged.send(spec);
73 :
74 34 : async::Async::call(this, [this, spec]() {
75 34 : configuration()->setAudioOutputDeviceId(spec.deviceId);
76 :
77 34 : m_outputDeviceChanged.notify();
78 34 : m_outputDeviceBufferSizeChanged.notify();
79 34 : m_outputDeviceSampleRateChanged.notify();
80 :
81 34 : updateOutputSpec();
82 34 : });
83 34 : });
84 : }
85 :
86 27 : auto audioWorkgroupSource = std::dynamic_pointer_cast<AudioTaskScheduler>(m_audioTaskScheduler);
87 27 : if (audioWorkgroupSource) {
88 27 : audioWorkgroupSource->setAudioDriver(m_audioDriver);
89 : }
90 27 : }
91 :
92 0 : std::string AudioDriverController::currentAudioDriverName() const
93 : {
94 0 : return configuration()->currentAudioDriverName();
95 : }
96 :
97 2 : void AudioDriverController::changeCurrentAudioDriver(const std::string& name)
98 : {
99 2 : IF_ASSERT_FAILED(m_audioDriver) {
100 0 : return;
101 : }
102 :
103 2 : if (m_audioDriver->isOpened()) {
104 2 : m_audioDriver->close();
105 : }
106 :
107 2 : IAudioDriverPtr driver = audioDriverFactory()->createDriver(name);
108 2 : setNewDriver(driver);
109 2 : m_audioDriver->init();
110 2 : LOGI() << "Used audio driver: " << m_audioDriver->name();
111 :
112 : // reset to default
113 2 : IAudioDriver::Spec spec;
114 2 : spec.output = configuration()->defaultOutputSpec();
115 2 : spec.callback = m_callback;
116 :
117 2 : if (m_audioDriver && !m_audioDriver->defaultDevice().empty()) {
118 1 : spec.deviceId = DEFAULT_DEVICE_ID;
119 1 : bool ok = m_audioDriver->open(spec, nullptr);
120 1 : if (!ok) {
121 0 : LOGE() << "Failed to open audio driver: " << name;
122 : }
123 : } else {
124 1 : LOGW() << "No devices for " << name;
125 : }
126 :
127 2 : configuration()->setCurrentAudioDriverName(name);
128 2 : m_currentAudioDriverChanged.notify();
129 2 : m_availableOutputDevicesChanged.notify();
130 2 : }
131 :
132 5 : async::Notification AudioDriverController::currentAudioDriverChanged() const
133 : {
134 5 : return m_currentAudioDriverChanged;
135 : }
136 :
137 0 : AudioDeviceList AudioDriverController::availableOutputDevices() const
138 : {
139 0 : IF_ASSERT_FAILED(m_audioDriver) {
140 0 : return {};
141 : }
142 0 : return m_audioDriver->availableOutputDevices();
143 : }
144 :
145 3 : async::Notification AudioDriverController::availableOutputDevicesChanged() const
146 : {
147 3 : return m_availableOutputDevicesChanged;
148 : }
149 :
150 23 : bool AudioDriverController::open(const IAudioDriver::Spec& spec, IAudioDriver::Spec* activeSpec)
151 : {
152 23 : m_callback = spec.callback;
153 :
154 23 : const std::string currentAudioDriverName = configuration()->currentAudioDriverName();
155 23 : IAudioDriverPtr driver = audioDriverFactory()->createDriver(currentAudioDriverName);
156 23 : driver->init();
157 23 : setNewDriver(driver);
158 :
159 23 : LOGI() << "Trying to open audio driver: " << m_audioDriver->name() << ", " << spec;
160 23 : bool ok = m_audioDriver->open(spec, activeSpec);
161 23 : if (!ok) {
162 : // reset to default device
163 4 : IAudioDriver::Spec defaultDeviceSpec = spec;
164 4 : defaultDeviceSpec.deviceId = m_audioDriver->defaultDevice();
165 4 : LOGW() << "Failed to open device: " << spec.deviceId << ", falling back to default: " << defaultDeviceSpec.deviceId;
166 4 : ok = m_audioDriver->open(defaultDeviceSpec, activeSpec);
167 4 : }
168 :
169 23 : if (!ok) {
170 3 : ok = switchToDefaultAudioDriver(activeSpec);
171 : }
172 :
173 23 : if (ok) {
174 21 : LOGI() << "Opened audio driver: " << m_audioDriver->name() << ", " << m_audioDriver->activeSpec();
175 : } else {
176 2 : LOGE() << "Failed to open any audio driver, last tried: " << m_audioDriver->name();
177 : }
178 :
179 23 : return ok;
180 23 : }
181 :
182 4 : void AudioDriverController::close()
183 : {
184 4 : if (m_audioDriver) {
185 4 : m_audioDriver->close();
186 : }
187 4 : }
188 :
189 24 : bool AudioDriverController::isOpened() const
190 : {
191 24 : IF_ASSERT_FAILED(m_audioDriver) {
192 0 : return false;
193 : }
194 24 : return m_audioDriver->isOpened();
195 : }
196 :
197 15 : const IAudioDriver::Spec& AudioDriverController::activeSpec() const
198 : {
199 15 : IF_ASSERT_FAILED(m_audioDriver) {
200 0 : static IAudioDriver::Spec dummy;
201 0 : return dummy;
202 : }
203 15 : return m_audioDriver->activeSpec();
204 : }
205 :
206 0 : async::Channel<IAudioDriver::Spec> AudioDriverController::activeSpecChanged() const
207 : {
208 0 : return m_activeSpecChanged;
209 : }
210 :
211 10 : AudioDeviceID AudioDriverController::outputDevice() const
212 : {
213 10 : IF_ASSERT_FAILED(m_audioDriver) {
214 0 : return AudioDeviceID();
215 : }
216 10 : return m_audioDriver->activeSpec().deviceId;
217 : }
218 :
219 5 : bool AudioDriverController::selectOutputDevice(const AudioDeviceID& deviceId)
220 : {
221 5 : IF_ASSERT_FAILED(m_audioDriver) {
222 0 : return false;
223 : }
224 :
225 : // Audio has never been started yet
226 5 : if (!m_audioDriver->isOpened() && !m_lastValidSpec.isValid()) {
227 1 : configuration()->setAudioOutputDeviceId(deviceId);
228 1 : return true;
229 : }
230 :
231 4 : IAudioDriver::Spec spec;
232 4 : spec.deviceId = deviceId;
233 4 : spec.callback = m_callback;
234 4 : spec.output = configuration()->desiredOutputSpec();
235 :
236 4 : LOGI() << "Trying to change output device to " << spec;
237 :
238 4 : return reopenWithSpec(spec);
239 4 : }
240 :
241 3 : async::Notification AudioDriverController::outputDeviceChanged() const
242 : {
243 3 : return m_outputDeviceChanged;
244 : }
245 :
246 6 : void AudioDriverController::handleOutputDeviceChange()
247 : {
248 6 : if (!m_audioDriver->isOpened() && !m_retryOpenDevice) {
249 1 : return;
250 : }
251 :
252 : // Some drivers reset their internal spec to a blank state after a failed open attempt, so fall
253 : // back to the last spec that was actually confirmed to work
254 5 : IAudioDriver::Spec spec = m_audioDriver->activeSpec();
255 5 : if (!spec.isValid()) {
256 1 : if (m_lastValidSpec.isValid()) {
257 1 : LOGW() << "Active spec is invalid, using last valid spec";
258 1 : spec = m_lastValidSpec;
259 : } else {
260 0 : LOGW() << "Active spec is invalid, using default spec";
261 0 : spec.deviceId = DEFAULT_DEVICE_ID;
262 0 : spec.output = configuration()->defaultOutputSpec();
263 0 : spec.callback = m_callback;
264 : }
265 : }
266 :
267 5 : LOGI() << "Reopening output device, " << spec;
268 :
269 5 : m_audioDriver->close();
270 5 : bool ok = m_audioDriver->open(spec, nullptr);
271 5 : if (!ok) {
272 : // reset to default device
273 3 : LOGW() << "Failed to reopen device: " << spec.deviceId << ", falling back to default";
274 3 : spec.deviceId = DEFAULT_DEVICE_ID;
275 3 : ok = m_audioDriver->open(spec, nullptr);
276 3 : if (!ok) {
277 2 : LOGE() << "Failed to reopen default device on " << m_audioDriver->name() << ", switching to default audio driver";
278 2 : ok = switchToDefaultAudioDriver();
279 : }
280 : }
281 :
282 5 : if (ok) {
283 4 : LOGI() << "Reopened output device: " << m_audioDriver->activeSpec();
284 : } else {
285 1 : LOGE() << "Failed to reopen output device, will retry on next device list change";
286 : }
287 :
288 5 : m_retryOpenDevice = !ok;
289 5 : }
290 :
291 5 : bool AudioDriverController::switchToDefaultAudioDriver(IAudioDriver::Spec* activeSpec)
292 : {
293 5 : const std::string defaultAudioDriverName = configuration()->defaultAudioDriverName();
294 5 : const std::string currentAudioDriverName = configuration()->currentAudioDriverName();
295 :
296 5 : if (defaultAudioDriverName == currentAudioDriverName) {
297 3 : LOGE() << "Already on the default audio driver: " << defaultAudioDriverName << ", cannot fall back further";
298 3 : return false;
299 : }
300 :
301 2 : LOGW() << "Switching from " << currentAudioDriverName << " to default audio driver: " << defaultAudioDriverName;
302 :
303 2 : IAudioDriverPtr defaultDriver = audioDriverFactory()->createDriver(defaultAudioDriverName);
304 2 : defaultDriver->init();
305 2 : setNewDriver(defaultDriver);
306 :
307 2 : IAudioDriver::Spec defSpec;
308 2 : defSpec.deviceId = DEFAULT_DEVICE_ID;
309 2 : defSpec.output = configuration()->defaultOutputSpec();
310 2 : defSpec.callback = m_callback;
311 :
312 2 : bool ok = m_audioDriver->open(defSpec, activeSpec);
313 2 : if (ok) {
314 2 : configuration()->setCurrentAudioDriverName(defaultAudioDriverName);
315 2 : m_currentAudioDriverChanged.notify();
316 2 : LOGI() << "Successfully switched to default audio driver: " << defaultAudioDriverName;
317 : } else {
318 0 : LOGE() << "Failed to open default audio driver: " << defaultAudioDriverName;
319 : }
320 :
321 2 : return ok;
322 5 : }
323 :
324 8 : bool AudioDriverController::reopenWithSpec(const IAudioDriver::Spec& spec)
325 : {
326 : // A failed reopen can blank the driver's spec
327 8 : IAudioDriver::Spec oldSpec = m_audioDriver->activeSpec();
328 8 : if (!oldSpec.isValid()) {
329 2 : oldSpec = m_lastValidSpec;
330 : }
331 :
332 8 : m_audioDriver->close();
333 8 : if (m_audioDriver->open(spec, nullptr)) {
334 4 : LOGI() << "Reopened with: " << spec;
335 4 : m_retryOpenDevice = false;
336 4 : return true;
337 : }
338 :
339 4 : LOGE() << "Failed to reopen with: " << spec << ", returning to: " << oldSpec;
340 4 : if (m_audioDriver->open(oldSpec, nullptr)) {
341 4 : LOGI() << "Restored previous spec: " << oldSpec;
342 4 : m_retryOpenDevice = false;
343 : } else {
344 0 : LOGE() << "Failed to restore previous spec: " << oldSpec << ", will retry on next device list change";
345 0 : m_retryOpenDevice = true;
346 : }
347 :
348 4 : return false;
349 8 : }
350 :
351 38 : void AudioDriverController::updateOutputSpec()
352 : {
353 38 : if (!m_audioDriver->isOpened()) {
354 2 : return;
355 : }
356 :
357 36 : IAudioDriver::Spec activeSpec = m_audioDriver->activeSpec();
358 36 : rpcChannel()->send(rpc::make_request(rpc::GLOBAL_CTX_ID, MsgCode::SetOutputSpec, RpcPacker::pack(activeSpec.output)));
359 36 : }
360 :
361 0 : std::vector<samples_t> AudioDriverController::availableOutputDeviceBufferSizes() const
362 : {
363 0 : IF_ASSERT_FAILED(m_audioDriver) {
364 0 : return {};
365 : }
366 0 : return m_audioDriver->availableOutputDeviceBufferSizes();
367 : }
368 :
369 3 : void AudioDriverController::changeBufferSize(samples_t samples)
370 : {
371 3 : IF_ASSERT_FAILED(m_audioDriver) {
372 0 : return;
373 : }
374 :
375 3 : LOGI() << "Trying to change buffer size: " << samples;
376 3 : bool ok = true;
377 :
378 3 : if (m_audioDriver->isOpened()) {
379 2 : IAudioDriver::Spec spec = m_audioDriver->activeSpec();
380 2 : spec.output.samplesPerChannel = samples;
381 2 : ok = reopenWithSpec(spec);
382 2 : }
383 :
384 3 : if (ok) {
385 2 : updateOutputSpec();
386 2 : configuration()->setDriverBufferSize(samples);
387 2 : m_outputDeviceBufferSizeChanged.notify();
388 : } else {
389 1 : LOGE() << "Failed to change buffer size to: " << samples;
390 : }
391 : }
392 :
393 0 : async::Notification AudioDriverController::outputDeviceBufferSizeChanged() const
394 : {
395 0 : return m_outputDeviceBufferSizeChanged;
396 : }
397 :
398 0 : std::vector<sample_rate_t> AudioDriverController::availableOutputDeviceSampleRates() const
399 : {
400 0 : IF_ASSERT_FAILED(m_audioDriver) {
401 0 : return {};
402 : }
403 0 : return m_audioDriver->availableOutputDeviceSampleRates();
404 : }
405 :
406 3 : void AudioDriverController::changeSampleRate(sample_rate_t sampleRate)
407 : {
408 3 : IF_ASSERT_FAILED(m_audioDriver) {
409 0 : return;
410 : }
411 :
412 3 : LOGI() << "Trying to change sampleRate: " << sampleRate;
413 3 : bool ok = true;
414 :
415 3 : if (m_audioDriver->isOpened()) {
416 2 : IAudioDriver::Spec spec = m_audioDriver->activeSpec();
417 2 : spec.output.sampleRate = sampleRate;
418 2 : ok = reopenWithSpec(spec);
419 2 : }
420 :
421 3 : if (ok) {
422 2 : updateOutputSpec();
423 2 : configuration()->setSampleRate(sampleRate);
424 2 : m_outputDeviceSampleRateChanged.notify();
425 : } else {
426 1 : LOGE() << "Failed to change sample rate to: " << sampleRate;
427 : }
428 : }
429 :
430 0 : async::Notification AudioDriverController::outputDeviceSampleRateChanged() const
431 : {
432 0 : return m_outputDeviceSampleRateChanged;
433 : }
434 :
435 0 : IAudioTaskSchedulerPtr muse::audio::AudioDriverController::getAudioTaskScheduler() const
436 : {
437 0 : return m_audioTaskScheduler;
438 : }
|