LCOV - code coverage report
Current view: top level - muse/framework/audio/main/internal - audiodrivercontroller.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 83.6 % 238 199
Test Date: 2026-11-05 04:07:02

            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              : }
        

Generated by: LCOV version 2.0-1