From 45f47df1d03d4dd8947b9ec6465615450a787e4a Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Wed, 12 Nov 2025 10:55:57 +0200 Subject: [PATCH 1/7] Added ability to change audio drivers without restarting --- CMakeLists.txt | 6 - ninja_build.sh | 2 +- src/framework/audio/CMakeLists.txt | 4 +- src/framework/audio/common/audiotypes.h | 13 +- src/framework/audio/driver/audio_driver.cmake | 2 +- .../driver/platform/lin/alsaaudiodriver.cpp | 2 +- .../driver/platform/lin/pwaudiodriver.cpp | 2 +- .../platform/win/wasapiaudiodriver2.cpp | 2 +- src/framework/audio/iaudiodriver.h | 2 + src/framework/audio/iaudiodrivercontroller.h | 9 +- src/framework/audio/main/audio_main.cmake | 2 - src/framework/audio/main/audiomodule.cpp | 4 - src/framework/audio/main/audiomodule.h | 2 - .../main/internal/audioconfiguration.cpp | 2 +- .../main/internal/audiodrivercontroller.cpp | 230 +++++++++++++----- .../main/internal/audiodrivercontroller.h | 24 +- .../internal/audiooutputdevicecontroller.cpp | 93 ------- .../internal/audiooutputdevicecontroller.h | 52 ---- .../main/internal/startaudiocontroller.cpp | 17 +- src/framework/cmake/MuseDeclareOptions.cmake | 12 +- .../cmake/muse_framework_config.h.in | 4 +- .../stubs/audio/audiodrivercontrollerstub.cpp | 26 +- .../stubs/audio/audiodrivercontrollerstub.h | 10 +- .../Preferences/audiomidipreferencesmodel.cpp | 4 +- .../commonaudioapiconfigurationmodel.cpp | 41 +++- .../Preferences/internal/AudioApiSection.qml | 21 -- 26 files changed, 300 insertions(+), 288 deletions(-) delete mode 100644 src/framework/audio/main/internal/audiooutputdevicecontroller.cpp delete mode 100644 src/framework/audio/main/internal/audiooutputdevicecontroller.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a2bc6d7eda397..38279e6d1a87e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,12 +131,6 @@ option(MUE_ENABLE_ENGRAVING_RENDER_DEBUG "Enable rendering debug" OFF) option(MUE_ENABLE_ENGRAVING_LD_ACCESS "Enable diagnostic engraving check layout data access" OFF) option(MUE_ENABLE_ENGRAVING_LD_PASSES "Enable engraving layout by passes" OFF) -if (OS_IS_LIN) - option(MUSE_PIPEWIRE_AUDIO_DRIVER "Use PipeWire audio driver" OFF) # Turns ON on CI -elseif (OS_IS_FBSD) - option(MUSE_PIPEWIRE_AUDIO_DRIVER "Use PipeWire audio driver" OFF) -endif() - ########################################### # Setup Configure ########################################### diff --git a/ninja_build.sh b/ninja_build.sh index d5902efd86757..6483e45b92a7a 100755 --- a/ninja_build.sh +++ b/ninja_build.sh @@ -102,7 +102,7 @@ function do_build() { -DMUSE_MODULE_GLOBAL_LOGGER_DEBUGLEVEL="${MUSESCORE_DEBUGLEVEL_ENABLED}" \ -DMUSE_MODULE_VST="${MUSESCORE_BUILD_VST_MODULE}" \ -DMUSE_MODULE_NETWORK_WEBSOCKET="${MUSESCORE_BUILD_WEBSOCKET}" \ - -DMUSE_PIPEWIRE_AUDIO_DRIVER="${MUSESCORE_BUILD_PIPEWIRE_AUDIO_DRIVER}" \ + -DMUSE_MODULE_AUDIO_PIPEWIRE="${MUSESCORE_BUILD_PIPEWIRE_AUDIO_DRIVER}" \ -DCMAKE_SKIP_RPATH="${MUSESCORE_NO_RPATH}" \ -DMUSE_COMPILE_USE_UNITY="${MUSESCORE_COMPILE_USE_UNITY}" diff --git a/src/framework/audio/CMakeLists.txt b/src/framework/audio/CMakeLists.txt index b2b459012c28c..100c604cd09a1 100644 --- a/src/framework/audio/CMakeLists.txt +++ b/src/framework/audio/CMakeLists.txt @@ -81,7 +81,8 @@ else () find_package(ALSA REQUIRED) set(MODULE_INCLUDE_PRIVATE ${MODULE_INCLUDE_PRIVATE} ${ALSA_INCLUDE_DIRS} ) set(MODULE_LINK ${MODULE_LINK} ${ALSA_LIBRARIES} pthread ) - if (MUSE_PIPEWIRE_AUDIO_DRIVER) + + if (MUSE_MODULE_AUDIO_PIPEWIRE) find_package(PkgConfig) pkg_check_modules(PipeWire libpipewire-0.3) if (PipeWire_FOUND) @@ -92,7 +93,6 @@ else () ) set(MODULE_INCLUDE ${MODULE_INCLUDE} ${PipeWire_INCLUDE_DIRS} ) set(MODULE_LINK ${MODULE_LINK} ${PipeWire_LIBRARIES}) - set(MODULE_DEF ${MODULE_DEF} -DMUSE_PIPEWIRE_AUDIO_DRIVER) else() message(WARNING "Pipewire development files not found.\nPipewire support is disabled from the build.") endif() diff --git a/src/framework/audio/common/audiotypes.h b/src/framework/audio/common/audiotypes.h index 5999dfef05bd9..2ca1cbf84045f 100644 --- a/src/framework/audio/common/audiotypes.h +++ b/src/framework/audio/common/audiotypes.h @@ -37,6 +37,8 @@ #include "mpe/events.h" +#include "log.h" + namespace muse::audio { using msecs_t = int64_t; using secs_t = muse::secs_t; @@ -404,7 +406,16 @@ struct AudioSignalsNotifier { } } - AudioSignalChanges audioSignalChanges; + //! NOTE It would be nice if the driver callback was called in one thread. + //! But some drivers, for example PipeWire, use queues + //! And then the callback can be called in different threads. + //! If a score is open, we will change the audio API (change the driver) + //! then the number of threads used may increase... + //! Channels allow 10 threads by default. Here we're increasing that to the maximum... + //! If this is not enough, then we need to make sure that the callback is called in one thread, + //! or use something else here instead of channels, some kind of queues. + const int _max_threads = 100; + AudioSignalChanges audioSignalChanges = AudioSignalChanges(_max_threads); private: static constexpr volume_dbfs_t PRESSURE_MINIMAL_VALUABLE_DIFF = volume_dbfs_t::make(2.5f); diff --git a/src/framework/audio/driver/audio_driver.cmake b/src/framework/audio/driver/audio_driver.cmake index 40e19d3288f32..a14be8ba70e94 100644 --- a/src/framework/audio/driver/audio_driver.cmake +++ b/src/framework/audio/driver/audio_driver.cmake @@ -60,7 +60,7 @@ elseif(OS_IS_LIN OR OS_IS_FBSD) ${CMAKE_CURRENT_LIST_DIR}/platform/lin/audiodeviceslistener.cpp ${CMAKE_CURRENT_LIST_DIR}/platform/lin/audiodeviceslistener.h ) - if (MUSE_PIPEWIRE_AUDIO_DRIVER) + if (MUSE_MODULE_AUDIO_PIPEWIRE) # this is conditionally added to module source if # pipewire is actually found on the system set(PW_AUDIO_DRIVER_SRC diff --git a/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp b/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp index 82eee82b89d16..3cc00a2ae6e66 100644 --- a/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp +++ b/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp @@ -128,7 +128,7 @@ void AlsaAudioDriver::init() std::string AlsaAudioDriver::name() const { - return "MUAUDIO(ALSA)"; + return "ALSA"; } bool AlsaAudioDriver::open(const Spec& spec, Spec* activeSpec) diff --git a/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp b/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp index fcfa4bccc36ab..d2e10f2c7451e 100644 --- a/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp +++ b/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp @@ -614,7 +614,7 @@ void PwAudioDriver::init() LOGD() << "Running with version " << pw_get_library_version(); } -std::string PwAudioDriver::name() const { return "MUAUDIO(PipeWire)"; } +std::string PwAudioDriver::name() const { return "PipeWire"; } bool PwAudioDriver::open(const Spec& spec, Spec* activeSpec) { diff --git a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp index 3f48c174c7e67..3f6b41e4b5b9a 100644 --- a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp +++ b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp @@ -234,7 +234,7 @@ WasapiAudioDriver2::~WasapiAudioDriver2() std::string WasapiAudioDriver2::name() const { - return "WASAPI_driver"; + return "WASAPI"; } void WasapiAudioDriver2::init() diff --git a/src/framework/audio/iaudiodriver.h b/src/framework/audio/iaudiodriver.h index ae1e8265df001..e0b36b7da85f0 100644 --- a/src/framework/audio/iaudiodriver.h +++ b/src/framework/audio/iaudiodriver.h @@ -50,6 +50,8 @@ class IAudioDriver Format format; // Audio data format Callback callback; // Callback that feeds the audio device void* userdata; // Userdata passed to callback (ignored for NULL callbacks). + + inline bool isValid() const { return output.isValid() && callback != nullptr; } }; virtual void init() = 0; diff --git a/src/framework/audio/iaudiodrivercontroller.h b/src/framework/audio/iaudiodrivercontroller.h index 6ff153eb7173d..b52a98170b646 100644 --- a/src/framework/audio/iaudiodrivercontroller.h +++ b/src/framework/audio/iaudiodrivercontroller.h @@ -35,11 +35,14 @@ class IAudioDriverController : MODULE_EXPORT_INTERFACE virtual ~IAudioDriverController() = default; virtual std::string currentAudioApi() const = 0; - virtual void setCurrentAudioApi(const std::string& name) = 0; - virtual async::Notification currentAudioApiChanged() const = 0; + virtual IAudioDriverPtr audioDriver() const = 0; + virtual void changeAudioDriver(const std::string& name) = 0; + virtual async::Notification audioDriverChanged() const = 0; virtual std::vector availableAudioApiList() const = 0; - virtual IAudioDriverPtr audioDriver() const = 0; + virtual void selectOutputDevice(const std::string& deviceId) = 0; + virtual void changeBufferSize(samples_t samples) = 0; + virtual void changeSampleRate(sample_rate_t sampleRate) = 0; }; } diff --git a/src/framework/audio/main/audio_main.cmake b/src/framework/audio/main/audio_main.cmake index 30c993563e7a1..395cf603ded37 100644 --- a/src/framework/audio/main/audio_main.cmake +++ b/src/framework/audio/main/audio_main.cmake @@ -41,8 +41,6 @@ set(AUDIO_MAIN_SRC ${CMAKE_CURRENT_LIST_DIR}/internal/playback.h ${CMAKE_CURRENT_LIST_DIR}/internal/player.cpp ${CMAKE_CURRENT_LIST_DIR}/internal/player.h - ${CMAKE_CURRENT_LIST_DIR}/internal/audiooutputdevicecontroller.cpp - ${CMAKE_CURRENT_LIST_DIR}/internal/audiooutputdevicecontroller.h ${CMAKE_CURRENT_LIST_DIR}/internal/audiodrivercontroller.cpp ${CMAKE_CURRENT_LIST_DIR}/internal/audiodrivercontroller.h ) diff --git a/src/framework/audio/main/audiomodule.cpp b/src/framework/audio/main/audiomodule.cpp index 26432f08860f1..7903d17a7e20f 100644 --- a/src/framework/audio/main/audiomodule.cpp +++ b/src/framework/audio/main/audiomodule.cpp @@ -40,7 +40,6 @@ #include "internal/audiouiactions.h" #include "internal/startaudiocontroller.h" #include "internal/playback.h" -#include "internal/audiooutputdevicecontroller.h" #include "internal/audiodrivercontroller.h" #include "diagnostics/idiagnosticspathsregister.h" @@ -70,7 +69,6 @@ void AudioModule::registerExports() { m_configuration = std::make_shared(iocContext()); m_actionsController = std::make_shared(); - m_audioOutputController = std::make_shared(iocContext()); m_mainPlayback = std::make_shared(iocContext()); m_audioDriverController = std::make_shared(iocContext()); @@ -132,8 +130,6 @@ void AudioModule::onInit(const IApplication::RunMode& mode) }, Ticker::Mode::Repeat); #endif - m_audioOutputController->init(); - m_mainPlayback->init(); m_startAudioController->init(); diff --git a/src/framework/audio/main/audiomodule.h b/src/framework/audio/main/audiomodule.h index e9ae0cd8e7497..89c3c837f38cd 100644 --- a/src/framework/audio/main/audiomodule.h +++ b/src/framework/audio/main/audiomodule.h @@ -36,7 +36,6 @@ namespace muse::audio { class AudioConfiguration; class AudioActionsController; class StartAudioController; -class AudioOutputDeviceController; class Playback; class ISoundFontController; class AudioDriverController; @@ -58,7 +57,6 @@ class AudioModule : public modularity::IModuleSetup, public async::Asyncable std::shared_ptr m_configuration; std::shared_ptr m_actionsController; std::shared_ptr m_startAudioController; - std::shared_ptr m_audioOutputController; std::shared_ptr m_mainPlayback; std::shared_ptr m_soundFontController; diff --git a/src/framework/audio/main/internal/audioconfiguration.cpp b/src/framework/audio/main/internal/audioconfiguration.cpp index 8c001d1d5a491..56aea6a654775 100644 --- a/src/framework/audio/main/internal/audioconfiguration.cpp +++ b/src/framework/audio/main/internal/audioconfiguration.cpp @@ -60,7 +60,7 @@ void AudioConfiguration::init() #if defined(Q_OS_WIN) settings()->setDefaultValue(AUDIO_API_KEY, Val("WASAPI")); #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - settings()->setDefaultValue(AUDIO_API_KEY, Val("PipeWire")); + settings()->setDefaultValue(AUDIO_API_KEY, Val("ALSA")); #endif settings()->valueChanged(AUDIO_API_KEY).onReceive(nullptr, [this](const Val&) { m_currentAudioApiChanged.notify(); diff --git a/src/framework/audio/main/internal/audiodrivercontroller.cpp b/src/framework/audio/main/internal/audiodrivercontroller.cpp index a7a0907052233..2978f77bcd08e 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.cpp +++ b/src/framework/audio/main/internal/audiodrivercontroller.cpp @@ -31,7 +31,7 @@ #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #include #include "audio/driver/platform/lin/alsaaudiodriver.h" -#ifdef MUSE_PIPEWIRE_AUDIO_DRIVER +#ifdef MUSE_MODULE_AUDIO_PIPEWIRE #include "audio/driver/platform/lin/pwaudiodriver.h" #endif #endif @@ -41,8 +41,10 @@ //#include "audio/driver/platform/win/wincoreaudiodriver.h" //#include "audio/driver/platform/win/wasapiaudiodriver.h" #include "audio/driver/platform/win/wasapiaudiodriver2.h" +#ifdef MUSE_MODULE_AUDIO_ASIO #include "audio/driver/platform/win/asio/asioaudiodriver.h" #endif +#endif #ifdef Q_OS_MACOS #include "audio/driver/platform/osx/osxaudiodriver.h" @@ -55,104 +57,211 @@ #include "log.h" using namespace muse::audio; +using namespace muse::audio::rpc; -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) -static std::shared_ptr makeLinuxAudioDriver(const std::string& driverName) +IAudioDriverPtr AudioDriverController::createDriver(const std::string& name) const { -#if defined(MUSE_PIPEWIRE_AUDIO_DRIVER) - if (!qEnvironmentVariableIsSet("MUSESCORE_FORCE_ALSA") && driverName == "PipeWire") { +#ifdef MUSE_MODULE_AUDIO_JACK + LOGW() << "required: " << name << ", but is set MUSE_MODULE_AUDIO_JACK"; + return std::shared_ptr(new JackAudioDriver()); +#endif + +#ifdef Q_OS_WIN + if (name == "ASIO") { +#ifdef MUSE_MODULE_AUDIO_ASIO + return std::shared_ptr(new AsioAudioDriver()); +#else + LOGW() << "ASIO is required but is not available, WASAPI will be used"; +#endif + } + + // required WASAPI or fallback + return std::shared_ptr(new WasapiAudioDriver2()); +#endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) + if (qEnvironmentVariableIsSet("MUSESCORE_FORCE_ALSA")) { + if (name != "ALSA") { + LOGW() << "required: " << name << ", but is set MUSESCORE_FORCE_ALSA"; + } + return std::make_shared(); + } + + if (name == "PipeWire") { +#ifdef MUSE_MODULE_AUDIO_PIPEWIRE auto driver = std::make_shared(); if (driver->connectedToPwServer()) { - LOGI() << "Using audio driver: Pipewire"; return driver; + } else { + LOGE() << "PipeWire driver failed connected to server"; } +#else + LOGW() << "PipeWire is required but is not available, ALSA will be used"; +#endif } -#endif // MUSE_PIPEWIRE_AUDIO_DRIVER - UNUSED(driverName); - LOGI() << "Using audio driver: ALSA"; + + // required ALSA or fallback return std::make_shared(); -} +#endif -#endif // Q_OS_LINUX || Q_OS_FREEBSD +#ifdef Q_OS_MACOS + UNUSED(name); + return std::shared_ptr(new OSXAudioDriver()); +#endif -void AudioDriverController::init() -{ -#if defined(MUSE_MODULE_AUDIO_JACK) - m_audioDriver = std::shared_ptr(new JackAudioDriver()); -#else +#ifdef Q_OS_WASM + UNUSED(name); + return std::shared_ptr(new WebAudioDriver()); +#endif +} -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - std::string name = configuration()->currentAudioApi(); - m_audioDriver = makeLinuxAudioDriver(name); +std::vector AudioDriverController::availableAudioApiList() const +{ + std::vector names; +#ifdef MUSE_MODULE_AUDIO_JACK + names.push_back("JACK"); + return names; #endif #ifdef Q_OS_WIN - //m_audioDriver = std::shared_ptr(new WinmmDriver()); - //m_audioDriver = std::shared_ptr(new CoreAudioDriver()); - //m_audioDriver = std::shared_ptr(new WasapiAudioDriver()); - - std::string name = configuration()->currentAudioApi(); - if (name == "ASIO") { + names.push_back("WASAPI"); #ifdef MUSE_MODULE_AUDIO_ASIO - m_audioDriver = std::shared_ptr(new AsioAudioDriver()); -#else - LOGW() << "ASIO is selected but is not available, WASAPI will be used"; - m_audioDriver = std::shared_ptr(new WasapiAudioDriver2()); + names.push_back("ASIO"); #endif - } else { - m_audioDriver = std::shared_ptr(new WasapiAudioDriver2()); + + return names; +#endif // Q_OS_WIN + +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) + names.push_back("ALSA"); + if (qEnvironmentVariableIsSet("MUSESCORE_FORCE_ALSA")) { + return names; } +#ifdef MUSE_MODULE_AUDIO_PIPEWIRE + names.push_back("PipeWire"); #endif + return names; +#endif // Q_OS_LINUX + #ifdef Q_OS_MACOS - m_audioDriver = std::shared_ptr(new OSXAudioDriver()); + names.push_back("CoreAudio"); + return names; #endif #ifdef Q_OS_WASM - m_audioDriver = std::shared_ptr(new WebAudioDriver()); + names.push_back("WASM"); + return names; #endif - -#endif // MUSE_MODULE_AUDIO_JACK } -std::string AudioDriverController::currentAudioApi() const +void AudioDriverController::init() { - return configuration()->currentAudioApi(); + std::string name = configuration()->currentAudioApi(); + m_audioDriver = createDriver(name); + m_audioDriver->init(); + subscribeOnDriver(); + LOGI() << "Used " << m_audioDriver->name() << " audio driver"; } -void AudioDriverController::setCurrentAudioApi(const std::string& name) +void AudioDriverController::changeAudioDriver(const std::string& name) { + IAudioDriver::Spec activeSpec; + if (m_audioDriver && m_audioDriver->isOpened()) { + activeSpec = m_audioDriver->activeSpec(); + m_audioDriver->availableOutputDevicesChanged().disconnect(this); + m_audioDriver->close(); + } + + m_audioDriver = createDriver(name); + m_audioDriver->init(); + subscribeOnDriver(); + LOGI() << "Used " << m_audioDriver->name() << " audio driver"; + + if (activeSpec.isValid()) { + m_audioDriver->open(activeSpec, &activeSpec); + } + configuration()->setCurrentAudioApi(name); + m_audioDriverChanged.notify(); } -muse::async::Notification AudioDriverController::currentAudioApiChanged() const +void AudioDriverController::subscribeOnDriver() { - return configuration()->currentAudioApiChanged(); + IF_ASSERT_FAILED(m_audioDriver) { + return; + } + + m_audioDriver->availableOutputDevicesChanged().onNotify(this, [this]() { + LOGI() << "Available output devices changed, checking connection..."; + checkOutputDevice(); + }); } -std::vector AudioDriverController::availableAudioApiList() const +void AudioDriverController::selectOutputDevice(const std::string& deviceId) { -#if defined(Q_OS_WIN) - std::vector names { - "WASAPI" - }; + LOGI() << "Trying to change output device: " << deviceId; + bool ok = audioDriver()->selectOutputDevice(deviceId); + if (ok) { + configuration()->setAudioOutputDeviceId(deviceId); + updateOutputSpec(); + } +} -#ifdef MUSE_MODULE_AUDIO_ASIO - names.push_back("ASIO"); -#endif +void AudioDriverController::checkOutputDevice() +{ + AudioDeviceID preferredDeviceId = configuration()->audioOutputDeviceId(); + //! NOTE If the driver cannot open with the selected device, + //! it will open with the default device. + bool deviceChanged = m_audioDriver->selectOutputDevice(preferredDeviceId); + if (deviceChanged) { + updateOutputSpec(); + } +} - return names; -#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - std::vector names { - "ALSA Audio", - "PipeWire", - }; +void AudioDriverController::updateOutputSpec() +{ + if (!m_audioDriver->isOpened()) { + return; + } - return names; -#else - return {}; -#endif + IAudioDriver::Spec activeSpec = m_audioDriver->activeSpec(); + rpcChannel()->send(rpc::make_request(Method::SetOutputSpec, RpcPacker::pack(activeSpec.output))); +} + +void AudioDriverController::changeBufferSize(samples_t samples) +{ + IF_ASSERT_FAILED(m_audioDriver) { + return; + } + + LOGI() << "Trying to change buffer size: " << samples; + bool ok = m_audioDriver->setOutputDeviceBufferSize(samples); + if (ok) { + configuration()->setDriverBufferSize(samples); + updateOutputSpec(); + } +} + +void AudioDriverController::changeSampleRate(sample_rate_t sampleRate) +{ + IF_ASSERT_FAILED(m_audioDriver) { + return; + } + + LOGI() << "Trying to change sample rate: " << sampleRate; + + bool ok = m_audioDriver->setOutputDeviceSampleRate(sampleRate); + if (ok) { + configuration()->setSampleRate(sampleRate); + updateOutputSpec(); + } +} + +std::string AudioDriverController::currentAudioApi() const +{ + return configuration()->currentAudioApi(); } IAudioDriverPtr AudioDriverController::audioDriver() const @@ -160,3 +269,8 @@ IAudioDriverPtr AudioDriverController::audioDriver() const DO_ASSERT(m_audioDriver); return m_audioDriver; } + +muse::async::Notification AudioDriverController::audioDriverChanged() const +{ + return configuration()->currentAudioApiChanged(); +} diff --git a/src/framework/audio/main/internal/audiodrivercontroller.h b/src/framework/audio/main/internal/audiodrivercontroller.h index fc0d24ac56ad8..897292880e938 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.h +++ b/src/framework/audio/main/internal/audiodrivercontroller.h @@ -22,15 +22,19 @@ #pragma once +#include "global/async/asyncable.h" + #include "audio/iaudiodrivercontroller.h" -#include "modularity/ioc.h" +#include "modularity/ioc.h" #include "audio/main/iaudioconfiguration.h" +#include "audio/common/rpc/irpcchannel.h" namespace muse::audio { -class AudioDriverController : public IAudioDriverController, public Injectable +class AudioDriverController : public IAudioDriverController, public Injectable, public async::Asyncable { Inject configuration = { this }; + Inject rpcChannel = { this }; public: AudioDriverController(const modularity::ContextPtr& iocCtx) @@ -39,14 +43,24 @@ class AudioDriverController : public IAudioDriverController, public Injectable void init(); std::string currentAudioApi() const override; - void setCurrentAudioApi(const std::string& name) override; - async::Notification currentAudioApiChanged() const override; - std::vector availableAudioApiList() const override; IAudioDriverPtr audioDriver() const override; + void changeAudioDriver(const std::string& name) override; + async::Notification audioDriverChanged() const override; + + void selectOutputDevice(const std::string& deviceId) override; + void changeBufferSize(samples_t samples) override; + void changeSampleRate(sample_rate_t sampleRate) override; private: + IAudioDriverPtr createDriver(const std::string& name) const; + void subscribeOnDriver(); + + void checkOutputDevice(); + void updateOutputSpec(); + IAudioDriverPtr m_audioDriver; + async::Notification m_audioDriverChanged; }; } diff --git a/src/framework/audio/main/internal/audiooutputdevicecontroller.cpp b/src/framework/audio/main/internal/audiooutputdevicecontroller.cpp deleted file mode 100644 index 38ba8663eb74c..0000000000000 --- a/src/framework/audio/main/internal/audiooutputdevicecontroller.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2025 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "audiooutputdevicecontroller.h" - -#include "audio/common/rpc/rpcpacker.h" - -#include "log.h" - -using namespace muse::audio; -using namespace muse::audio::rpc; - -void AudioOutputDeviceController::init() -{ - audioDriver()->availableOutputDevicesChanged().onNotify(this, [this]() { - LOGI() << "Available output devices changed, checking connection..."; - changeOutputDevice(); - }); - - configuration()->audioOutputDeviceIdChanged().onNotify(this, [this]() { - AudioDeviceID deviceId = configuration()->audioOutputDeviceId(); - LOGI() << "Trying to change output device: " << deviceId; - - bool ok = audioDriver()->selectOutputDevice(deviceId); - if (ok) { - onOutputDeviceChanged(); - } - }); - - configuration()->driverBufferSizeChanged().onNotify(this, [this]() { - uint16_t bufferSize = configuration()->driverBufferSize(); - LOGI() << "Trying to change buffer size: " << bufferSize; - - bool ok = audioDriver()->setOutputDeviceBufferSize(bufferSize); - if (ok) { - rpcChannel()->send(rpc::make_request(Method::SetOutputSpec, RpcPacker::pack(audioDriver()->activeSpec().output))); - } - }); - - configuration()->sampleRateChanged().onNotify(this, [this]() { - sample_rate_t sampleRate = configuration()->sampleRate(); - LOGI() << "Trying to change sample rate: " << sampleRate; - - bool ok = audioDriver()->setOutputDeviceSampleRate(sampleRate); - if (ok) { - rpcChannel()->send(rpc::make_request(Method::SetOutputSpec, RpcPacker::pack(audioDriver()->activeSpec().output))); - } - }); -} - -void AudioOutputDeviceController::changeOutputDevice() -{ - AudioDeviceID preferredDeviceId = configuration()->audioOutputDeviceId(); - //! NOTE If the driver cannot open with the selected device, - //! it will open with the default device. - bool deviceChanged = audioDriver()->selectOutputDevice(preferredDeviceId); - if (deviceChanged) { - onOutputDeviceChanged(); - } -} - -void AudioOutputDeviceController::onOutputDeviceChanged() -{ - if (!audioDriver()->isOpened()) { - return; - } - - IAudioDriver::Spec activeSpec = audioDriver()->activeSpec(); - rpcChannel()->send(rpc::make_request(Method::SetOutputSpec, RpcPacker::pack(activeSpec.output))); -} - -IAudioDriverPtr AudioOutputDeviceController::audioDriver() const -{ - return audioDriverController()->audioDriver(); -} diff --git a/src/framework/audio/main/internal/audiooutputdevicecontroller.h b/src/framework/audio/main/internal/audiooutputdevicecontroller.h deleted file mode 100644 index 317a3f97953f0..0000000000000 --- a/src/framework/audio/main/internal/audiooutputdevicecontroller.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2025 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "global/async/asyncable.h" - -#include "global/modularity/ioc.h" -#include "audio/main/iaudioconfiguration.h" -#include "audio/iaudiodrivercontroller.h" -#include "audio/common/rpc/irpcchannel.h" - -namespace muse::audio { -class AudioOutputDeviceController : public Injectable, public async::Asyncable -{ - Inject configuration = { this }; - Inject audioDriverController = { this }; - Inject rpcChannel = { this }; - -public: - - AudioOutputDeviceController(const modularity::ContextPtr& iocCtx) - : Injectable(iocCtx) {} - - void init(); - -private: - void changeOutputDevice(); - void onOutputDeviceChanged(); - - IAudioDriverPtr audioDriver() const; -}; -} diff --git a/src/framework/audio/main/internal/startaudiocontroller.cpp b/src/framework/audio/main/internal/startaudiocontroller.cpp index 46d723f2809d2..6b42aca30dcf7 100644 --- a/src/framework/audio/main/internal/startaudiocontroller.cpp +++ b/src/framework/audio/main/internal/startaudiocontroller.cpp @@ -172,10 +172,19 @@ void StartAudioController::startAudioProcessing(const IApplication::RunMode& mod #ifndef Q_OS_WASM m_requiredSamplesTotal = requiredSpec.output.samplesPerChannel * requiredSpec.output.audioChannelCount; - audioDriver()->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) { - m_requiredSamplesTotal = spec.output.samplesPerChannel * spec.output.audioChannelCount; + + auto subscribeOnActiveSpecChanged = [this]() { + audioDriver()->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) { + m_requiredSamplesTotal = spec.output.samplesPerChannel * spec.output.audioChannelCount; + }); + }; + + audioDriverController()->audioDriverChanged().onNotify(this, [subscribeOnActiveSpecChanged](){ + subscribeOnActiveSpecChanged(); }); + subscribeOnActiveSpecChanged(); + bool shouldMeasureInputLag = configuration()->shouldMeasureInputLag(); requiredSpec.callback = [this, shouldMeasureInputLag] (void* /*userdata*/, uint8_t* stream, int byteCount) { @@ -207,8 +216,8 @@ void StartAudioController::startAudioProcessing(const IApplication::RunMode& mod if (!m_alignmentBuffer || m_alignmentBuffer->capacity() != capacity) { m_alignmentBuffer = std::make_shared(capacity); } - alignbuf = m_alignmentBuffer.get(); // minor optimization and easier debugging - static thread_local std::vector proc_buf; // temp buffer + alignbuf = m_alignmentBuffer.get(); // minor optimization and easier debugging + static thread_local std::vector proc_buf; // temp buffer if (proc_buf.size() != requiredSamplesTotal) { proc_buf.resize(requiredSamplesTotal); } diff --git a/src/framework/cmake/MuseDeclareOptions.cmake b/src/framework/cmake/MuseDeclareOptions.cmake index 88c94b2b57bf6..41369d2547227 100644 --- a/src/framework/cmake/MuseDeclareOptions.cmake +++ b/src/framework/cmake/MuseDeclareOptions.cmake @@ -13,13 +13,21 @@ declare_muse_module_opt(ACTIONS ON) declare_muse_module_opt(AUDIO ON) option(MUSE_MODULE_AUDIO_JACK "Enable jack support" OFF) -option(MUSE_MODULE_AUDIO_ASIO "Enable asio support" ON) + +if (OS_IS_WIN) + option(MUSE_MODULE_AUDIO_ASIO "Enable asio support" ON) +endif() + +if (OS_IS_LIN OR OS_IS_FBSD) + option(MUSE_MODULE_AUDIO_PIPEWIRE "Use PipeWire audio driver" OFF) # Turns ON on CI +endif() + option(MUSE_MODULE_AUDIO_EXPORT "Enable audio export" ON) # 1 - worker # 2 - driver callback # 3 - worker - RPC, driver callback - process -set(MUSE_MODULE_AUDIO_WORKMODE 1 CACHE STRING "Audio subsystem work mode") +set(MUSE_MODULE_AUDIO_WORKMODE 1 CACHE INT "Audio subsystem work mode") declare_muse_module_opt(AUDIOPLUGINS ON) diff --git a/src/framework/cmake/muse_framework_config.h.in b/src/framework/cmake/muse_framework_config.h.in index 4b0ec2bf727c9..2cb564ce6934e 100644 --- a/src/framework/cmake/muse_framework_config.h.in +++ b/src/framework/cmake/muse_framework_config.h.in @@ -56,8 +56,9 @@ #cmakedefine MUSE_MODULE_AUDIO 1 #cmakedefine MUSE_MODULE_AUDIO_TESTS 1 #cmakedefine MUSE_MODULE_AUDIO_API 1 -#cmakedefine MUSE_MODULE_AUDIO_JACK 1 #cmakedefine MUSE_MODULE_AUDIO_ASIO 1 +#cmakedefine MUSE_MODULE_AUDIO_PIPEWIRE 1 +#cmakedefine MUSE_MODULE_AUDIO_JACK 1 #cmakedefine MUSE_MODULE_AUDIO_EXPORT 1 #define MUSE_MODULE_AUDIO_WORKER_MODE 1 @@ -155,4 +156,3 @@ #cmakedefine MUSE_MODULE_WORKSPACE_API 1 #cmakedefine MUSE_ENABLE_UNIT_TESTS 1 - diff --git a/src/framework/stubs/audio/audiodrivercontrollerstub.cpp b/src/framework/stubs/audio/audiodrivercontrollerstub.cpp index 13a2998b28964..b8487688c9291 100644 --- a/src/framework/stubs/audio/audiodrivercontrollerstub.cpp +++ b/src/framework/stubs/audio/audiodrivercontrollerstub.cpp @@ -31,11 +31,20 @@ std::string AudioDriverControllerStub::currentAudioApi() const return {}; } -void AudioDriverControllerStub::setCurrentAudioApi(const std::string&) +IAudioDriverPtr AudioDriverControllerStub::audioDriver() const +{ + if (!m_audioDriver) { + m_audioDriver = std::make_shared(); + } + + return m_audioDriver; +} + +void AudioDriverControllerStub::changeAudioDriver(const std::string&) { } -muse::async::Notification AudioDriverControllerStub::currentAudioApiChanged() const +muse::async::Notification AudioDriverControllerStub::audioDriverChanged() const { return {}; } @@ -45,11 +54,14 @@ std::vector AudioDriverControllerStub::availableAudioApiList() cons return {}; } -IAudioDriverPtr AudioDriverControllerStub::audioDriver() const +void AudioDriverControllerStub::selectOutputDevice(const std::string&) { - if (!m_audioDriver) { - m_audioDriver = std::make_shared(); - } +} - return m_audioDriver; +void AudioDriverControllerStub::changeBufferSize(samples_t) +{ +} + +void AudioDriverControllerStub::changeSampleRate(sample_rate_t) +{ } diff --git a/src/framework/stubs/audio/audiodrivercontrollerstub.h b/src/framework/stubs/audio/audiodrivercontrollerstub.h index 6fe3417d66e30..9fbcbbc7219e9 100644 --- a/src/framework/stubs/audio/audiodrivercontrollerstub.h +++ b/src/framework/stubs/audio/audiodrivercontrollerstub.h @@ -28,13 +28,17 @@ namespace muse::audio { class AudioDriverControllerStub : public IAudioDriverController { public: + std::string currentAudioApi() const override; - void setCurrentAudioApi(const std::string& name) override; - async::Notification currentAudioApiChanged() const override; + IAudioDriverPtr audioDriver() const override; + void changeAudioDriver(const std::string& name) override; + async::Notification audioDriverChanged() const override; std::vector availableAudioApiList() const override; - IAudioDriverPtr audioDriver() const override; + void selectOutputDevice(const std::string& deviceId) override; + void changeBufferSize(samples_t samples) override; + void changeSampleRate(sample_rate_t sampleRate) override; private: mutable IAudioDriverPtr m_audioDriver; diff --git a/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp b/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp index 9d3fc817b5cb9..0e389602ca192 100644 --- a/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp +++ b/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp @@ -50,7 +50,7 @@ void AudioMidiPreferencesModel::setCurrentAudioApiIndex(int index) return; } - audioDriverController()->setCurrentAudioApi(apiList.at(index)); + audioDriverController()->changeAudioDriver(apiList.at(index)); emit currentAudioApiIndexChanged(index); } @@ -108,7 +108,7 @@ void AudioMidiPreferencesModel::init() emit onlineSoundsShowProgressBarModeChanged(); }); - audioDriverController()->currentAudioApiChanged().onNotify(this, [this]() { + audioDriverController()->audioDriverChanged().onNotify(this, [this]() { emit currentAudioApiIndexChanged(currentAudioApiIndex()); }); diff --git a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp index ff6a5f797e35d..7b40bbeb98bc7 100644 --- a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp +++ b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp @@ -34,24 +34,39 @@ CommonAudioApiConfigurationModel::CommonAudioApiConfigurationModel(QObject* pare void CommonAudioApiConfigurationModel::load() { - audioDriver()->availableOutputDevicesChanged().onNotify(this, [this]() { + auto subscribeOnDriver = [this]() { + audioDriver()->availableOutputDevicesChanged().onNotify(this, [this]() { + emit deviceListChanged(); + emit currentDeviceIdChanged(); + }); + + audioDriver()->outputDeviceChanged().onNotify(this, [this]() { + emit currentDeviceIdChanged(); + emit sampleRateChanged(); + emit bufferSizeListChanged(); + emit bufferSizeChanged(); + }); + + audioDriver()->outputDeviceSampleRateChanged().onNotify(this, [this]() { + emit sampleRateChanged(); + }); + + audioDriver()->outputDeviceBufferSizeChanged().onNotify(this, [this]() { + emit bufferSizeChanged(); + }); + }; + + audioDriverController()->audioDriverChanged().onNotify(this, [this, subscribeOnDriver]() { emit deviceListChanged(); - }); - - audioDriver()->outputDeviceChanged().onNotify(this, [this]() { emit currentDeviceIdChanged(); emit sampleRateChanged(); emit bufferSizeListChanged(); emit bufferSizeChanged(); - }); - audioDriver()->outputDeviceSampleRateChanged().onNotify(this, [this]() { - emit sampleRateChanged(); + subscribeOnDriver(); }); - audioDriver()->outputDeviceBufferSizeChanged().onNotify(this, [this]() { - emit bufferSizeChanged(); - }); + subscribeOnDriver(); } QString CommonAudioApiConfigurationModel::currentDeviceId() const @@ -77,7 +92,7 @@ QVariantList CommonAudioApiConfigurationModel::deviceList() const void CommonAudioApiConfigurationModel::deviceSelected(const QString& deviceId) { - audioConfiguration()->setAudioOutputDeviceId(deviceId.toStdString()); + audioDriverController()->selectOutputDevice(deviceId.toStdString()); } unsigned int CommonAudioApiConfigurationModel::bufferSize() const @@ -100,7 +115,7 @@ QList CommonAudioApiConfigurationModel::bufferSizeList() const void CommonAudioApiConfigurationModel::bufferSizeSelected(const QString& bufferSizeStr) { - audioConfiguration()->setDriverBufferSize(bufferSizeStr.toInt()); + audioDriverController()->changeBufferSize(bufferSizeStr.toInt()); } unsigned int CommonAudioApiConfigurationModel::sampleRate() const @@ -122,7 +137,7 @@ QList CommonAudioApiConfigurationModel::sampleRateList() const void CommonAudioApiConfigurationModel::sampleRateSelected(const QString& sampleRateStr) { - audioConfiguration()->setSampleRate(sampleRateStr.toInt()); + audioDriverController()->changeSampleRate(sampleRateStr.toInt()); } muse::audio::IAudioDriverPtr CommonAudioApiConfigurationModel::audioDriver() const diff --git a/src/preferences/qml/MuseScore/Preferences/internal/AudioApiSection.qml b/src/preferences/qml/MuseScore/Preferences/internal/AudioApiSection.qml index b6e81b7d33c1a..12d7524c19d47 100644 --- a/src/preferences/qml/MuseScore/Preferences/internal/AudioApiSection.qml +++ b/src/preferences/qml/MuseScore/Preferences/internal/AudioApiSection.qml @@ -39,8 +39,6 @@ BaseSection { ComboBoxWithTitle { id: apiComboBox - property int initialIndex: -1 - title: qsTrc("preferences", "Audio API") columnWidth: root.columnWidth @@ -56,25 +54,6 @@ BaseSection { onValueEdited: function(newIndex, newValue) { root.currentAudioApiIndexChangeRequested(newIndex) } - - onCurrentIndexChanged: { - if (apiComboBox.initialIndex !== -1) { - restartRequiredLabel.visible = apiComboBox.currentIndex !== apiComboBox.initialIndex - } - } - - Component.onCompleted: { - apiComboBox.initialIndex = apiComboBox.currentIndex - } - } - - StyledTextLabel { - id: restartRequiredLabel - - anchors.verticalCenter: parent.verticalCenter - - text: qsTrc("preferences", "Restart required") - visible: false } } From ada7a55120084a7fffc4cc26fd570767a10b4430 Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Thu, 13 Nov 2025 14:01:41 +0300 Subject: [PATCH 2/7] [asio] added reset --- .../platform/win/asio/asioaudiodriver.cpp | 53 ++++++++++++++----- .../platform/win/asio/asioaudiodriver.h | 5 +- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp index 407b02beaae2c..88d9d5c405d6c 100644 --- a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp +++ b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp @@ -21,6 +21,8 @@ */ #include "asioaudiodriver.h" +#include "global/async/notification.h" + #undef UNICODE #include "ASIOSDK/common/asiosys.h" #include "ASIOSDK/common/asio.h" @@ -32,6 +34,7 @@ using namespace muse; using namespace muse::audio; struct AsioData { + // Drivers list AsioDrivers drivers; // ASIOInit() @@ -64,6 +67,9 @@ struct AsioData { // ASIOCallbacks ASIOCallbacks callbacks; + + // Reset + async::Notification resetRequest; }; static AsioData s_adata; @@ -267,28 +273,29 @@ static ASIOTime* s_bufferSwitchTimeInfo(ASIOTime* /*params*/, long index, ASIOBo return nullptr; } +static void s_resetRequest() +{ + s_adata.resetRequest.notify(); +} + static void s_sampleRateChanged(ASIOSampleRate rate) { LOGI() << "rate: " << rate; + s_resetRequest(); } -static long s_asioMessages(long selector, long value, void* message, double* /*opt*/) +static long s_asioMessages(long selector, long value, void* /*message*/, double* /*opt*/) { LOGI() << "selector: " << selector - << "value: " << value - << "message: " << (const char*)message; + << " value: " << value; long ret = 0; switch (selector) { case kAsioSelectorSupported: if (value == kAsioEngineVersion - // || value == kAsioResetRequest - // || value == kAsioResyncRequest - // || value == kAsioLatenciesChanged - // // the following three were added for ASIO 2.0, you don't necessarily have to support them - // || value == kAsioSupportsTimeInfo - // || value == kAsioSupportsTimeCode - // || value == kAsioSupportsInputMonitor + || value == kAsioResetRequest + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged ) { ret = 1L; } @@ -298,8 +305,12 @@ static long s_asioMessages(long selector, long value, void* message, double* /*o // You cannot reset the driver right now, as this code is called from the driver. // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction // Afterwards you initialize the driver again. - ret = 0L; + s_resetRequest(); + ret = 1L; break; + case kAsioBufferSizeChange: + s_resetRequest(); + ret = 1L; case kAsioResyncRequest: // This informs the application, that the driver encountered some non fatal data loss. // It is used for synchronization purposes of different media. @@ -307,13 +318,15 @@ static long s_asioMessages(long selector, long value, void* message, double* /*o // Windows Multimedia system, which could loose data because the Mutex was hold too long // by another thread. // However a driver can issue it in other situations, too. + s_resetRequest(); ret = 1L; break; case kAsioLatenciesChanged: // This will inform the host application that the drivers were latencies changed. // Beware, it this does not mean that the buffer sizes have changed! // You might need to update internal delay data. - ret = 0L; + s_resetRequest(); + ret = 1L; break; case kAsioEngineVersion: // return the supported ASIO version of the host application @@ -489,6 +502,10 @@ bool AsioAudioDriver::doOpen(const AudioDeviceID& device, const Spec& spec, Spec return ok; } + s_adata.resetRequest.onNotify(this, [this]() { + reset(); + }, async::Asyncable::Mode::SetReplace); + m_running = true; m_thread = std::thread([this]() { bool ok = ASIOStart() == ASE_OK; @@ -532,6 +549,18 @@ void AsioAudioDriver::close() s_adata.drivers.removeCurrentDriver(); } +void AsioAudioDriver::reset() +{ + LOGI() << "! driver reset called"; + if (!isOpened()) { + return; + } + + Spec spec = s_adata.activeSpec; + close(); + open(spec, nullptr); +} + bool AsioAudioDriver::isOpened() const { return m_running; diff --git a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h index 1b095aa3ed6ec..bb3974d28f0a6 100644 --- a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h +++ b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h @@ -26,8 +26,10 @@ #include "../../../../iaudiodriver.h" +#include "global/async/asyncable.h" + namespace muse::audio { -class AsioAudioDriver : public IAudioDriver +class AsioAudioDriver : public IAudioDriver, public async::Asyncable { public: AsioAudioDriver(); @@ -63,6 +65,7 @@ class AsioAudioDriver : public IAudioDriver private: bool doOpen(const AudioDeviceID& device, const Spec& spec, Spec* activeSpec); + void reset(); std::thread m_thread; std::atomic m_running = false; From dda33008c2bfcaa925771d7a98edef18abe5b915 Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Mon, 17 Nov 2025 09:30:06 +0300 Subject: [PATCH 3/7] now the driver controller is used everywhere, instead of the driver directlyy --- src/framework/audio/common/audiotypes.h | 1 + .../platform/win/asio/asioaudiodriver.cpp | 9 +- src/framework/audio/iaudiodrivercontroller.h | 29 ++- .../audio/main/iaudioconfiguration.h | 1 + .../main/internal/audioconfiguration.cpp | 5 + .../audio/main/internal/audioconfiguration.h | 1 + .../main/internal/audiodrivercontroller.cpp | 221 +++++++++++++++--- .../main/internal/audiodrivercontroller.h | 38 ++- .../main/internal/startaudiocontroller.cpp | 28 +-- .../main/internal/startaudiocontroller.h | 1 - .../Preferences/audiomidipreferencesmodel.cpp | 4 +- .../commonaudioapiconfigurationmodel.cpp | 69 +++--- .../commonaudioapiconfigurationmodel.h | 5 +- 13 files changed, 299 insertions(+), 113 deletions(-) diff --git a/src/framework/audio/common/audiotypes.h b/src/framework/audio/common/audiotypes.h index 2ca1cbf84045f..0c9d9a4534175 100644 --- a/src/framework/audio/common/audiotypes.h +++ b/src/framework/audio/common/audiotypes.h @@ -80,6 +80,7 @@ static constexpr samples_t MINIMUM_BUFFER_SIZE = 128; #endif static constexpr samples_t MAXIMUM_BUFFER_SIZE = 4096; +static constexpr samples_t DEFAULT_BUFFER_SIZE = 1024; struct OutputSpec { sample_rate_t sampleRate = 0; diff --git a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp index 88d9d5c405d6c..1c074d779f997 100644 --- a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp +++ b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp @@ -412,6 +412,7 @@ bool AsioAudioDriver::open(const Spec& spec, Spec* activeSpec) bool AsioAudioDriver::doOpen(const AudioDeviceID& device, const Spec& spec, Spec* activeSpec) { + LOGI() << "try open: " << device; const char* name = device.c_str(); bool ok = s_adata.drivers.loadDriver(const_cast(name)); if (!ok) { @@ -427,7 +428,7 @@ bool AsioAudioDriver::doOpen(const AudioDeviceID& device, const Spec& spec, Spec LOGI() << "asioVersion: " << s_adata.driverInfo.asioVersion << " driverVersion: " << s_adata.driverInfo.driverVersion - << " name: " << s_adata.driverInfo.name; + << " driverName: " << s_adata.driverInfo.name; // Get device metrics AsioData::DeviceMetrics& metrics = s_adata.deviceMetrics; @@ -534,7 +535,9 @@ bool AsioAudioDriver::doOpen(const AudioDeviceID& device, const Spec& spec, Spec void AsioAudioDriver::close() { m_running = false; - m_thread.join(); + if (m_thread.joinable()) { + m_thread.join(); + } ASIODisposeBuffers(); @@ -676,7 +679,7 @@ bool AsioAudioDriver::selectOutputDevice(const AudioDeviceID& id) //! NOTE We are trying to open a new device with the default value; //! it is not known what it was before. - spec.output.samplesPerChannel = 1024; + spec.output.samplesPerChannel = DEFAULT_BUFFER_SIZE; result = open(spec, nullptr); } diff --git a/src/framework/audio/iaudiodrivercontroller.h b/src/framework/audio/iaudiodrivercontroller.h index b52a98170b646..aa6c2aac3cd29 100644 --- a/src/framework/audio/iaudiodrivercontroller.h +++ b/src/framework/audio/iaudiodrivercontroller.h @@ -34,15 +34,34 @@ class IAudioDriverController : MODULE_EXPORT_INTERFACE public: virtual ~IAudioDriverController() = default; + // Api + virtual std::vector availableAudioApiList() const = 0; + virtual std::string currentAudioApi() const = 0; - virtual IAudioDriverPtr audioDriver() const = 0; - virtual void changeAudioDriver(const std::string& name) = 0; - virtual async::Notification audioDriverChanged() const = 0; + virtual void changeCurrentAudioApi(const std::string& name) = 0; + virtual async::Notification currentAudioApiChanged() const = 0; - virtual std::vector availableAudioApiList() const = 0; + // Current driver operation + virtual AudioDeviceList availableOutputDevices() const = 0; + virtual async::Notification availableOutputDevicesChanged() const = 0; + + virtual bool open(const IAudioDriver::Spec& spec, IAudioDriver::Spec* activeSpec) = 0; + virtual void close() = 0; + virtual bool isOpened() const = 0; - virtual void selectOutputDevice(const std::string& deviceId) = 0; + virtual const IAudioDriver::Spec& activeSpec() const = 0; + virtual async::Channel activeSpecChanged() const = 0; + + virtual AudioDeviceID outputDevice() const = 0; + virtual bool selectOutputDevice(const std::string& deviceId) = 0; + virtual async::Notification outputDeviceChanged() const = 0; + + virtual std::vector availableOutputDeviceBufferSizes() const = 0; virtual void changeBufferSize(samples_t samples) = 0; + virtual async::Notification outputDeviceBufferSizeChanged() const = 0; + + virtual std::vector availableOutputDeviceSampleRates() const = 0; virtual void changeSampleRate(sample_rate_t sampleRate) = 0; + virtual async::Notification outputDeviceSampleRateChanged() const = 0; }; } diff --git a/src/framework/audio/main/iaudioconfiguration.h b/src/framework/audio/main/iaudioconfiguration.h index 30db324d6af12..2362adff94de4 100644 --- a/src/framework/audio/main/iaudioconfiguration.h +++ b/src/framework/audio/main/iaudioconfiguration.h @@ -39,6 +39,7 @@ class IAudioConfiguration : MODULE_EXPORT_INTERFACE virtual AudioEngineConfig engineConfig() const = 0; + virtual std::string defaultAudioApi() const = 0; virtual std::string currentAudioApi() const = 0; virtual void setCurrentAudioApi(const std::string& name) = 0; virtual async::Notification currentAudioApiChanged() const = 0; diff --git a/src/framework/audio/main/internal/audioconfiguration.cpp b/src/framework/audio/main/internal/audioconfiguration.cpp index 56aea6a654775..c890b1cd82308 100644 --- a/src/framework/audio/main/internal/audioconfiguration.cpp +++ b/src/framework/audio/main/internal/audioconfiguration.cpp @@ -108,6 +108,11 @@ void AudioConfiguration::onWorkerConfigChanged() rpcChannel()->send(rpc::make_notification(rpc::Method::EngineConfigChanged, rpc::RpcPacker::pack(engineConfig()))); } +std::string AudioConfiguration::defaultAudioApi() const +{ + return settings()->defaultValue(AUDIO_API_KEY).toString(); +} + std::string AudioConfiguration::currentAudioApi() const { return settings()->value(AUDIO_API_KEY).toString(); diff --git a/src/framework/audio/main/internal/audioconfiguration.h b/src/framework/audio/main/internal/audioconfiguration.h index b3488a4b5837c..0dcf5c67b12fa 100644 --- a/src/framework/audio/main/internal/audioconfiguration.h +++ b/src/framework/audio/main/internal/audioconfiguration.h @@ -45,6 +45,7 @@ class AudioConfiguration : public IAudioConfiguration, public Injectable AudioEngineConfig engineConfig() const override; void onWorkerConfigChanged(); + std::string defaultAudioApi() const override; std::string currentAudioApi() const override; void setCurrentAudioApi(const std::string& name) override; async::Notification currentAudioApiChanged() const override; diff --git a/src/framework/audio/main/internal/audiodrivercontroller.cpp b/src/framework/audio/main/internal/audiodrivercontroller.cpp index 2978f77bcd08e..e471610acb068 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.cpp +++ b/src/framework/audio/main/internal/audiodrivercontroller.cpp @@ -56,6 +56,7 @@ #include "log.h" +using namespace muse; using namespace muse::audio; using namespace muse::audio::rpc; @@ -156,57 +157,203 @@ std::vector AudioDriverController::availableAudioApiList() const #endif } +void AudioDriverController::setNewDriver(IAudioDriverPtr newDriver) +{ + if (m_audioDriver) { + // unsubscribe + m_audioDriver->availableOutputDevicesChanged().disconnect(this); + m_audioDriver->activeSpecChanged().disconnect(this); + m_audioDriver->outputDeviceChanged().disconnect(this); + m_audioDriver->outputDeviceBufferSizeChanged().disconnect(this); + m_audioDriver->outputDeviceSampleRateChanged().disconnect(this); + } + + m_audioDriver = newDriver; + + if (m_audioDriver) { + // subscribe + m_audioDriver->availableOutputDevicesChanged().onNotify(this, [this]() { + m_availableOutputDevicesChanged.notify(); + LOGI() << "Available output devices changed, checking connection..."; + checkOutputDevice(); + }); + + m_audioDriver->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) { + m_activeSpecChanged.send(spec); + }); + + m_audioDriver->outputDeviceChanged().onNotify(this, [this]() { + m_outputDeviceChanged.notify(); + }); + + m_audioDriver->outputDeviceBufferSizeChanged().onNotify(this, [this]() { + m_outputDeviceBufferSizeChanged.notify(); + }); + + m_audioDriver->outputDeviceSampleRateChanged().onNotify(this, [this]() { + m_outputDeviceSampleRateChanged.notify(); + }); + } +} + void AudioDriverController::init() { - std::string name = configuration()->currentAudioApi(); - m_audioDriver = createDriver(name); - m_audioDriver->init(); - subscribeOnDriver(); - LOGI() << "Used " << m_audioDriver->name() << " audio driver"; } -void AudioDriverController::changeAudioDriver(const std::string& name) +std::string AudioDriverController::currentAudioApi() const +{ + return configuration()->currentAudioApi(); +} + +void AudioDriverController::changeCurrentAudioApi(const std::string& name) { IAudioDriver::Spec activeSpec; if (m_audioDriver && m_audioDriver->isOpened()) { activeSpec = m_audioDriver->activeSpec(); - m_audioDriver->availableOutputDevicesChanged().disconnect(this); m_audioDriver->close(); } - m_audioDriver = createDriver(name); - m_audioDriver->init(); - subscribeOnDriver(); + IAudioDriverPtr driver = createDriver(name); + driver->init(); + setNewDriver(driver); + m_audioDriver->resetToDefaultOutputDevice(); LOGI() << "Used " << m_audioDriver->name() << " audio driver"; if (activeSpec.isValid()) { + activeSpec.output.samplesPerChannel = DEFAULT_BUFFER_SIZE; m_audioDriver->open(activeSpec, &activeSpec); } + updateOutputSpec(); + configuration()->setCurrentAudioApi(name); - m_audioDriverChanged.notify(); + configuration()->setAudioOutputDeviceId(m_audioDriver->outputDevice()); + m_currentAudioApiChanged.notify(); } -void AudioDriverController::subscribeOnDriver() +async::Notification AudioDriverController::currentAudioApiChanged() const +{ + return m_currentAudioApiChanged; +} + +AudioDeviceList AudioDriverController::availableOutputDevices() const +{ + IF_ASSERT_FAILED(m_audioDriver) { + return {}; + } + return m_audioDriver->availableOutputDevices(); +} + +async::Notification AudioDriverController::availableOutputDevicesChanged() const +{ + return m_availableOutputDevicesChanged; +} + +bool AudioDriverController::open(const IAudioDriver::Spec& spec, IAudioDriver::Spec* activeSpec) +{ + const std::string currentAudioApi = configuration()->currentAudioApi(); + IAudioDriverPtr driver = createDriver(currentAudioApi); + driver->init(); + setNewDriver(driver); + + const std::string audioOutputDeviceId = configuration()->audioOutputDeviceId(); + m_audioDriver->selectOutputDevice(audioOutputDeviceId); + + bool ok = m_audioDriver->open(spec, activeSpec); + if (!ok) { + m_audioDriver->resetToDefaultOutputDevice(); + ok = m_audioDriver->open(spec, activeSpec); + if (ok) { + configuration()->setAudioOutputDeviceId(m_audioDriver->outputDevice()); + } + } + + if (!ok) { + const std::string defaultAudioApi = configuration()->defaultAudioApi(); + if (defaultAudioApi != currentAudioApi) { + IAudioDriverPtr defaultDriver = createDriver(defaultAudioApi); + defaultDriver->init(); + setNewDriver(defaultDriver); + m_audioDriver->resetToDefaultOutputDevice(); + ok = m_audioDriver->open(spec, activeSpec); + if (ok) { + configuration()->setCurrentAudioApi(defaultAudioApi); + configuration()->setAudioOutputDeviceId(m_audioDriver->outputDevice()); + } + } + } + + LOGI() << "Used audio driver: " << m_audioDriver->name() + << ", opened: " << (ok ? "success" : "failed") + << ", device: " << m_audioDriver->outputDevice(); + + updateOutputSpec(); + + return ok; +} + +void AudioDriverController::close() { IF_ASSERT_FAILED(m_audioDriver) { return; } + m_audioDriver->close(); +} + +bool AudioDriverController::isOpened() const +{ + IF_ASSERT_FAILED(m_audioDriver) { + return false; + } + return m_audioDriver->isOpened(); +} + +const IAudioDriver::Spec& AudioDriverController::activeSpec() const +{ + IF_ASSERT_FAILED(m_audioDriver) { + static IAudioDriver::Spec dummy; + return dummy; + } + return m_audioDriver->activeSpec(); +} + +async::Channel AudioDriverController::activeSpecChanged() const +{ + return m_activeSpecChanged; +} - m_audioDriver->availableOutputDevicesChanged().onNotify(this, [this]() { - LOGI() << "Available output devices changed, checking connection..."; - checkOutputDevice(); - }); +AudioDeviceID AudioDriverController::outputDevice() const +{ + IF_ASSERT_FAILED(m_audioDriver) { + return AudioDeviceID(); + } + return m_audioDriver->outputDevice(); } -void AudioDriverController::selectOutputDevice(const std::string& deviceId) +bool AudioDriverController::selectOutputDevice(const std::string& deviceId) { - LOGI() << "Trying to change output device: " << deviceId; - bool ok = audioDriver()->selectOutputDevice(deviceId); + IF_ASSERT_FAILED(m_audioDriver) { + return false; + } + + const AudioDeviceID oldDeviceId = m_audioDriver->outputDevice(); + LOGI() << "Trying to change output device" + << " from: " << oldDeviceId + << ", to: " << deviceId; + bool ok = m_audioDriver->selectOutputDevice(deviceId); if (ok) { configuration()->setAudioOutputDeviceId(deviceId); updateOutputSpec(); + } else { + LOGE() << "failed select device, return to old: " << oldDeviceId; + m_audioDriver->selectOutputDevice(oldDeviceId); } + return ok; +} + +async::Notification AudioDriverController::outputDeviceChanged() const +{ + return m_outputDeviceChanged; } void AudioDriverController::checkOutputDevice() @@ -230,6 +377,14 @@ void AudioDriverController::updateOutputSpec() rpcChannel()->send(rpc::make_request(Method::SetOutputSpec, RpcPacker::pack(activeSpec.output))); } +std::vector AudioDriverController::availableOutputDeviceBufferSizes() const +{ + IF_ASSERT_FAILED(m_audioDriver) { + return {}; + } + return m_audioDriver->availableOutputDeviceBufferSizes(); +} + void AudioDriverController::changeBufferSize(samples_t samples) { IF_ASSERT_FAILED(m_audioDriver) { @@ -244,6 +399,19 @@ void AudioDriverController::changeBufferSize(samples_t samples) } } +async::Notification AudioDriverController::outputDeviceBufferSizeChanged() const +{ + return m_outputDeviceBufferSizeChanged; +} + +std::vector AudioDriverController::availableOutputDeviceSampleRates() const +{ + IF_ASSERT_FAILED(m_audioDriver) { + return {}; + } + return m_audioDriver->availableOutputDeviceSampleRates(); +} + void AudioDriverController::changeSampleRate(sample_rate_t sampleRate) { IF_ASSERT_FAILED(m_audioDriver) { @@ -259,18 +427,7 @@ void AudioDriverController::changeSampleRate(sample_rate_t sampleRate) } } -std::string AudioDriverController::currentAudioApi() const -{ - return configuration()->currentAudioApi(); -} - -IAudioDriverPtr AudioDriverController::audioDriver() const -{ - DO_ASSERT(m_audioDriver); - return m_audioDriver; -} - -muse::async::Notification AudioDriverController::audioDriverChanged() const +async::Notification AudioDriverController::outputDeviceSampleRateChanged() const { - return configuration()->currentAudioApiChanged(); + return m_outputDeviceSampleRateChanged; } diff --git a/src/framework/audio/main/internal/audiodrivercontroller.h b/src/framework/audio/main/internal/audiodrivercontroller.h index 897292880e938..70c00652a707f 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.h +++ b/src/framework/audio/main/internal/audiodrivercontroller.h @@ -42,25 +42,49 @@ class AudioDriverController : public IAudioDriverController, public Injectable, void init(); - std::string currentAudioApi() const override; + // Api std::vector availableAudioApiList() const override; - IAudioDriverPtr audioDriver() const override; - void changeAudioDriver(const std::string& name) override; - async::Notification audioDriverChanged() const override; + std::string currentAudioApi() const override; + void changeCurrentAudioApi(const std::string& name) override; + async::Notification currentAudioApiChanged() const override; + + // Current driver operation + AudioDeviceList availableOutputDevices() const override; + async::Notification availableOutputDevicesChanged() const override; + + bool open(const IAudioDriver::Spec& spec, IAudioDriver::Spec* activeSpec) override; + void close() override; + bool isOpened() const override; - void selectOutputDevice(const std::string& deviceId) override; + const IAudioDriver::Spec& activeSpec() const override; + async::Channel activeSpecChanged() const override; + + AudioDeviceID outputDevice() const override; + bool selectOutputDevice(const std::string& deviceId) override; + async::Notification outputDeviceChanged() const override; + + std::vector availableOutputDeviceBufferSizes() const override; void changeBufferSize(samples_t samples) override; + async::Notification outputDeviceBufferSizeChanged() const override; + + std::vector availableOutputDeviceSampleRates() const override; void changeSampleRate(sample_rate_t sampleRate) override; + async::Notification outputDeviceSampleRateChanged() const override; private: IAudioDriverPtr createDriver(const std::string& name) const; - void subscribeOnDriver(); + void setNewDriver(IAudioDriverPtr newDriver); void checkOutputDevice(); void updateOutputSpec(); IAudioDriverPtr m_audioDriver; - async::Notification m_audioDriverChanged; + async::Notification m_currentAudioApiChanged; + async::Notification m_availableOutputDevicesChanged; + async::Channel m_activeSpecChanged; + async::Notification m_outputDeviceChanged; + async::Notification m_outputDeviceBufferSizeChanged; + async::Notification m_outputDeviceSampleRateChanged; }; } diff --git a/src/framework/audio/main/internal/startaudiocontroller.cpp b/src/framework/audio/main/internal/startaudiocontroller.cpp index 6b42aca30dcf7..0c705f8b65d42 100644 --- a/src/framework/audio/main/internal/startaudiocontroller.cpp +++ b/src/framework/audio/main/internal/startaudiocontroller.cpp @@ -173,18 +173,10 @@ void StartAudioController::startAudioProcessing(const IApplication::RunMode& mod m_requiredSamplesTotal = requiredSpec.output.samplesPerChannel * requiredSpec.output.audioChannelCount; - auto subscribeOnActiveSpecChanged = [this]() { - audioDriver()->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) { - m_requiredSamplesTotal = spec.output.samplesPerChannel * spec.output.audioChannelCount; - }); - }; - - audioDriverController()->audioDriverChanged().onNotify(this, [subscribeOnActiveSpecChanged](){ - subscribeOnActiveSpecChanged(); + audioDriverController()->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) { + m_requiredSamplesTotal = spec.output.samplesPerChannel * spec.output.audioChannelCount; }); - subscribeOnActiveSpecChanged(); - bool shouldMeasureInputLag = configuration()->shouldMeasureInputLag(); requiredSpec.callback = [this, shouldMeasureInputLag] (void* /*userdata*/, uint8_t* stream, int byteCount) { @@ -271,11 +263,7 @@ void StartAudioController::startAudioProcessing(const IApplication::RunMode& mod IAudioDriver::Spec activeSpec; if (mode == IApplication::RunMode::GuiApp) { - audioDriver()->init(); - - audioDriver()->selectOutputDevice(configuration()->audioOutputDeviceId()); - - if (!audioDriver()->open(requiredSpec, &activeSpec)) { + if (!audioDriverController()->open(requiredSpec, &activeSpec)) { return; } } else { @@ -304,17 +292,11 @@ void StartAudioController::stopAudioProcessing() { m_isAudioStarted.set(false); - if (audioDriver()->isOpened()) { - audioDriver()->close(); - } + audioDriverController()->close(); + #ifndef Q_OS_WASM if (m_worker && m_worker->isRunning()) { m_worker->stop(); } #endif } - -IAudioDriverPtr StartAudioController::audioDriver() const -{ - return audioDriverController()->audioDriver(); -} diff --git a/src/framework/audio/main/internal/startaudiocontroller.h b/src/framework/audio/main/internal/startaudiocontroller.h index 917b0862c6143..8005c993ad6b9 100644 --- a/src/framework/audio/main/internal/startaudiocontroller.h +++ b/src/framework/audio/main/internal/startaudiocontroller.h @@ -58,7 +58,6 @@ class StartAudioController : public IStartAudioController, public async::Asyncab void stopAudioProcessing() override; private: - IAudioDriverPtr audioDriver() const; void th_setupEngine(); diff --git a/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp b/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp index 0e389602ca192..e5f0d276dc184 100644 --- a/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp +++ b/src/preferences/qml/MuseScore/Preferences/audiomidipreferencesmodel.cpp @@ -50,7 +50,7 @@ void AudioMidiPreferencesModel::setCurrentAudioApiIndex(int index) return; } - audioDriverController()->changeAudioDriver(apiList.at(index)); + audioDriverController()->changeCurrentAudioApi(apiList.at(index)); emit currentAudioApiIndexChanged(index); } @@ -108,7 +108,7 @@ void AudioMidiPreferencesModel::init() emit onlineSoundsShowProgressBarModeChanged(); }); - audioDriverController()->audioDriverChanged().onNotify(this, [this]() { + audioDriverController()->currentAudioApiChanged().onNotify(this, [this]() { emit currentAudioApiIndexChanged(currentAudioApiIndex()); }); diff --git a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp index 7b40bbeb98bc7..f6ee74bff00b8 100644 --- a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp +++ b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp @@ -22,6 +22,8 @@ #include "commonaudioapiconfigurationmodel.h" +#include "global/translation.h" + #include "audio/common/audiotypes.h" using namespace mu::preferences; @@ -34,51 +36,45 @@ CommonAudioApiConfigurationModel::CommonAudioApiConfigurationModel(QObject* pare void CommonAudioApiConfigurationModel::load() { - auto subscribeOnDriver = [this]() { - audioDriver()->availableOutputDevicesChanged().onNotify(this, [this]() { - emit deviceListChanged(); - emit currentDeviceIdChanged(); - }); - - audioDriver()->outputDeviceChanged().onNotify(this, [this]() { - emit currentDeviceIdChanged(); - emit sampleRateChanged(); - emit bufferSizeListChanged(); - emit bufferSizeChanged(); - }); - - audioDriver()->outputDeviceSampleRateChanged().onNotify(this, [this]() { - emit sampleRateChanged(); - }); - - audioDriver()->outputDeviceBufferSizeChanged().onNotify(this, [this]() { - emit bufferSizeChanged(); - }); - }; - - audioDriverController()->audioDriverChanged().onNotify(this, [this, subscribeOnDriver]() { + audioDriverController()->currentAudioApiChanged().onNotify(this, [this]() { emit deviceListChanged(); emit currentDeviceIdChanged(); emit sampleRateChanged(); emit bufferSizeListChanged(); emit bufferSizeChanged(); + }); + + audioDriverController()->availableOutputDevicesChanged().onNotify(this, [this]() { + emit deviceListChanged(); + emit currentDeviceIdChanged(); + }); - subscribeOnDriver(); + audioDriverController()->outputDeviceChanged().onNotify(this, [this]() { + emit currentDeviceIdChanged(); + emit sampleRateChanged(); + emit bufferSizeListChanged(); + emit bufferSizeChanged(); }); - subscribeOnDriver(); + audioDriverController()->outputDeviceSampleRateChanged().onNotify(this, [this]() { + emit sampleRateChanged(); + }); + + audioDriverController()->outputDeviceBufferSizeChanged().onNotify(this, [this]() { + emit bufferSizeChanged(); + }); } QString CommonAudioApiConfigurationModel::currentDeviceId() const { - return QString::fromStdString(audioDriver()->outputDevice()); + return QString::fromStdString(audioDriverController()->outputDevice()); } QVariantList CommonAudioApiConfigurationModel::deviceList() const { QVariantList result; - AudioDeviceList devices = audioDriver()->availableOutputDevices(); + AudioDeviceList devices = audioDriverController()->availableOutputDevices(); for (const AudioDevice& device : devices) { QVariantMap obj; obj["value"] = QString::fromStdString(device.id); @@ -92,19 +88,23 @@ QVariantList CommonAudioApiConfigurationModel::deviceList() const void CommonAudioApiConfigurationModel::deviceSelected(const QString& deviceId) { - audioDriverController()->selectOutputDevice(deviceId.toStdString()); + bool ok = audioDriverController()->selectOutputDevice(deviceId.toStdString()); + if (!ok) { + interactive()->error("", + muse::trc("appshell/preferences", "The driver for this device could not be opened.")); + } } unsigned int CommonAudioApiConfigurationModel::bufferSize() const { - unsigned int val = audioDriver()->activeSpec().output.samplesPerChannel; + unsigned int val = audioDriverController()->activeSpec().output.samplesPerChannel; return val; } QList CommonAudioApiConfigurationModel::bufferSizeList() const { QList result; - std::vector bufferSizes = audioDriver()->availableOutputDeviceBufferSizes(); + std::vector bufferSizes = audioDriverController()->availableOutputDeviceBufferSizes(); for (unsigned int bufferSize : bufferSizes) { result << bufferSize; @@ -120,13 +120,13 @@ void CommonAudioApiConfigurationModel::bufferSizeSelected(const QString& bufferS unsigned int CommonAudioApiConfigurationModel::sampleRate() const { - return audioDriver()->activeSpec().output.sampleRate; + return audioDriverController()->activeSpec().output.sampleRate; } QList CommonAudioApiConfigurationModel::sampleRateList() const { QList result; - std::vector sampleRates = audioDriver()->availableOutputDeviceSampleRates(); + std::vector sampleRates = audioDriverController()->availableOutputDeviceSampleRates(); for (unsigned int sampleRate : sampleRates) { result << sampleRate; @@ -139,8 +139,3 @@ void CommonAudioApiConfigurationModel::sampleRateSelected(const QString& sampleR { audioDriverController()->changeSampleRate(sampleRateStr.toInt()); } - -muse::audio::IAudioDriverPtr CommonAudioApiConfigurationModel::audioDriver() const -{ - return audioDriverController()->audioDriver(); -} diff --git a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.h b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.h index 9cb672cfebd82..41270b9e7be03 100644 --- a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.h +++ b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.h @@ -31,6 +31,7 @@ #include "audio/main/iaudioconfiguration.h" #include "audio/iaudiodrivercontroller.h" +#include "global/iinteractive.h" namespace mu::preferences { class CommonAudioApiConfigurationModel : public QObject, public muse::Injectable, public muse::async::Asyncable @@ -49,6 +50,7 @@ class CommonAudioApiConfigurationModel : public QObject, public muse::Injectable muse::Inject audioConfiguration = { this }; muse::Inject audioDriverController = { this }; + muse::Inject interactive = { this }; public: explicit CommonAudioApiConfigurationModel(QObject* parent = nullptr); @@ -76,8 +78,5 @@ class CommonAudioApiConfigurationModel : public QObject, public muse::Injectable void bufferSizeChanged(); void bufferSizeListChanged(); - -private: - muse::audio::IAudioDriverPtr audioDriver() const; }; } From 04476b254062294cf4ce42772605c788fbcf24de Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Mon, 17 Nov 2025 14:50:15 +0300 Subject: [PATCH 4/7] moved logic to audio driver controller --- src/framework/audio/driver/audio_driver.cmake | 14 +- .../platform/win/asio/asioaudiodriver.cpp | 130 ++------------- .../platform/win/asio/asioaudiodriver.h | 29 +--- .../platform/win/wasapiaudiodriver2.cpp | 121 +++----------- .../driver/platform/win/wasapiaudiodriver2.h | 25 +-- src/framework/audio/iaudiodriver.h | 35 ++-- src/framework/audio/iaudiodrivercontroller.h | 6 +- src/framework/audio/main/audiomodule.cpp | 1 - .../main/internal/audiodrivercontroller.cpp | 152 +++++++++++------- .../main/internal/audiodrivercontroller.h | 10 +- .../main/internal/startaudiocontroller.cpp | 4 +- .../commonaudioapiconfigurationmodel.cpp | 17 +- 12 files changed, 175 insertions(+), 369 deletions(-) diff --git a/src/framework/audio/driver/audio_driver.cmake b/src/framework/audio/driver/audio_driver.cmake index a14be8ba70e94..258949463dc39 100644 --- a/src/framework/audio/driver/audio_driver.cmake +++ b/src/framework/audio/driver/audio_driver.cmake @@ -27,13 +27,13 @@ if (OS_IS_WIN) #${CMAKE_CURRENT_LIST_DIR}/platform/win/winmmdriver.h #${CMAKE_CURRENT_LIST_DIR}/platform/win/wincoreaudiodriver.cpp #${CMAKE_CURRENT_LIST_DIR}/platform/win/wincoreaudiodriver.h - ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudioclient.cpp - ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudioclient.h - ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapitypes.h - ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.cpp - ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.h - ${CMAKE_CURRENT_LIST_DIR}/platform/win/audiodeviceslistener.cpp - ${CMAKE_CURRENT_LIST_DIR}/platform/win/audiodeviceslistener.h + # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudioclient.cpp + # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudioclient.h + # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapitypes.h + # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.cpp + # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.h + # ${CMAKE_CURRENT_LIST_DIR}/platform/win/audiodeviceslistener.cpp + # ${CMAKE_CURRENT_LIST_DIR}/platform/win/audiodeviceslistener.h ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver2.cpp ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver2.h ) diff --git a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp index 1c074d779f997..088377ad4e5d4 100644 --- a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp +++ b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp @@ -125,7 +125,7 @@ static void s_bufferSwitch(long index, ASIOBool /*processNow*/) } uint8_t* stream = reinterpret_cast(&proc_buf[0]); - active.callback(nullptr, stream, (int)(procSamplesTotal * sizeof(float))); + active.callback(stream, (int)(procSamplesTotal * sizeof(float))); constexpr float MAX_S16 = 32767.0f; constexpr float MAX_S18 = 131071.0f; @@ -397,23 +397,23 @@ static ASIOError create_asio_buffers(long bufferSize, long outputChannels, long return ASE_OK; } -bool AsioAudioDriver::open(const Spec& spec, Spec* activeSpec) +AudioDeviceID AsioAudioDriver::defaultDevice() const { - if (m_deviceId.empty()) { - AudioDeviceList devices = availableOutputDevices(); - if (devices.empty()) { - return false; - } - m_deviceId = devices.at(0).id; + AudioDeviceList devices = availableOutputDevices(); + if (devices.empty()) { + return AudioDeviceID(); } - - return doOpen(m_deviceId, spec, activeSpec); + return devices.at(0).id; } -bool AsioAudioDriver::doOpen(const AudioDeviceID& device, const Spec& spec, Spec* activeSpec) +bool AsioAudioDriver::open(const Spec& spec, Spec* activeSpec) { - LOGI() << "try open: " << device; - const char* name = device.c_str(); + LOGI() << "try open: " << spec.deviceId; + IF_ASSERT_FAILED(!spec.deviceId.empty()) { + return false; + } + + const char* name = spec.deviceId.c_str(); bool ok = s_adata.drivers.loadDriver(const_cast(name)); if (!ok) { LOGE() << "failed load driver: " << name; @@ -579,53 +579,7 @@ async::Channel AsioAudioDriver::activeSpecChanged() const return m_activeSpecChanged; } -bool AsioAudioDriver::setOutputDeviceBufferSize(unsigned int bufferSize) -{ - bool result = true; - - if (isOpened()) { - close(); - Spec spec = s_adata.activeSpec; - spec.output.samplesPerChannel = bufferSize; - result = open(spec, nullptr); - } - - if (result) { - m_outputDeviceBufferSizeChanged.notify(); - } - - return result; -} - -async::Notification AsioAudioDriver::outputDeviceBufferSizeChanged() const -{ - return m_outputDeviceBufferSizeChanged; -} - -bool AsioAudioDriver::setOutputDeviceSampleRate(unsigned int sampleRate) -{ - bool result = true; - - if (isOpened()) { - close(); - Spec spec = s_adata.activeSpec; - spec.output.sampleRate = sampleRate; - result = open(spec, nullptr); - } - - if (result) { - m_outputDeviceSampleRateChanged.notify(); - } - - return result; -} - -async::Notification AsioAudioDriver::outputDeviceSampleRateChanged() const -{ - return m_outputDeviceSampleRateChanged; -} - -std::vector AsioAudioDriver::availableOutputDeviceBufferSizes() const +std::vector AsioAudioDriver::availableOutputDeviceBufferSizes() const { if (!isOpened()) { return { @@ -637,20 +591,20 @@ std::vector AsioAudioDriver::availableOutputDeviceBufferSizes() co }; } - std::vector result; + std::vector result; samples_t n = s_adata.deviceMetrics.maxSize; samples_t min = s_adata.deviceMetrics.minSize; while (n >= min) { - result.push_back(static_cast(n)); + result.push_back(n); n /= 2; } return result; } -std::vector AsioAudioDriver::availableOutputDeviceSampleRates() const +std::vector AsioAudioDriver::availableOutputDeviceSampleRates() const { return { 44100, @@ -658,48 +612,6 @@ std::vector AsioAudioDriver::availableOutputDeviceSampleRates() co }; } -AudioDeviceID AsioAudioDriver::outputDevice() const -{ - return m_deviceId; -} - -bool AsioAudioDriver::selectOutputDevice(const AudioDeviceID& id) -{ - bool result = true; - - if (m_deviceId == id) { - return result; - } - - m_deviceId = id; - - if (isOpened()) { - close(); - Spec spec = s_adata.activeSpec; - - //! NOTE We are trying to open a new device with the default value; - //! it is not known what it was before. - spec.output.samplesPerChannel = DEFAULT_BUFFER_SIZE; - result = open(spec, nullptr); - } - - if (result) { - m_outputDeviceChanged.notify(); - } - - return result; -} - -bool AsioAudioDriver::resetToDefaultOutputDevice() -{ - return selectOutputDevice(AudioDeviceID()); -} - -async::Notification AsioAudioDriver::outputDeviceChanged() const -{ - return m_outputDeviceChanged; -} - AudioDeviceList AsioAudioDriver::availableOutputDevices() const { char names[16][32]; @@ -727,11 +639,3 @@ async::Notification AsioAudioDriver::availableOutputDevicesChanged() const { return m_availableOutputDevicesChanged; } - -void AsioAudioDriver::resume() -{ -} - -void AsioAudioDriver::suspend() -{ -} diff --git a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h index bb3974d28f0a6..0176bad391014 100644 --- a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h +++ b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h @@ -37,6 +37,9 @@ class AsioAudioDriver : public IAudioDriver, public async::Asyncable void init() override; std::string name() const override; + + AudioDeviceID defaultDevice() const override; + bool open(const Spec& spec, Spec* activeSpec) override; void close() override; bool isOpened() const override; @@ -44,39 +47,19 @@ class AsioAudioDriver : public IAudioDriver, public async::Asyncable const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - bool setOutputDeviceSampleRate(unsigned int sampleRate) override; - async::Notification outputDeviceSampleRateChanged() const override; - std::vector availableOutputDeviceBufferSizes() const override; - std::vector availableOutputDeviceSampleRates() const override; - - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& id) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; - + std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceSampleRates() const override; AudioDeviceList availableOutputDevices() const override; async::Notification availableOutputDevicesChanged() const override; - void resume() override; - void suspend() override; - private: - bool doOpen(const AudioDeviceID& device, const Spec& spec, Spec* activeSpec); void reset(); std::thread m_thread; std::atomic m_running = false; - AudioDeviceID m_deviceId; - async::Notification m_outputDeviceChanged; - async::Notification m_availableOutputDevicesChanged; - async::Channel m_activeSpecChanged; - - async::Notification m_outputDeviceBufferSizeChanged; - async::Notification m_outputDeviceSampleRateChanged; + async::Notification m_availableOutputDevicesChanged; }; } diff --git a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp index 3f6b41e4b5b9a..9341177fa96da 100644 --- a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp +++ b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp @@ -223,6 +223,8 @@ WasapiAudioDriver2::WasapiAudioDriver2() WasapiAudioDriver2::~WasapiAudioDriver2() { + close(); + if (m_data->enumerator) { m_data->enumerator->UnregisterEndpointNotificationCallback(m_deviceListener.get()); } @@ -369,23 +371,28 @@ static IAudioClient* audioClientForDevice(IMMDeviceEnumerator* enumerator, const return audioClient; } +AudioDeviceID WasapiAudioDriver2::defaultDevice() const +{ + return m_defaultDeviceId; +} + bool WasapiAudioDriver2::open(const Spec& spec, Spec* activeSpec) { TRACEFUNC; - IF_ASSERT_FAILED(m_data->enumerator) { + IF_ASSERT_FAILED(!spec.deviceId.empty()) { return false; } - LOGI() << "begin driver open"; - - m_data->audioClient = audioClientForDevice(m_data->enumerator, m_deviceId); - if (!m_data->audioClient) { - m_deviceId = m_defaultDeviceId; - m_data->audioClient = audioClientForDevice(m_data->enumerator, m_deviceId); + IF_ASSERT_FAILED(m_data->enumerator) { + return false; } + LOGI() << "try open driver, device: " << spec.deviceId; + + m_data->audioClient = audioClientForDevice(m_data->enumerator, spec.deviceId); if (!m_data->audioClient) { + LOGE() << "failed get audio client for device: " << spec.deviceId; return false; } @@ -520,8 +527,7 @@ void WasapiAudioDriver2::th_processAudioData() m_surroundAudioBuffer.resize(surroundBufferDesiredSize, 0); } - m_activeSpec.callback(m_activeSpec.userdata, - m_surroundAudioBuffer.data(), + m_activeSpec.callback(m_surroundAudioBuffer.data(), (int)surroundBufferDesiredSize); for (uint32_t i = 0; i < actualFramesToRead; ++i) { @@ -530,7 +536,7 @@ void WasapiAudioDriver2::th_processAudioData() std::memset(frameStartPos + muFrameSize, 0, clientFrameSize - muFrameSize); } } else { - m_activeSpec.callback(m_activeSpec.userdata, data, actualFramesToRead * clientFrameSize); + m_activeSpec.callback(data, actualFramesToRead * clientFrameSize); } } m_data->renderClient->ReleaseBuffer(actualFramesToRead, 0); @@ -539,7 +545,9 @@ void WasapiAudioDriver2::th_processAudioData() void WasapiAudioDriver2::close() { m_opened = false; - m_audioThread.join(); + if (m_audioThread.joinable()) { + m_audioThread.join(); + } m_data->releaseClient(); } @@ -559,43 +567,6 @@ async::Channel WasapiAudioDriver2::activeSpecChanged() const return m_activeSpecChanged; } -AudioDeviceID WasapiAudioDriver2::outputDevice() const -{ - return m_deviceId; -} - -bool WasapiAudioDriver2::selectOutputDevice(const AudioDeviceID& id) -{ - bool result = true; - - if (m_deviceId == id) { - return result; - } - - m_deviceId = id; - - if (isOpened()) { - close(); - result = open(m_activeSpec, &m_activeSpec); - } - - if (result) { - m_outputDeviceChanged.notify(); - } - - return result; -} - -bool WasapiAudioDriver2::resetToDefaultOutputDevice() -{ - return selectOutputDevice(m_defaultDeviceId); -} - -async::Notification WasapiAudioDriver2::outputDeviceChanged() const -{ - return m_outputDeviceChanged; -} - AudioDeviceList WasapiAudioDriver2::availableOutputDevices() const { return m_deviceList; @@ -606,28 +577,9 @@ async::Notification WasapiAudioDriver2::availableOutputDevicesChanged() const return m_deviceListChanged; } -bool WasapiAudioDriver2::setOutputDeviceBufferSize(unsigned int bufferSize) +std::vector WasapiAudioDriver2::availableOutputDeviceBufferSizes() const { - bool result = true; - - if (isOpened()) { - close(); - m_activeSpec.output.samplesPerChannel = bufferSize; - result = open(m_activeSpec, &m_activeSpec); - } - m_outputDeviceBufferSizeChanged.notify(); - - return result; -} - -async::Notification WasapiAudioDriver2::outputDeviceBufferSizeChanged() const -{ - return m_outputDeviceBufferSizeChanged; -} - -std::vector WasapiAudioDriver2::availableOutputDeviceBufferSizes() const -{ - std::vector result; + std::vector result; samples_t n = MAXIMUM_BUFFER_SIZE; samples_t min = std::max(m_minimumPeriod, MINIMUM_BUFFER_SIZE); @@ -640,28 +592,7 @@ std::vector WasapiAudioDriver2::availableOutputDeviceBufferSizes() return result; } -bool WasapiAudioDriver2::setOutputDeviceSampleRate(unsigned int sampleRate) -{ - bool result = true; - - if (isOpened()) { - close(); - - m_activeSpec.output.sampleRate = sampleRate; - result = open(m_activeSpec, &m_activeSpec); - } - - m_outputDeviceSampleRateChanged.notify(); - - return result; -} - -async::Notification WasapiAudioDriver2::outputDeviceSampleRateChanged() const -{ - return m_outputDeviceSampleRateChanged; -} - -std::vector WasapiAudioDriver2::availableOutputDeviceSampleRates() const +std::vector WasapiAudioDriver2::availableOutputDeviceSampleRates() const { return { 44100, @@ -670,11 +601,3 @@ std::vector WasapiAudioDriver2::availableOutputDeviceSampleRates() 96000, }; } - -void WasapiAudioDriver2::resume() -{ -} - -void WasapiAudioDriver2::suspend() -{ -} diff --git a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.h b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.h index dcc4034a543d9..055b9724b0c49 100644 --- a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.h +++ b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.h @@ -37,6 +37,8 @@ class WasapiAudioDriver2 : public IAudioDriver void init() override; std::string name() const override; + + AudioDeviceID defaultDevice() const override; bool open(const Spec& spec, Spec* activeSpec) override; void close() override; bool isOpened() const override; @@ -44,25 +46,10 @@ class WasapiAudioDriver2 : public IAudioDriver const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& id) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; - AudioDeviceList availableOutputDevices() const override; async::Notification availableOutputDevicesChanged() const override; - - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - - std::vector availableOutputDeviceBufferSizes() const override; - - bool setOutputDeviceSampleRate(unsigned int sampleRate) override; - async::Notification outputDeviceSampleRateChanged() const override; - std::vector availableOutputDeviceSampleRates() const override; - - void resume() override; - void suspend() override; + std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceSampleRates() const override; private: @@ -75,7 +62,6 @@ class WasapiAudioDriver2 : public IAudioDriver struct Data; std::shared_ptr m_data; - AudioDeviceID m_deviceId; AudioDeviceID m_defaultDeviceId; async::Notification m_outputDeviceChanged; @@ -92,8 +78,5 @@ class WasapiAudioDriver2 : public IAudioDriver samples_t m_minimumPeriod = 0; std::vector m_surroundAudioBuffer; //! NOTE: See #17648 std::atomic m_opened; - - async::Notification m_outputDeviceBufferSizeChanged; - async::Notification m_outputDeviceSampleRateChanged; }; } diff --git a/src/framework/audio/iaudiodriver.h b/src/framework/audio/iaudiodriver.h index e0b36b7da85f0..8076a9de077e6 100644 --- a/src/framework/audio/iaudiodriver.h +++ b/src/framework/audio/iaudiodriver.h @@ -37,26 +37,22 @@ class IAudioDriver public: virtual ~IAudioDriver() = default; - enum class Format { - AudioF32, // float 32 bit - AudioS16 // short 16 bit - }; - - using Callback = std::function; + using Callback = std::function; struct Spec { + AudioDeviceID deviceId; OutputSpec output; - Format format; // Audio data format - Callback callback; // Callback that feeds the audio device - void* userdata; // Userdata passed to callback (ignored for NULL callbacks). - - inline bool isValid() const { return output.isValid() && callback != nullptr; } + Callback callback; + inline bool isValid() const { return output.isValid() && !deviceId.empty() && callback != nullptr; } }; virtual void init() = 0; virtual std::string name() const = 0; + + virtual AudioDeviceID defaultDevice() const = 0; + virtual bool open(const Spec& spec, Spec* activeSpec) = 0; virtual void close() = 0; virtual bool isOpened() const = 0; @@ -64,23 +60,10 @@ class IAudioDriver virtual const Spec& activeSpec() const = 0; virtual async::Channel activeSpecChanged() const = 0; - virtual bool setOutputDeviceBufferSize(unsigned int bufferSize) = 0; - virtual async::Notification outputDeviceBufferSizeChanged() const = 0; - virtual bool setOutputDeviceSampleRate(unsigned int sampleRate) = 0; - virtual async::Notification outputDeviceSampleRateChanged() const = 0; - virtual std::vector availableOutputDeviceBufferSizes() const = 0; - virtual std::vector availableOutputDeviceSampleRates() const = 0; - - virtual AudioDeviceID outputDevice() const = 0; - virtual bool selectOutputDevice(const AudioDeviceID& id) = 0; - virtual bool resetToDefaultOutputDevice() = 0; - virtual async::Notification outputDeviceChanged() const = 0; - + virtual std::vector availableOutputDeviceBufferSizes() const = 0; + virtual std::vector availableOutputDeviceSampleRates() const = 0; virtual AudioDeviceList availableOutputDevices() const = 0; virtual async::Notification availableOutputDevicesChanged() const = 0; - - virtual void resume() = 0; - virtual void suspend() = 0; }; using IAudioDriverPtr = std::shared_ptr; } diff --git a/src/framework/audio/iaudiodrivercontroller.h b/src/framework/audio/iaudiodrivercontroller.h index aa6c2aac3cd29..c59c625d14225 100644 --- a/src/framework/audio/iaudiodrivercontroller.h +++ b/src/framework/audio/iaudiodrivercontroller.h @@ -53,14 +53,14 @@ class IAudioDriverController : MODULE_EXPORT_INTERFACE virtual async::Channel activeSpecChanged() const = 0; virtual AudioDeviceID outputDevice() const = 0; - virtual bool selectOutputDevice(const std::string& deviceId) = 0; + virtual bool selectOutputDevice(const AudioDeviceID& deviceId) = 0; virtual async::Notification outputDeviceChanged() const = 0; - virtual std::vector availableOutputDeviceBufferSizes() const = 0; + virtual std::vector availableOutputDeviceBufferSizes() const = 0; virtual void changeBufferSize(samples_t samples) = 0; virtual async::Notification outputDeviceBufferSizeChanged() const = 0; - virtual std::vector availableOutputDeviceSampleRates() const = 0; + virtual std::vector availableOutputDeviceSampleRates() const = 0; virtual void changeSampleRate(sample_rate_t sampleRate) = 0; virtual async::Notification outputDeviceSampleRateChanged() const = 0; }; diff --git a/src/framework/audio/main/audiomodule.cpp b/src/framework/audio/main/audiomodule.cpp index 7903d17a7e20f..9eb02fd6adb97 100644 --- a/src/framework/audio/main/audiomodule.cpp +++ b/src/framework/audio/main/audiomodule.cpp @@ -120,7 +120,6 @@ void AudioModule::onInit(const IApplication::RunMode& mode) } m_actionsController->init(); - m_audioDriverController->init(); // rpc m_rpcChannel->setupOnMain(); diff --git a/src/framework/audio/main/internal/audiodrivercontroller.cpp b/src/framework/audio/main/internal/audiodrivercontroller.cpp index e471610acb068..2aa596d0ea2c7 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.cpp +++ b/src/framework/audio/main/internal/audiodrivercontroller.cpp @@ -22,6 +22,8 @@ #include "audiodrivercontroller.h" +#include "global/async/async.h" + #include "muse_framework_config.h" #ifdef MUSE_MODULE_AUDIO_JACK @@ -163,9 +165,6 @@ void AudioDriverController::setNewDriver(IAudioDriverPtr newDriver) // unsubscribe m_audioDriver->availableOutputDevicesChanged().disconnect(this); m_audioDriver->activeSpecChanged().disconnect(this); - m_audioDriver->outputDeviceChanged().disconnect(this); - m_audioDriver->outputDeviceBufferSizeChanged().disconnect(this); - m_audioDriver->outputDeviceSampleRateChanged().disconnect(this); } m_audioDriver = newDriver; @@ -180,24 +179,28 @@ void AudioDriverController::setNewDriver(IAudioDriverPtr newDriver) m_audioDriver->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) { m_activeSpecChanged.send(spec); - }); - m_audioDriver->outputDeviceChanged().onNotify(this, [this]() { - m_outputDeviceChanged.notify(); - }); + async::Async::call(this, [this, spec]() { + configuration()->setAudioOutputDeviceId(spec.deviceId); - m_audioDriver->outputDeviceBufferSizeChanged().onNotify(this, [this]() { - m_outputDeviceBufferSizeChanged.notify(); - }); + m_outputDeviceChanged.notify(); + m_outputDeviceBufferSizeChanged.notify(); + m_outputDeviceSampleRateChanged.notify(); - m_audioDriver->outputDeviceSampleRateChanged().onNotify(this, [this]() { - m_outputDeviceSampleRateChanged.notify(); + updateOutputSpec(); + }); }); } } -void AudioDriverController::init() +IAudioDriver::Spec AudioDriverController::defaultSpec() const { + IAudioDriver::Spec spec; + spec.deviceId = m_audioDriver ? m_audioDriver->defaultDevice() : AudioDeviceID(); + spec.output.audioChannelCount = 2; + spec.output.sampleRate = 44100; + spec.output.samplesPerChannel = DEFAULT_BUFFER_SIZE; + return spec; } std::string AudioDriverController::currentAudioApi() const @@ -207,28 +210,28 @@ std::string AudioDriverController::currentAudioApi() const void AudioDriverController::changeCurrentAudioApi(const std::string& name) { - IAudioDriver::Spec activeSpec; - if (m_audioDriver && m_audioDriver->isOpened()) { - activeSpec = m_audioDriver->activeSpec(); + IF_ASSERT_FAILED(m_audioDriver) { + return; + } + + IAudioDriver::Spec oldSpec = m_audioDriver->activeSpec(); + if (m_audioDriver->isOpened()) { m_audioDriver->close(); } IAudioDriverPtr driver = createDriver(name); driver->init(); setNewDriver(driver); - m_audioDriver->resetToDefaultOutputDevice(); LOGI() << "Used " << m_audioDriver->name() << " audio driver"; - if (activeSpec.isValid()) { - activeSpec.output.samplesPerChannel = DEFAULT_BUFFER_SIZE; - m_audioDriver->open(activeSpec, &activeSpec); - } - - updateOutputSpec(); + // reset to default + IAudioDriver::Spec spec = defaultSpec(); + spec.callback = oldSpec.callback; + m_audioDriver->open(spec, nullptr); configuration()->setCurrentAudioApi(name); - configuration()->setAudioOutputDeviceId(m_audioDriver->outputDevice()); m_currentAudioApiChanged.notify(); + m_availableOutputDevicesChanged.notify(); } async::Notification AudioDriverController::currentAudioApiChanged() const @@ -256,16 +259,12 @@ bool AudioDriverController::open(const IAudioDriver::Spec& spec, IAudioDriver::S driver->init(); setNewDriver(driver); - const std::string audioOutputDeviceId = configuration()->audioOutputDeviceId(); - m_audioDriver->selectOutputDevice(audioOutputDeviceId); - bool ok = m_audioDriver->open(spec, activeSpec); if (!ok) { - m_audioDriver->resetToDefaultOutputDevice(); - ok = m_audioDriver->open(spec, activeSpec); - if (ok) { - configuration()->setAudioOutputDeviceId(m_audioDriver->outputDevice()); - } + // reset to default device + IAudioDriver::Spec defaultDeviceSpec = spec; + defaultDeviceSpec.deviceId = m_audioDriver->defaultDevice(); + ok = m_audioDriver->open(defaultDeviceSpec, activeSpec); } if (!ok) { @@ -274,20 +273,19 @@ bool AudioDriverController::open(const IAudioDriver::Spec& spec, IAudioDriver::S IAudioDriverPtr defaultDriver = createDriver(defaultAudioApi); defaultDriver->init(); setNewDriver(defaultDriver); - m_audioDriver->resetToDefaultOutputDevice(); - ok = m_audioDriver->open(spec, activeSpec); + // reset to default + IAudioDriver::Spec defSpec = defaultSpec(); + defSpec.callback = spec.callback; + ok = m_audioDriver->open(defSpec, activeSpec); if (ok) { configuration()->setCurrentAudioApi(defaultAudioApi); - configuration()->setAudioOutputDeviceId(m_audioDriver->outputDevice()); } } } LOGI() << "Used audio driver: " << m_audioDriver->name() << ", opened: " << (ok ? "success" : "failed") - << ", device: " << m_audioDriver->outputDevice(); - - updateOutputSpec(); + << ", device: " << m_audioDriver->activeSpec().deviceId; return ok; } @@ -327,26 +325,34 @@ AudioDeviceID AudioDriverController::outputDevice() const IF_ASSERT_FAILED(m_audioDriver) { return AudioDeviceID(); } - return m_audioDriver->outputDevice(); + return m_audioDriver->activeSpec().deviceId; } -bool AudioDriverController::selectOutputDevice(const std::string& deviceId) +bool AudioDriverController::selectOutputDevice(const AudioDeviceID& deviceId) { IF_ASSERT_FAILED(m_audioDriver) { return false; } - const AudioDeviceID oldDeviceId = m_audioDriver->outputDevice(); + if (!m_audioDriver->isOpened()) { + configuration()->setAudioOutputDeviceId(deviceId); + return true; + } + + const IAudioDriver::Spec oldSpec = m_audioDriver->activeSpec(); LOGI() << "Trying to change output device" - << " from: " << oldDeviceId + << " from: " << oldSpec.deviceId << ", to: " << deviceId; - bool ok = m_audioDriver->selectOutputDevice(deviceId); - if (ok) { - configuration()->setAudioOutputDeviceId(deviceId); - updateOutputSpec(); - } else { - LOGE() << "failed select device, return to old: " << oldDeviceId; - m_audioDriver->selectOutputDevice(oldDeviceId); + + IAudioDriver::Spec spec = defaultSpec(); + spec.deviceId = deviceId; + spec.callback = oldSpec.callback; + + m_audioDriver->close(); + bool ok = m_audioDriver->open(spec, nullptr); + if (!ok) { + LOGE() << "failed select device, return to old: " << oldSpec.deviceId; + m_audioDriver->open(oldSpec, nullptr); } return ok; } @@ -358,12 +364,17 @@ async::Notification AudioDriverController::outputDeviceChanged() const void AudioDriverController::checkOutputDevice() { - AudioDeviceID preferredDeviceId = configuration()->audioOutputDeviceId(); - //! NOTE If the driver cannot open with the selected device, - //! it will open with the default device. - bool deviceChanged = m_audioDriver->selectOutputDevice(preferredDeviceId); - if (deviceChanged) { - updateOutputSpec(); + if (!m_audioDriver->isOpened()) { + return; + } + + IAudioDriver::Spec spec = m_audioDriver->activeSpec(); + m_audioDriver->close(); + bool ok = m_audioDriver->open(spec, nullptr); + if (!ok) { + // reset to default device + spec.deviceId = m_audioDriver->defaultDevice(); + m_audioDriver->open(spec, nullptr); } } @@ -377,7 +388,7 @@ void AudioDriverController::updateOutputSpec() rpcChannel()->send(rpc::make_request(Method::SetOutputSpec, RpcPacker::pack(activeSpec.output))); } -std::vector AudioDriverController::availableOutputDeviceBufferSizes() const +std::vector AudioDriverController::availableOutputDeviceBufferSizes() const { IF_ASSERT_FAILED(m_audioDriver) { return {}; @@ -392,10 +403,19 @@ void AudioDriverController::changeBufferSize(samples_t samples) } LOGI() << "Trying to change buffer size: " << samples; - bool ok = m_audioDriver->setOutputDeviceBufferSize(samples); + bool ok = true; + + if (m_audioDriver->isOpened()) { + IAudioDriver::Spec spec = m_audioDriver->activeSpec(); + m_audioDriver->close(); + spec.output.samplesPerChannel = samples; + ok = m_audioDriver->open(spec, &spec); + } + if (ok) { - configuration()->setDriverBufferSize(samples); updateOutputSpec(); + configuration()->setDriverBufferSize(samples); + m_outputDeviceBufferSizeChanged.notify(); } } @@ -404,7 +424,7 @@ async::Notification AudioDriverController::outputDeviceBufferSizeChanged() const return m_outputDeviceBufferSizeChanged; } -std::vector AudioDriverController::availableOutputDeviceSampleRates() const +std::vector AudioDriverController::availableOutputDeviceSampleRates() const { IF_ASSERT_FAILED(m_audioDriver) { return {}; @@ -418,12 +438,20 @@ void AudioDriverController::changeSampleRate(sample_rate_t sampleRate) return; } - LOGI() << "Trying to change sample rate: " << sampleRate; + LOGI() << "Trying to change sampleRate: " << sampleRate; + bool ok = true; + + if (m_audioDriver->isOpened()) { + IAudioDriver::Spec spec = m_audioDriver->activeSpec(); + m_audioDriver->close(); + spec.output.sampleRate = sampleRate; + ok = m_audioDriver->open(spec, &spec); + } - bool ok = m_audioDriver->setOutputDeviceSampleRate(sampleRate); if (ok) { - configuration()->setSampleRate(sampleRate); updateOutputSpec(); + configuration()->setSampleRate(sampleRate); + m_outputDeviceSampleRateChanged.notify(); } } diff --git a/src/framework/audio/main/internal/audiodrivercontroller.h b/src/framework/audio/main/internal/audiodrivercontroller.h index 70c00652a707f..b20853d5184b8 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.h +++ b/src/framework/audio/main/internal/audiodrivercontroller.h @@ -40,8 +40,6 @@ class AudioDriverController : public IAudioDriverController, public Injectable, AudioDriverController(const modularity::ContextPtr& iocCtx) : Injectable(iocCtx) {} - void init(); - // Api std::vector availableAudioApiList() const override; @@ -61,14 +59,14 @@ class AudioDriverController : public IAudioDriverController, public Injectable, async::Channel activeSpecChanged() const override; AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const std::string& deviceId) override; + bool selectOutputDevice(const AudioDeviceID& deviceId) override; async::Notification outputDeviceChanged() const override; - std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceBufferSizes() const override; void changeBufferSize(samples_t samples) override; async::Notification outputDeviceBufferSizeChanged() const override; - std::vector availableOutputDeviceSampleRates() const override; + std::vector availableOutputDeviceSampleRates() const override; void changeSampleRate(sample_rate_t sampleRate) override; async::Notification outputDeviceSampleRateChanged() const override; @@ -76,6 +74,8 @@ class AudioDriverController : public IAudioDriverController, public Injectable, IAudioDriverPtr createDriver(const std::string& name) const; void setNewDriver(IAudioDriverPtr newDriver); + IAudioDriver::Spec defaultSpec() const; + void checkOutputDevice(); void updateOutputSpec(); diff --git a/src/framework/audio/main/internal/startaudiocontroller.cpp b/src/framework/audio/main/internal/startaudiocontroller.cpp index 0c705f8b65d42..a56b4e88353ae 100644 --- a/src/framework/audio/main/internal/startaudiocontroller.cpp +++ b/src/framework/audio/main/internal/startaudiocontroller.cpp @@ -164,7 +164,7 @@ async::Channel StartAudioController::isAudioStartedChanged() const void StartAudioController::startAudioProcessing(const IApplication::RunMode& mode) { IAudioDriver::Spec requiredSpec; - requiredSpec.format = IAudioDriver::Format::AudioF32; + requiredSpec.deviceId = configuration()->audioOutputDeviceId(); requiredSpec.output.sampleRate = configuration()->sampleRate(); requiredSpec.output.audioChannelCount = configuration()->audioChannelsCount(); requiredSpec.output.samplesPerChannel = configuration()->driverBufferSize(); @@ -179,7 +179,7 @@ void StartAudioController::startAudioProcessing(const IApplication::RunMode& mod bool shouldMeasureInputLag = configuration()->shouldMeasureInputLag(); requiredSpec.callback = [this, shouldMeasureInputLag] - (void* /*userdata*/, uint8_t* stream, int byteCount) { + (uint8_t* stream, int byteCount) { std::memset(stream, 0, byteCount); // driver metrics const size_t driverSamplesTotal = byteCount / sizeof(float); diff --git a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp index f6ee74bff00b8..464d85ffa65be 100644 --- a/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp +++ b/src/preferences/qml/MuseScore/Preferences/commonaudioapiconfigurationmodel.cpp @@ -39,6 +39,7 @@ void CommonAudioApiConfigurationModel::load() audioDriverController()->currentAudioApiChanged().onNotify(this, [this]() { emit deviceListChanged(); emit currentDeviceIdChanged(); + emit sampleRateListChanged(); emit sampleRateChanged(); emit bufferSizeListChanged(); emit bufferSizeChanged(); @@ -51,6 +52,7 @@ void CommonAudioApiConfigurationModel::load() audioDriverController()->outputDeviceChanged().onNotify(this, [this]() { emit currentDeviceIdChanged(); + emit sampleRateListChanged(); emit sampleRateChanged(); emit bufferSizeListChanged(); emit bufferSizeChanged(); @@ -67,7 +69,8 @@ void CommonAudioApiConfigurationModel::load() QString CommonAudioApiConfigurationModel::currentDeviceId() const { - return QString::fromStdString(audioDriverController()->outputDevice()); + AudioDeviceID device = audioDriverController()->outputDevice(); + return QString::fromStdString(device); } QVariantList CommonAudioApiConfigurationModel::deviceList() const @@ -104,10 +107,10 @@ unsigned int CommonAudioApiConfigurationModel::bufferSize() const QList CommonAudioApiConfigurationModel::bufferSizeList() const { QList result; - std::vector bufferSizes = audioDriverController()->availableOutputDeviceBufferSizes(); + std::vector bufferSizes = audioDriverController()->availableOutputDeviceBufferSizes(); - for (unsigned int bufferSize : bufferSizes) { - result << bufferSize; + for (samples_t bufferSize : bufferSizes) { + result << static_cast(bufferSize); } return result; @@ -126,10 +129,10 @@ unsigned int CommonAudioApiConfigurationModel::sampleRate() const QList CommonAudioApiConfigurationModel::sampleRateList() const { QList result; - std::vector sampleRates = audioDriverController()->availableOutputDeviceSampleRates(); + std::vector sampleRates = audioDriverController()->availableOutputDeviceSampleRates(); - for (unsigned int sampleRate : sampleRates) { - result << sampleRate; + for (sample_rate_t sampleRate : sampleRates) { + result << static_cast(sampleRate); } return result; From fa5a2b30194a83a50083688809b3f9bdae9f5b22 Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Mon, 17 Nov 2025 20:04:12 +0200 Subject: [PATCH 5/7] removed logic from linux drivers --- .../driver/platform/lin/alsaaudiodriver.cpp | 127 ++----------- .../driver/platform/lin/alsaaudiodriver.h | 30 +--- .../driver/platform/lin/pwaudiodriver.cpp | 167 ++---------------- .../audio/driver/platform/lin/pwaudiodriver.h | 28 +-- 4 files changed, 46 insertions(+), 306 deletions(-) diff --git a/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp b/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp index 3cc00a2ae6e66..2471b19af6208 100644 --- a/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp +++ b/src/framework/audio/driver/platform/lin/alsaaudiodriver.cpp @@ -68,7 +68,7 @@ static void* alsaThread(void* aParam) uint8_t* stream = (uint8_t*)data->buffer; int len = data->samples * data->channels * sizeof(float); - data->callback(data->userdata, stream, len); + data->callback(stream, len); snd_pcm_sframes_t pcm = snd_pcm_writei(data->alsaDeviceHandle, data->buffer, data->samples); if (pcm != -EPIPE) { @@ -107,7 +107,6 @@ static void alsaCleanup() AlsaAudioDriver::AlsaAudioDriver() { - m_deviceId = DEFAULT_DEVICE_ID; } AlsaAudioDriver::~AlsaAudioDriver() @@ -131,18 +130,26 @@ std::string AlsaAudioDriver::name() const return "ALSA"; } +AudioDeviceID AlsaAudioDriver::defaultDevice() const +{ + return DEFAULT_DEVICE_ID; +} + bool AlsaAudioDriver::open(const Spec& spec, Spec* activeSpec) { + IF_ASSERT_FAILED(!spec.deviceId.empty()) { + return false; + } + s_alsaData = new ALSAData(); s_alsaData->samples = spec.output.samplesPerChannel; s_alsaData->channels = spec.output.audioChannelCount; s_alsaData->callback = spec.callback; - s_alsaData->userdata = spec.userdata; snd_pcm_t* handle; - int rc = snd_pcm_open(&handle, outputDevice().c_str(), SND_PCM_STREAM_PLAYBACK, 0); + int rc = snd_pcm_open(&handle, spec.deviceId.c_str(), SND_PCM_STREAM_PLAYBACK, 0); if (rc < 0) { - LOGE() << "Unable to open device: " << outputDevice() << ", err code: " << rc; + LOGE() << "Unable to open device: " << spec.deviceId << ", err code: " << rc; return false; } @@ -183,7 +190,6 @@ bool AlsaAudioDriver::open(const Spec& spec, Spec* activeSpec) //_alsaData->sampleBuffer = new short[_alsaData->samples * _alsaData->channels]; s_format = spec; - s_format.format = Format::AudioF32; s_format.output.sampleRate = aSamplerate; m_activeSpecChanged.send(s_format); @@ -199,7 +205,7 @@ bool AlsaAudioDriver::open(const Spec& spec, Spec* activeSpec) return false; } - LOGI() << "Connected to " << outputDevice() + LOGI() << "Connected to " << spec.deviceId << " with bufferSize " << s_format.output.samplesPerChannel << ", sampleRate " << s_format.output.sampleRate << ", channels: " << s_format.output.audioChannelCount; @@ -227,43 +233,6 @@ async::Channel AlsaAudioDriver::activeSpecChanged() const return m_activeSpecChanged; } -AudioDeviceID AlsaAudioDriver::outputDevice() const -{ - return m_deviceId; -} - -bool AlsaAudioDriver::selectOutputDevice(const AudioDeviceID& deviceId) -{ - if (m_deviceId == deviceId) { - return true; - } - - bool reopen = isOpened(); - close(); - m_deviceId = deviceId; - - bool ok = true; - if (reopen) { - ok = open(s_format, &s_format); - } - - if (ok) { - m_outputDeviceChanged.notify(); - } - - return ok; -} - -bool AlsaAudioDriver::resetToDefaultOutputDevice() -{ - return selectOutputDevice(DEFAULT_DEVICE_ID); -} - -async::Notification AlsaAudioDriver::outputDeviceChanged() const -{ - return m_outputDeviceChanged; -} - AudioDeviceList AlsaAudioDriver::availableOutputDevices() const { AudioDeviceList devices; @@ -277,38 +246,11 @@ async::Notification AlsaAudioDriver::availableOutputDevicesChanged() const return m_availableOutputDevicesChanged; } -bool AlsaAudioDriver::setOutputDeviceBufferSize(unsigned int bufferSize) +std::vector AlsaAudioDriver::availableOutputDeviceBufferSizes() const { - if (s_format.output.samplesPerChannel == bufferSize) { - return true; - } - - bool reopen = isOpened(); - close(); - s_format.output.samplesPerChannel = bufferSize; - - bool ok = true; - if (reopen) { - ok = open(s_format, &s_format); - } + std::vector result; - if (ok) { - m_bufferSizeChanged.notify(); - } - - return ok; -} - -async::Notification AlsaAudioDriver::outputDeviceBufferSizeChanged() const -{ - return m_bufferSizeChanged; -} - -std::vector AlsaAudioDriver::availableOutputDeviceBufferSizes() const -{ - std::vector result; - - unsigned int n = MAXIMUM_BUFFER_SIZE; + samples_t n = MAXIMUM_BUFFER_SIZE; while (n >= MINIMUM_BUFFER_SIZE) { result.push_back(n); n /= 2; @@ -319,34 +261,7 @@ std::vector AlsaAudioDriver::availableOutputDeviceBufferSizes() co return result; } -bool AlsaAudioDriver::setOutputDeviceSampleRate(unsigned int sampleRate) -{ - if (s_format.output.sampleRate == sampleRate) { - return true; - } - - bool reopen = isOpened(); - close(); - s_format.output.sampleRate = sampleRate; - - bool ok = true; - if (reopen) { - ok = open(s_format, &s_format); - } - - if (ok) { - m_sampleRateChanged.notify(); - } - - return ok; -} - -async::Notification AlsaAudioDriver::outputDeviceSampleRateChanged() const -{ - return m_sampleRateChanged; -} - -std::vector AlsaAudioDriver::availableOutputDeviceSampleRates() const +std::vector AlsaAudioDriver::availableOutputDeviceSampleRates() const { // ALSA API is not of any help to get sample rates supported by the driver. // (snd_pcm_hw_params_get_rate_[min|max] will return 1 to 384000 Hz) @@ -358,11 +273,3 @@ std::vector AlsaAudioDriver::availableOutputDeviceSampleRates() co 96000, }; } - -void AlsaAudioDriver::resume() -{ -} - -void AlsaAudioDriver::suspend() -{ -} diff --git a/src/framework/audio/driver/platform/lin/alsaaudiodriver.h b/src/framework/audio/driver/platform/lin/alsaaudiodriver.h index bf59047b21da9..ae753db98eec1 100644 --- a/src/framework/audio/driver/platform/lin/alsaaudiodriver.h +++ b/src/framework/audio/driver/platform/lin/alsaaudiodriver.h @@ -38,6 +38,9 @@ class AlsaAudioDriver : public IAudioDriver, public async::Asyncable void init() override; std::string name() const override; + + AudioDeviceID defaultDevice() const override; + bool open(const Spec& spec, Spec* activeSpec) override; void close() override; bool isOpened() const override; @@ -45,39 +48,16 @@ class AlsaAudioDriver : public IAudioDriver, public async::Asyncable const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& deviceId) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; - AudioDeviceList availableOutputDevices() const override; async::Notification availableOutputDevicesChanged() const override; - - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - - std::vector availableOutputDeviceBufferSizes() const override; - - bool setOutputDeviceSampleRate(unsigned int sampleRate) override; - async::Notification outputDeviceSampleRateChanged() const override; - - std::vector availableOutputDeviceSampleRates() const override; - - void resume() override; - void suspend() override; + std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceSampleRates() const override; private: async::Channel m_activeSpecChanged; - async::Notification m_outputDeviceChanged; - mutable std::mutex m_devicesMutex; AudioDevicesListener m_devicesListener; async::Notification m_availableOutputDevicesChanged; - - std::string m_deviceId; - - async::Notification m_bufferSizeChanged; - async::Notification m_sampleRateChanged; }; } diff --git a/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp b/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp index d2e10f2c7451e..014658f62e5a7 100644 --- a/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp +++ b/src/framework/audio/driver/platform/lin/pwaudiodriver.cpp @@ -453,22 +453,8 @@ PwStream::PwStream(pw_core* core, const IAudioDriver::Spec& spec, const std::str spa_audio_info_raw formatInfo {}; formatInfo.rate = m_spec.output.sampleRate; formatInfo.channels = m_spec.output.audioChannelCount; - switch (m_spec.format) { - case IAudioDriver::Format::AudioF32: - formatInfo.format = SPA_AUDIO_FORMAT_F32; - m_stride = m_spec.output.audioChannelCount * 4; - break; - case IAudioDriver::Format::AudioS16: - formatInfo.format = SPA_AUDIO_FORMAT_S16; - m_stride = m_spec.output.audioChannelCount * 2; - break; - default: - LOGW() << "Unknow format, falling back to F32"; - formatInfo.format = SPA_AUDIO_FORMAT_F32; - m_spec.format = IAudioDriver::Format::AudioF32; - m_stride = m_spec.output.audioChannelCount * 4; - break; - } + formatInfo.format = SPA_AUDIO_FORMAT_F32; + m_stride = m_spec.output.audioChannelCount * 4; char buf[1024]; auto builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); @@ -528,7 +514,7 @@ void PwStream::process() const auto numFrames = b->requested ? SPA_MIN(b->requested, maxFrames) : maxFrames; const auto len = numFrames * m_stride; - m_spec.callback(m_spec.userdata, dst, len); + m_spec.callback(dst, len); buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = m_stride; @@ -539,8 +525,6 @@ void PwStream::process() PwAudioDriver::PwAudioDriver() { - m_deviceId = PW_DEFAULT_DEVICE; - static bool initDone = false; if (!initDone) { @@ -614,7 +598,15 @@ void PwAudioDriver::init() LOGD() << "Running with version " << pw_get_library_version(); } -std::string PwAudioDriver::name() const { return "PipeWire"; } +std::string PwAudioDriver::name() const +{ + return "PipeWire"; +} + +AudioDeviceID PwAudioDriver::defaultDevice() const +{ + return PW_DEFAULT_DEVICE; +} bool PwAudioDriver::open(const Spec& spec, Spec* activeSpec) { @@ -624,10 +616,10 @@ bool PwAudioDriver::open(const Spec& spec, Spec* activeSpec) PwLoopLock lk { m_loop }; - LOGI() << "Connecting to " << m_deviceId << " / " << spec.output.sampleRate << "Hz / " + LOGI() << "Connecting to " << spec.deviceId << " / " << spec.output.sampleRate << "Hz / " << spec.output.samplesPerChannel << " samples"; - m_stream = std::make_unique(m_core, spec, m_deviceId); + m_stream = std::make_unique(m_core, spec, spec.deviceId); m_formatSpec = m_stream->spec(); if (activeSpec) { @@ -636,7 +628,7 @@ bool PwAudioDriver::open(const Spec& spec, Spec* activeSpec) m_activeSpecChanged.send(m_formatSpec); - LOGD() << "Connected to " << outputDevice(); + LOGD() << "Connected to " << spec.deviceId; return true; } @@ -653,53 +645,6 @@ const IAudioDriver::Spec& PwAudioDriver::activeSpec() const { return m_formatSpe async::Channel PwAudioDriver::activeSpecChanged() const { return m_activeSpecChanged; } -AudioDeviceID PwAudioDriver::outputDevice() const { return m_deviceId; } - -bool PwAudioDriver::selectOutputDevice(const AudioDeviceID& deviceId) -{ - LOGD() << "Selecting output device: " << deviceId; - - if (m_deviceId == deviceId) { - LOGD() << "Output device already selected: " << deviceId; - return true; - } - - auto devices = availableOutputDevices(); - bool reopen = isOpened(); - - close(); - - auto dev = std::find_if(devices.begin(), devices.end(), [&](const AudioDevice& device) { return device.id == deviceId; }); - if (dev == devices.end()) { - LOGW() << "Could not find device \"" << m_deviceId << "\". Falling back to \"" << PW_DEFAULT_DEVICE << "\""; - m_deviceId = PW_DEFAULT_DEVICE; - } else { - LOGI() << "Selecting device \"" << dev->name << "\" (" << dev->id << ")"; - m_deviceId = deviceId; - } - - bool ok = true; - if (reopen) { - ok = open(m_formatSpec, nullptr); - } - - if (ok) { - m_outputDeviceChanged.notify(); - } - - return ok; -} - -bool PwAudioDriver::resetToDefaultOutputDevice() -{ - return selectOutputDevice(PW_DEFAULT_DEVICE); -} - -async::Notification PwAudioDriver::outputDeviceChanged() const -{ - return m_outputDeviceChanged; -} - AudioDeviceList PwAudioDriver::availableOutputDevices() const { IF_ASSERT_FAILED_X(m_core, "Not connected to PipeWire. Should fallback to ALSA!") { @@ -722,40 +667,11 @@ async::Notification PwAudioDriver::availableOutputDevicesChanged() const return m_registry->availableOutputDevicesChanged(); } -bool PwAudioDriver::setOutputDeviceBufferSize(unsigned int bufferSize) -{ - if (m_formatSpec.output.samplesPerChannel == bufferSize) { - return true; - } - - bool reopen = isOpened(); - - close(); - m_formatSpec.output.samplesPerChannel = bufferSize; - - bool ok = true; - if (reopen) { - ok = open(m_formatSpec, nullptr); - } - - if (ok) { - m_bufferSizeChanged.notify(); - } - - return ok; -} - -async::Notification PwAudioDriver::outputDeviceBufferSizeChanged() const -{ - return m_bufferSizeChanged; -} - -std::vector -PwAudioDriver::availableOutputDeviceBufferSizes() const +std::vector PwAudioDriver::availableOutputDeviceBufferSizes() const { - std::vector result; + std::vector result; - unsigned int n = MAXIMUM_BUFFER_SIZE; + samples_t n = MAXIMUM_BUFFER_SIZE; while (n >= MINIMUM_BUFFER_SIZE) { result.push_back(n); n /= 2; @@ -766,36 +682,7 @@ PwAudioDriver::availableOutputDeviceBufferSizes() const return result; } -bool PwAudioDriver::setOutputDeviceSampleRate(unsigned int sampleRate) -{ - if (m_formatSpec.output.sampleRate == sampleRate) { - return true; - } - - bool reopen = isOpened(); - - close(); - m_formatSpec.output.sampleRate = sampleRate; - - bool ok = true; - if (reopen) { - ok = open(m_formatSpec, nullptr); - } - - if (ok) { - m_sampleRateChanged.notify(); - } - - return ok; -} - -async::Notification PwAudioDriver::outputDeviceSampleRateChanged() const -{ - return m_sampleRateChanged; -} - -std::vector -PwAudioDriver::availableOutputDeviceSampleRates() const +std::vector PwAudioDriver::availableOutputDeviceSampleRates() const { return { 44100, @@ -804,19 +691,3 @@ PwAudioDriver::availableOutputDeviceSampleRates() const 96000, }; } - -void PwAudioDriver::resume() -{ - if (m_stream) { - PwLoopLock lk { m_loop }; - m_stream->resume(); - } -} - -void PwAudioDriver::suspend() -{ - if (m_stream) { - PwLoopLock lk { m_loop }; - m_stream->suspend(); - } -} diff --git a/src/framework/audio/driver/platform/lin/pwaudiodriver.h b/src/framework/audio/driver/platform/lin/pwaudiodriver.h index 177dc4f1e95c1..bdc65699f95f7 100644 --- a/src/framework/audio/driver/platform/lin/pwaudiodriver.h +++ b/src/framework/audio/driver/platform/lin/pwaudiodriver.h @@ -51,6 +51,9 @@ class PwAudioDriver : public IAudioDriver, public async::Asyncable void init() override; std::string name() const override; + + AudioDeviceID defaultDevice() const override; + bool open(const Spec& spec, Spec* activeSpec) override; void close() override; bool isOpened() const override; @@ -58,34 +61,13 @@ class PwAudioDriver : public IAudioDriver, public async::Asyncable const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& deviceId) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; - AudioDeviceList availableOutputDevices() const override; async::Notification availableOutputDevicesChanged() const override; - - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - std::vector availableOutputDeviceBufferSizes() const override; - - bool setOutputDeviceSampleRate(unsigned int sampleRate) override; - async::Notification outputDeviceSampleRateChanged() const override; - std::vector availableOutputDeviceSampleRates() const override; - - void resume() override; - void suspend() override; + std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceSampleRates() const override; private: - async::Notification m_outputDeviceChanged; - - std::string m_deviceId; - - async::Notification m_bufferSizeChanged; - async::Notification m_sampleRateChanged; - Spec m_formatSpec; async::Channel m_activeSpecChanged; From edd9fea9d1035056d2632c57742394a9efdecc21 Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Mon, 17 Nov 2025 20:14:53 +0200 Subject: [PATCH 6/7] removed logic from osx driver --- src/framework/audio/driver/audio_driver.cmake | 15 +- .../driver/platform/osx/osxaudiodriver.h | 27 +- .../driver/platform/osx/osxaudiodriver.mm | 154 +--- .../platform/win/audiodeviceslistener.cpp | 139 ---- .../platform/win/audiodeviceslistener.h | 56 -- .../driver/platform/win/wasapiaudioclient.cpp | 705 ------------------ .../driver/platform/win/wasapiaudioclient.h | 108 --- .../driver/platform/win/wasapiaudiodriver.cpp | 691 +++++++++++------ .../driver/platform/win/wasapiaudiodriver.h | 71 +- .../platform/win/wasapiaudiodriver2.cpp | 603 --------------- .../driver/platform/win/wasapiaudiodriver2.h | 82 -- .../audio/driver/platform/win/wasapitypes.h | 132 ---- .../platform/win/wincoreaudiodriver.cpp | 543 -------------- .../driver/platform/win/wincoreaudiodriver.h | 84 --- .../audio/driver/platform/win/winmmdriver.cpp | 250 ------- .../audio/driver/platform/win/winmmdriver.h | 44 -- .../main/internal/audiodrivercontroller.cpp | 7 +- .../stubs/audio/audioconfigurationstub.cpp | 7 +- .../stubs/audio/audioconfigurationstub.h | 1 + .../stubs/audio/audiodrivercontrollerstub.cpp | 82 +- .../stubs/audio/audiodrivercontrollerstub.h | 33 +- src/framework/stubs/audio/audiodriverstub.cpp | 59 +- src/framework/stubs/audio/audiodriverstub.h | 22 +- 23 files changed, 634 insertions(+), 3281 deletions(-) delete mode 100644 src/framework/audio/driver/platform/win/audiodeviceslistener.cpp delete mode 100644 src/framework/audio/driver/platform/win/audiodeviceslistener.h delete mode 100644 src/framework/audio/driver/platform/win/wasapiaudioclient.cpp delete mode 100644 src/framework/audio/driver/platform/win/wasapiaudioclient.h delete mode 100644 src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp delete mode 100644 src/framework/audio/driver/platform/win/wasapiaudiodriver2.h delete mode 100644 src/framework/audio/driver/platform/win/wasapitypes.h delete mode 100644 src/framework/audio/driver/platform/win/wincoreaudiodriver.cpp delete mode 100755 src/framework/audio/driver/platform/win/wincoreaudiodriver.h delete mode 100644 src/framework/audio/driver/platform/win/winmmdriver.cpp delete mode 100644 src/framework/audio/driver/platform/win/winmmdriver.h diff --git a/src/framework/audio/driver/audio_driver.cmake b/src/framework/audio/driver/audio_driver.cmake index 258949463dc39..eb3a257dd82ad 100644 --- a/src/framework/audio/driver/audio_driver.cmake +++ b/src/framework/audio/driver/audio_driver.cmake @@ -23,19 +23,8 @@ include(GetPlatformInfo) if (OS_IS_WIN) set(AUDIO_DRIVER_SRC - #${CMAKE_CURRENT_LIST_DIR}/platform/win/winmmdriver.cpp - #${CMAKE_CURRENT_LIST_DIR}/platform/win/winmmdriver.h - #${CMAKE_CURRENT_LIST_DIR}/platform/win/wincoreaudiodriver.cpp - #${CMAKE_CURRENT_LIST_DIR}/platform/win/wincoreaudiodriver.h - # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudioclient.cpp - # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudioclient.h - # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapitypes.h - # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.cpp - # ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.h - # ${CMAKE_CURRENT_LIST_DIR}/platform/win/audiodeviceslistener.cpp - # ${CMAKE_CURRENT_LIST_DIR}/platform/win/audiodeviceslistener.h - ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver2.cpp - ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver2.h + ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.cpp + ${CMAKE_CURRENT_LIST_DIR}/platform/win/wasapiaudiodriver.h ) if (MUSE_MODULE_AUDIO_ASIO) diff --git a/src/framework/audio/driver/platform/osx/osxaudiodriver.h b/src/framework/audio/driver/platform/osx/osxaudiodriver.h index 942a92bf8259e..be695b389a418 100644 --- a/src/framework/audio/driver/platform/osx/osxaudiodriver.h +++ b/src/framework/audio/driver/platform/osx/osxaudiodriver.h @@ -44,6 +44,9 @@ class OSXAudioDriver : public IAudioDriver void init() override; std::string name() const override; + + AudioDeviceID defaultDevice() const override; + bool open(const Spec& spec, Spec* activeSpec) override; void close() override; bool isOpened() const override; @@ -51,27 +54,12 @@ class OSXAudioDriver : public IAudioDriver const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; - void resume() override; - void suspend() override; - - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& deviceId) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; - AudioDeviceList availableOutputDevices() const override; async::Notification availableOutputDevicesChanged() const override; void updateDeviceMap(); - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - - std::vector availableOutputDeviceBufferSizes() const override; - - bool setOutputDeviceSampleRate(unsigned int sampleRate) override; - async::Notification outputDeviceSampleRateChanged() const override; - - std::vector availableOutputDeviceSampleRates() const override; + std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceSampleRates() const override; private: static void OnFillBuffer(void* context, OpaqueAudioQueue* queue, AudioQueueBuffer* buffer); @@ -89,12 +77,7 @@ class OSXAudioDriver : public IAudioDriver async::Channel m_activeSpecChanged; std::map m_outputDevices = {}, m_inputDevices = {}; mutable std::mutex m_devicesMutex; - async::Notification m_outputDeviceChanged; async::Notification m_availableOutputDevicesChanged; - AudioDeviceID m_deviceId; - - async::Notification m_bufferSizeChanged; - async::Notification m_sampleRateChanged; }; } #endif // MUSE_AUDIO_OSXAUDIODRIVER_H diff --git a/src/framework/audio/driver/platform/osx/osxaudiodriver.mm b/src/framework/audio/driver/platform/osx/osxaudiodriver.mm index ea63eb4c19125..a5e7bd32c5a85 100644 --- a/src/framework/audio/driver/platform/osx/osxaudiodriver.mm +++ b/src/framework/audio/driver/platform/osx/osxaudiodriver.mm @@ -38,7 +38,6 @@ Spec format; AudioQueueRef audioQueue; Callback callback; - void* mUserData; }; OSXAudioDriver::OSXAudioDriver() @@ -49,8 +48,6 @@ initDeviceMapListener(); updateDeviceMap(); - - m_deviceId = DEFAULT_DEVICE_ID; } OSXAudioDriver::~OSXAudioDriver() @@ -64,7 +61,12 @@ std::string OSXAudioDriver::name() const { - return "MUAUDIO(OSX)"; + return "OSX"; +} + +muse::audio::AudioDeviceID OSXAudioDriver::defaultDevice() const +{ + return DEFAULT_DEVICE_ID; } bool OSXAudioDriver::open(const Spec& spec, Spec* activeSpec) @@ -77,9 +79,11 @@ return 0; } - *activeSpec = spec; - activeSpec->format = Format::AudioF32; - m_data->format = *activeSpec; + if (activeSpec) { + *activeSpec = spec; + } + + m_data->format = spec; m_activeSpecChanged.send(m_data->format); AudioStreamBasicDescription audioFormat; @@ -88,21 +92,12 @@ audioFormat.mFramesPerPacket = 1; audioFormat.mChannelsPerFrame = spec.output.audioChannelCount; audioFormat.mReserved = 0; - switch (activeSpec->format) { - case Format::AudioF32: - audioFormat.mBitsPerChannel = 32; - audioFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; - break; - case Format::AudioS16: - audioFormat.mBitsPerChannel = 16; - audioFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - break; - } + audioFormat.mBitsPerChannel = 32; + audioFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; audioFormat.mBytesPerPacket = audioFormat.mBitsPerChannel * spec.output.audioChannelCount / 8; audioFormat.mBytesPerFrame = audioFormat.mBytesPerPacket * audioFormat.mFramesPerPacket; m_data->callback = spec.callback; - m_data->mUserData = spec.userdata; OSStatus result = AudioQueueNewOutput(&audioFormat, OnFillBuffer, m_data.get(), NULL, NULL, 0, &m_data->audioQueue); if (result != noErr) { @@ -110,7 +105,7 @@ return false; } - audioQueueSetDeviceName(outputDevice()); + audioQueueSetDeviceName(m_data->format.deviceId); AudioValueRange bufferSizeRange = { 0, 0 }; UInt32 bufferSizeRangeSize = sizeof(AudioValueRange); @@ -164,7 +159,9 @@ return false; } - LOGI() << "Connected to " << outputDevice() << " with bufferSize " << bufferSizeOut << ", sampleRate " << spec.output.sampleRate; + LOGI() << "Connected to " << m_data->format.deviceId + << " with bufferSize " << bufferSizeOut + << ", sampleRate " << spec.output.sampleRate; return true; } @@ -216,11 +213,6 @@ return m_availableOutputDevicesChanged; } -muse::audio::AudioDeviceID OSXAudioDriver::outputDevice() const -{ - return m_deviceId; -} - void OSXAudioDriver::updateDeviceMap() { std::lock_guard lock(m_devicesMutex); @@ -306,34 +298,7 @@ m_availableOutputDevicesChanged.notify(); } -bool OSXAudioDriver::setOutputDeviceBufferSize(unsigned int bufferSize) -{ - if (m_data->format.output.samplesPerChannel == bufferSize) { - return true; - } - - bool reopen = isOpened(); - close(); - m_data->format.output.samplesPerChannel = bufferSize; - - bool ok = true; - if (reopen) { - ok = open(m_data->format, &m_data->format); - } - - if (ok) { - m_bufferSizeChanged.notify(); - } - - return ok; -} - -async::Notification OSXAudioDriver::outputDeviceBufferSizeChanged() const -{ - return m_bufferSizeChanged; -} - -std::vector OSXAudioDriver::availableOutputDeviceBufferSizes() const +std::vector OSXAudioDriver::availableOutputDeviceBufferSizes() const { OSXAudioDeviceID osxDeviceId = this->osxDeviceId(); AudioObjectPropertyAddress bufferFrameSizePropertyAddress = { @@ -346,15 +311,15 @@ UInt32 dataSize = sizeof(AudioValueRange); OSStatus rangeResult = AudioObjectGetPropertyData(osxDeviceId, &bufferFrameSizePropertyAddress, 0, NULL, &dataSize, &range); if (rangeResult != noErr) { - logError("Failed to get device " + outputDevice() + " bufferFrameSize, err: ", rangeResult); + logError("Failed to get device " + m_data->format.deviceId + " bufferFrameSize, err: ", rangeResult); return {}; } - unsigned int minimum = std::max(static_cast(range.mMinimum), MINIMUM_BUFFER_SIZE); - unsigned int maximum = std::min(static_cast(range.mMaximum), MAXIMUM_BUFFER_SIZE); + samples_t minimum = std::max(static_cast(range.mMinimum), MINIMUM_BUFFER_SIZE); + samples_t maximum = std::min(static_cast(range.mMaximum), MAXIMUM_BUFFER_SIZE); - std::vector result; - for (unsigned int bufferSize = maximum; bufferSize >= minimum;) { + std::vector result; + for (samples_t bufferSize = maximum; bufferSize >= minimum;) { result.push_back(bufferSize); bufferSize /= 2; } @@ -364,34 +329,7 @@ return result; } -bool OSXAudioDriver::setOutputDeviceSampleRate(unsigned int sampleRate) -{ - if (m_data->format.output.sampleRate == sampleRate) { - return true; - } - - bool reopen = isOpened(); - close(); - m_data->format.output.sampleRate = sampleRate; - - bool ok = true; - if (reopen) { - ok = open(m_data->format, &m_data->format); - } - - if (ok) { - m_sampleRateChanged.notify(); - } - - return ok; -} - -async::Notification OSXAudioDriver::outputDeviceSampleRateChanged() const -{ - return m_sampleRateChanged; -} - -std::vector OSXAudioDriver::availableOutputDeviceSampleRates() const +std::vector OSXAudioDriver::availableOutputDeviceSampleRates() const { return { 44100, @@ -463,7 +401,7 @@ UInt32 OSXAudioDriver::osxDeviceId() const { - AudioDeviceID deviceId = outputDevice(); + AudioDeviceID deviceId = m_data->format.deviceId; if (deviceId == DEFAULT_DEVICE_ID) { deviceId = defaultDeviceId(); } @@ -471,46 +409,6 @@ return QString::fromStdString(deviceId).toInt(); } -bool OSXAudioDriver::selectOutputDevice(const AudioDeviceID& deviceId /*, unsigned int bufferSize*/) -{ - if (m_deviceId == deviceId) { - return true; - } - - bool reopen = isOpened(); - close(); - m_deviceId = deviceId; - - bool ok = true; - if (reopen) { - ok = open(m_data->format, &m_data->format); - } - - if (ok) { - m_outputDeviceChanged.notify(); - } - - return ok; -} - -bool OSXAudioDriver::resetToDefaultOutputDevice() -{ - return selectOutputDevice(DEFAULT_DEVICE_ID); -} - -async::Notification OSXAudioDriver::outputDeviceChanged() const -{ - return m_outputDeviceChanged; -} - -void OSXAudioDriver::resume() -{ -} - -void OSXAudioDriver::suspend() -{ -} - void OSXAudioDriver::logError(const std::string message, OSStatus error) { if (error == noErr) { @@ -561,6 +459,6 @@ static OSStatus onDeviceListChanged(AudioObjectID inObjectID, UInt32 inNumberAdd void OSXAudioDriver::OnFillBuffer(void* context, AudioQueueRef, AudioQueueBufferRef buffer) { Data* pData = (Data*)context; - pData->callback(pData->mUserData, (uint8_t*)buffer->mAudioData, buffer->mAudioDataByteSize); + pData->callback((uint8_t*)buffer->mAudioData, buffer->mAudioDataByteSize); AudioQueueEnqueueBuffer(pData->audioQueue, buffer, 0, NULL); } diff --git a/src/framework/audio/driver/platform/win/audiodeviceslistener.cpp b/src/framework/audio/driver/platform/win/audiodeviceslistener.cpp deleted file mode 100644 index ac2718c95eab9..0000000000000 --- a/src/framework/audio/driver/platform/win/audiodeviceslistener.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "audiodeviceslistener.h" - -#include "log.h" - -using namespace muse; -using namespace muse::audio; - -AudioDevicesListener::AudioDevicesListener() -{ - HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&m_deviceEnumerator)); - - if (hr == CO_E_NOTINITIALIZED) { - CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); - hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&m_deviceEnumerator)); - - if (hr == CO_E_NOTINITIALIZED) { - LOGE() << "CoCreateInstance fails with CO_E_NOTINITIALIZED"; - return; - } - - m_successfullyInitializedCOM = true; - } - - if (!m_deviceEnumerator) { - LOGE() << "Failed to create device enumeration"; - return; - } - - hr = m_deviceEnumerator->RegisterEndpointNotificationCallback(this); - if (FAILED(hr)) { - LOGE() << "RegisterEndpointNotificationCallback failed"; - return; - } -} - -AudioDevicesListener::~AudioDevicesListener() -{ - if (m_deviceEnumerator) { - m_deviceEnumerator->UnregisterEndpointNotificationCallback(this); - } - - if (m_successfullyInitializedCOM) { - CoUninitialize(); - } -} - -async::Notification AudioDevicesListener::devicesChanged() const -{ - return m_devicesChanged; -} - -async::Notification AudioDevicesListener::defaultDeviceChanged() const -{ - return m_defaultDeviceChanged; -} - -ULONG AudioDevicesListener::AddRef() -{ - return 1; -} - -ULONG AudioDevicesListener::Release() -{ - return 1; -} - -HRESULT AudioDevicesListener::QueryInterface(const IID&, void**) -{ - return S_OK; -} - -HRESULT AudioDevicesListener::OnDeviceStateChanged(LPCWSTR, DWORD) -{ - return S_OK; -} - -HRESULT AudioDevicesListener::OnDeviceAdded(LPCWSTR) -{ - m_devicesChanged.notify(); - - return S_OK; -} - -HRESULT AudioDevicesListener::OnDeviceRemoved(LPCWSTR) -{ - m_devicesChanged.notify(); - - return S_OK; -} - -HRESULT AudioDevicesListener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) -{ - if ((role != eConsole && role != eCommunications) || (flow != eRender && flow != eCapture)) { - return S_OK; - } - - winrt::hstring newDefaultDeviceIdString = new_default_device_id ? new_default_device_id : winrt::hstring(); - - if (m_previousDefaultDeviceId == newDefaultDeviceIdString) { - return S_OK; - } - - m_previousDefaultDeviceId = newDefaultDeviceIdString; - - m_defaultDeviceChanged.notify(); - m_devicesChanged.notify(); - - return S_OK; -} - -HRESULT AudioDevicesListener::OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) -{ - return S_OK; -} diff --git a/src/framework/audio/driver/platform/win/audiodeviceslistener.h b/src/framework/audio/driver/platform/win/audiodeviceslistener.h deleted file mode 100644 index 93961f9677c1d..0000000000000 --- a/src/framework/audio/driver/platform/win/audiodeviceslistener.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include "wasapitypes.h" - -#include "global/async/notification.h" - -namespace muse::audio { -class AudioDevicesListener : public IMMNotificationClient -{ -public: - explicit AudioDevicesListener(); - ~AudioDevicesListener(); - - async::Notification devicesChanged() const; - async::Notification defaultDeviceChanged() const; - -private: - STDMETHOD_(ULONG, AddRef)(); - STDMETHOD_(ULONG, Release)(); - STDMETHOD(QueryInterface)(REFIID iid, void** object); - STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD new_state); - STDMETHOD(OnDeviceAdded)(LPCWSTR device_id); - STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id); - STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role, LPCWSTR new_default_device_id); - STDMETHOD(OnPropertyValueChanged)(LPCWSTR device_id, const PROPERTYKEY key); - - winrt::com_ptr m_deviceEnumerator; - winrt::hstring m_previousDefaultDeviceId; - - async::Notification m_devicesChanged; - async::Notification m_defaultDeviceChanged; - - bool m_successfullyInitializedCOM = false; -}; -} diff --git a/src/framework/audio/driver/platform/win/wasapiaudioclient.cpp b/src/framework/audio/driver/platform/win/wasapiaudioclient.cpp deleted file mode 100644 index 5f53a0a867e67..0000000000000 --- a/src/framework/audio/driver/platform/win/wasapiaudioclient.cpp +++ /dev/null @@ -1,705 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2022 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "wasapiaudioclient.h" - -#include "log.h" - -using namespace winrt; -using namespace winrt::Windows::Foundation; - -WasapiAudioClient::WasapiAudioClient(HANDLE clientStartedEvent, HANDLE clientFailedToStartEvent, HANDLE clientStoppedEvent) - : m_clientStartedEvent(clientStartedEvent), m_clientFailedToStartEvent(clientFailedToStartEvent), m_clientStoppedEvent( - clientStoppedEvent) -{ - check_hresult(MFStartup(MF_VERSION, MFSTARTUP_LITE)); -} - -WasapiAudioClient::~WasapiAudioClient() -{ - MFShutdown(); -} - -void WasapiAudioClient::setHardWareOffload(bool value) -{ - m_isHWOffload = value; -} - -void WasapiAudioClient::setBackgroundAudio(bool value) -{ - m_isBackground = value; -} - -void WasapiAudioClient::setRawAudio(bool value) -{ - m_isRawAudio = value; -} - -void WasapiAudioClient::setLowLatency(bool value) -{ - m_isLowLatency = value; -} - -void WasapiAudioClient::setBufferDuration(REFERENCE_TIME value) -{ - m_hnsBufferDuration = value; -} - -void WasapiAudioClient::setSampleRequestCallback(SampleRequestCallback callback) -{ - m_sampleRequestCallback = callback; -} - -unsigned int WasapiAudioClient::lowLatencyUpperBound() const -{ - //!Note See https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/low-latency-audio - - static constexpr unsigned int LOWER_BOUND_SAMPLES_PER_CHANNEL = 1024; - - return LOWER_BOUND_SAMPLES_PER_CHANNEL; -} - -unsigned int WasapiAudioClient::sampleRate() const -{ - if (!m_mixFormat.get()) { - return 0; - } - - return m_mixFormat.get()->nSamplesPerSec; -} - -unsigned int WasapiAudioClient::channelCount() const -{ - if (!m_mixFormat.get()) { - return 0; - } - - return m_mixFormat.get()->nChannels; -} - -unsigned int WasapiAudioClient::minPeriodInFrames() const -{ - return m_minPeriodInFrames; -} - -void WasapiAudioClient::setFallbackDevice(const hstring& deviceId) -{ - m_fallbackDeviceIdString = deviceId; -} - -void WasapiAudioClient::asyncInitializeAudioDevice(const hstring& deviceId, bool useClosestSupportedFormat) noexcept -{ - try { - // This call can be made safely from a background thread because we are asking for the IAudioClient3 - // interface of an audio device. Async operation will call back to - // IActivateAudioInterfaceCompletionHandler::ActivateCompleted, which must be an agile interface implementation - m_deviceIdString = deviceId; - - m_useClosestSupportedFormat = useClosestSupportedFormat; - m_deviceState = DeviceState::Uninitialized; - - com_ptr asyncOp; - check_hresult(ActivateAudioInterfaceAsync(m_deviceIdString.c_str(), __uuidof(IAudioClient3), nullptr, this, asyncOp.put())); - } catch (...) { - setStateAndNotify(DeviceState::Error, to_hresult()); - } -} - -static void logWAVEFORMATEX(WAVEFORMATEX* format) -{ - LOGI() << "Format tag: " << format->wFormatTag; - LOGI() << "Channels: " << format->nChannels; - LOGI() << "Sample rate: " << format->nSamplesPerSec; - LOGI() << "Average bytes per second: " << format->nAvgBytesPerSec; - LOGI() << "Block align: " << format->nBlockAlign; - LOGI() << "Bits per sample: " << format->wBitsPerSample; - LOGI() << "cbSize: " << format->cbSize; -} - -// -// ActivateCompleted() -// -// Callback implementation of ActivateAudioInterfaceAsync function. This will be called on MTA thread -// when results of the activation are available. -// -HRESULT WasapiAudioClient::ActivateCompleted(IActivateAudioInterfaceAsyncOperation* operation) noexcept -{ - try { - if (m_deviceState != DeviceState::Uninitialized) { - throw hresult_error(E_NOT_VALID_STATE); - } - - // Check for a successful activation result - HRESULT hrActivateResult = S_OK; - com_ptr<::IUnknown> punkAudioInterface; - check_hresult(operation->GetActivateResult(&hrActivateResult, punkAudioInterface.put())); - check_hresult(hrActivateResult); - - // Remember that we have been activated, but don't raise the event yet. - setState(DeviceState::Activated); - - // Get the pointer for the Audio Client - m_audioClient = punkAudioInterface.as(); - - // Configure user defined properties - check_hresult(configureDeviceInternal()); - - // Initialize the AudioClient in Shared Mode with the user specified buffer - if (!m_isLowLatency) { - check_hresult(m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, - m_hnsBufferDuration, - 0, - m_mixFormat.get(), - nullptr)); - } else { - check_hresult(m_audioClient->InitializeSharedAudioStream(AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - m_minPeriodInFrames, - m_mixFormat.get(), - nullptr)); - } - - LOGI() << "Initialized WASAPI audio endpoint with: "; - logWAVEFORMATEX(m_mixFormat.get()); - LOGI() << "HnsBufferDuration: " << m_hnsBufferDuration; - LOGI() << "Minimal period in frames: " << m_minPeriodInFrames; - LOGI() << "Default period in frames: " << m_defaultPeriodInFrames; - LOGI() << "Fundamental period in frames: " << m_fundamentalPeriodInFrames; - LOGI() << "Max period in frames: " << m_maxPeriodInFrames; - LOGI() << "Min period in frames: " << m_minPeriodInFrames; - - // Get the maximum size of the AudioClient Buffer - check_hresult(m_audioClient->GetBufferSize(&m_bufferFrames)); - LOGI() << "Buffer size: " << m_bufferFrames; - - // Get the render client - m_audioRenderClient.capture(m_audioClient, &IAudioClient::GetService); - - // Create Async callback for sample events - check_hresult(MFCreateAsyncResult(nullptr, &m_sampleReadyCallback, nullptr, m_sampleReadyAsyncResult.put())); - - // Sets the event handle that the system signals when an audio buffer is ready to be processed by the client - check_hresult(m_audioClient->SetEventHandle(m_sampleReadyEvent.get())); - - // Everything succeeded - setStateAndNotify(DeviceState::Initialized, S_OK); - - startPlayback(); - - return S_OK; - } catch (...) { - setStateAndNotify(DeviceState::Error, to_hresult()); - - m_audioClient = nullptr; - m_audioRenderClient = nullptr; - m_sampleReadyAsyncResult = nullptr; - - SetEvent(m_clientFailedToStartEvent); - - // Must return S_OK even on failure. - return S_OK; - } -} - -// -// GetBufferFramesPerPeriod() -// -// Get the time in seconds between passes of the audio device -// -UINT32 WasapiAudioClient::getBufferFramesPerPeriod() noexcept -{ - REFERENCE_TIME defaultDevicePeriod = 0; - REFERENCE_TIME minimumDevicePeriod = 0; - - if (m_isHWOffload) { - return m_bufferFrames; - } - - if (m_isLowLatency) { - return m_minPeriodInFrames; - } - - // Get the audio device period - HRESULT hr = m_audioClient->GetDevicePeriod(&defaultDevicePeriod, &minimumDevicePeriod); - if (FAILED(hr)) { - return 0; - } - - double devicePeriodInSeconds = defaultDevicePeriod / (10000.0 * 1000.0); - return static_cast(m_mixFormat->nSamplesPerSec * devicePeriodInSeconds + 0.5); -} - -// -// ConfigureDeviceInternal() -// -// Sets additional playback parameters and opts into hardware offload -// -HRESULT WasapiAudioClient::configureDeviceInternal() noexcept -{ - try { - if (m_deviceState != DeviceState::Activated) { - return E_NOT_VALID_STATE; - } - - // Opt into HW Offloading. If the endpoint does not support offload it will return AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE - AudioClientProperties audioProps = { 0 }; - audioProps.cbSize = sizeof(AudioClientProperties); - audioProps.bIsOffload = m_isHWOffload; - audioProps.eCategory = AudioCategory_Media; - - if (m_isRawAudio) { - audioProps.Options = AUDCLNT_STREAMOPTIONS_RAW; - } - - LOGI() << "WASAPI: Settings device client properties"; - check_hresult(m_audioClient->SetClientProperties(&audioProps)); - - // If application already has a preferred source format available, - // it can test whether the format is supported by the device: - // - // unique_cotaskmem_ptr applicationFormat = { ... }; - // if (S_OK == m_AudioClient->IsFormatSupported(applicationFormat.get())) - // { - // m_MixFormat = std::move(applicationFormat); - // } - // else - // { - // // device does not support the application format, so ask the device what format it prefers - // check_hresult(m_AudioClient->GetMixFormat(&m_MixFormat.put())); - // } - - // This sample opens the device is shared mode so we need to find the supported WAVEFORMATEX mix format - LOGI() << "WASAPI: Getting device mix format"; - check_hresult(m_audioClient->GetMixFormat(m_mixFormat.put())); - - LOGI() << "WASAPI: Mix format after getting from audio client:"; - logWAVEFORMATEX(m_mixFormat.get()); - - m_mixFormat->wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - m_mixFormat->nChannels = 2; - m_mixFormat->wBitsPerSample = 32; - m_mixFormat->nAvgBytesPerSec = m_mixFormat->nSamplesPerSec * m_mixFormat->nChannels * sizeof(float); - m_mixFormat->nBlockAlign = (m_mixFormat->nChannels * m_mixFormat->wBitsPerSample) / 8; - m_mixFormat->cbSize = 0; - - LOGI() << "WASAPI: Modified mix format:"; - logWAVEFORMATEX(m_mixFormat.get()); - - if (m_useClosestSupportedFormat) { - LOGI() << "WASAPI: Querying closest supported format"; - - unique_cotaskmem_ptr closestSupported; - m_audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, m_mixFormat.get(), closestSupported.put()); - - // According to the documentation, closestSupported may be null - if (closestSupported) { - LOGI() << "WASAPI: Closest supported format:"; - logWAVEFORMATEX(closestSupported.get()); - m_mixFormat = std::move(closestSupported); - } else { - LOGW() << "WASAPI: Could not query closest supported format"; - } - } - - if (!audioProps.bIsOffload) { - LOGI() << "WASAPI: Getting shared mode engine period"; - // The wfx parameter below is optional (Its needed only for MATCH_FORMAT clients). Otherwise, wfx will be assumed - // to be the current engine format based on the processing mode for this stream - check_hresult(m_audioClient->GetSharedModeEnginePeriod(m_mixFormat.get(), &m_defaultPeriodInFrames, - &m_fundamentalPeriodInFrames, - &m_minPeriodInFrames, &m_maxPeriodInFrames)); - } - - LOGI() << "WASAPI: Device successfully configured"; - - return S_OK; - } catch (...) { - return to_hresult(); - } -} - -// -// ValidateBufferValue() -// -// Verifies the user specified buffer value for hardware offload -// Throws an exception on failure. -// -void WasapiAudioClient::validateBufferValue() -{ - if (!m_isHWOffload) { - // If we aren't using HW Offload, set this to 0 to use the default value - m_hnsBufferDuration = 0; - return; - } - - REFERENCE_TIME hnsMinBufferDuration; - REFERENCE_TIME hnsMaxBufferDuration; - - check_hresult(m_audioClient->GetBufferSizeLimits(m_mixFormat.get(), true, &hnsMinBufferDuration, &hnsMaxBufferDuration)); - if (m_hnsBufferDuration < hnsMinBufferDuration) { - // using MINIMUM size instead - m_hnsBufferDuration = hnsMinBufferDuration; - } else if (m_hnsBufferDuration > hnsMaxBufferDuration) { - // using MAXIMUM size instead - m_hnsBufferDuration = hnsMaxBufferDuration; - } -} - -// -// StartPlaybackAsync() -// -// Starts asynchronous playback on a separate thread via MF Work Item -// Errors are reported via the DeviceStateChanged event. -// -void WasapiAudioClient::startPlayback() noexcept -{ - try { - switch (m_deviceState) { - // We should be stopped if the user stopped playback, or we should be - // initialized if this is the first time through getting ready to playback. - case DeviceState::Stopped: - case DeviceState::Initialized: - setStateAndNotify(DeviceState::Starting, S_OK); - check_hresult(MFPutWorkItem2(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_startPlaybackCallback, nullptr)); - break; - - default: - // Otherwise something else happened - throw hresult_error(E_FAIL); - } - } catch (...) { - hresult error = to_hresult(); - setStateAndNotify(DeviceState::Error, error); - - SetEvent(m_clientFailedToStartEvent); - } -} - -// -// OnStartPlayback() -// -// Callback method to start playback -// -HRESULT WasapiAudioClient::onStartPlayback(IMFAsyncResult*) noexcept -{ - try { - // Pre-Roll the buffer with silence - onAudioSampleRequested(true); - - // Set the initial volume. - //SetAudioClientChannelVolume(); - - // Actually start the playback - check_hresult(m_audioClient->Start()); - - setStateAndNotify(DeviceState::Playing, S_OK); - check_hresult(MFPutWaitingWorkItem(m_sampleReadyEvent.get(), 0, m_sampleReadyAsyncResult.get(), &m_sampleReadyKey)); - - SetEvent(m_clientStartedEvent); - - return S_OK; - } catch (...) { - setStateAndNotify(DeviceState::Error, to_hresult()); - - SetEvent(m_clientFailedToStartEvent); - - // Must return S_OK. - return S_OK; - } -} - -// -// StopPlaybackAsync() -// -// Stop playback asynchronously via MF Work Item -// -HRESULT WasapiAudioClient::stopPlaybackAsync() noexcept -{ - if ((m_deviceState != DeviceState::Playing) - && (m_deviceState != DeviceState::Paused) - && (m_deviceState != DeviceState::Error)) { - return E_NOT_VALID_STATE; - } - - setStateAndNotify(DeviceState::Stopping, S_OK); - - return MFPutWorkItem2(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_stopPlaybackCallback, nullptr); -} - -// -// OnStopPlayback() -// -// Callback method to stop playback -// -HRESULT WasapiAudioClient::onStopPlayback(IMFAsyncResult*) -{ - // Stop playback by cancelling Work Item - // Cancel the queued work item (if any) - - if (0 != m_sampleReadyKey) { - MFCancelWorkItem(m_sampleReadyKey); - m_sampleReadyKey = 0; - } - - // Flush anything left in buffer with silence, best effort. - try { - onAudioSampleRequested(true); - } catch (...) { } - - m_audioClient->Stop(); - m_sampleReadyAsyncResult = nullptr; - - setStateAndNotify(DeviceState::Stopped, S_OK); - - SetEvent(m_clientStoppedEvent); - - return S_OK; -} - -// -// OnSampleReady() -// -// Callback method when ready to fill sample buffer -// -HRESULT WasapiAudioClient::onSampleReady(IMFAsyncResult*) -{ - try { - onAudioSampleRequested(false); - - // Re-queue work item for next sample - if (m_deviceState == DeviceState::Playing) { - check_hresult(MFPutWaitingWorkItem(m_sampleReadyEvent.get(), 0, m_sampleReadyAsyncResult.get(), &m_sampleReadyKey)); - } - - return S_OK; - } catch (...) { - hresult error = to_hresult(); - setStateAndNotify(DeviceState::Error, error); - return error; - } -} - -// -// OnAudioSampleRequested() -// -// Called when audio device fires m_SampleReadyEvent -// -void WasapiAudioClient::onAudioSampleRequested(bool IsSilence) -{ - try { - auto guard = slim_lock_guard(m_lock); - - // Get padding in existing buffer - UINT32 PaddingFrames = 0; - check_hresult(m_audioClient->GetCurrentPadding(&PaddingFrames)); - - // GetCurrentPadding represents the number of queued frames - // so we can subtract that from the overall number of frames we have - uint32_t framesAvailable = m_bufferFrames - PaddingFrames; - - // Only continue if we have buffer to write data - if (framesAvailable == 0) { - return; - } - - if (IsSilence) { - // Fill the buffer with silence - uint8_t* data; - check_hresult(m_audioRenderClient->GetBuffer(framesAvailable, &data)); - check_hresult(m_audioRenderClient->ReleaseBuffer(framesAvailable, AUDCLNT_BUFFERFLAGS_SILENT)); - return; - } - - // Even if we cancel a work item, this may still fire due to the async - // nature of things. There should be a queued work item already to handle - // the process of stopping or stopped - if (m_deviceState == DeviceState::Playing) { - // Fill the buffer with a playback sample - getSamples(framesAvailable); - } - } - catch (hresult_error const& error) - { - if (error.code() != AUDCLNT_E_RESOURCES_INVALIDATED) { - throw; - } - - // Attempt auto-recovery from loss of resources. - LOGI() << "Attempting to auto-recovery audio endpoint"; - - setState(DeviceState::Uninitialized); - m_audioClient = nullptr; - m_audioRenderClient = nullptr; - m_sampleReadyAsyncResult = nullptr; - - asyncInitializeAudioDevice(m_fallbackDeviceIdString); - } -} - -void WasapiAudioClient::getSamples(uint32_t framesAvailable) -{ - uint8_t* data; - - uint32_t actualFramesToRead = framesAvailable; - - // WASAPI: "nBlockAlign must be equal to the product of nChannels and wBitsPerSample divided by 8 (bits per byte)" - const uint32_t clientFrameSize = m_mixFormat->nBlockAlign; - - // MuseScore assumes only 2 audio channels (same calculation as above to determine frame size) - const uint32_t muFrameSize = 2 * m_mixFormat->wBitsPerSample / 8; - - check_hresult(m_audioRenderClient->GetBuffer(actualFramesToRead, &data)); - if (actualFramesToRead > 0) { - // Based on the previous calculations, the only way that clientFrameSize will be larger than muFrameSize is - // if the client specifies more than 2 channels. MuseScore doesn't support this (yet), so we use a workaround - // where the missing channels are padded with zeroes... - if (clientFrameSize > muFrameSize) { - const size_t surroundBufferDesiredSize = actualFramesToRead * muFrameSize; - if (m_surroundAudioBuffer.size() < surroundBufferDesiredSize) { - m_surroundAudioBuffer.resize(surroundBufferDesiredSize, 0); - } - - m_sampleRequestCallback(nullptr, m_surroundAudioBuffer.data(), (int)surroundBufferDesiredSize); - - for (uint32_t i = 0; i < actualFramesToRead; ++i) { - uint8_t* frameStartPos = data + i * clientFrameSize; - std::memcpy(frameStartPos, m_surroundAudioBuffer.data() + i * muFrameSize, muFrameSize); - std::memset(frameStartPos + muFrameSize, 0, clientFrameSize - muFrameSize); - } - } else { - m_sampleRequestCallback(nullptr, data, actualFramesToRead * clientFrameSize); - } - } - check_hresult(m_audioRenderClient->ReleaseBuffer(actualFramesToRead, 0)); -} - -void WasapiAudioClient::setState(const DeviceState newState) -{ - if (m_deviceState == newState) { - return; - } - - m_deviceState = newState; -} - -void WasapiAudioClient::setStateAndNotify(const DeviceState newState, hresult resultCode) -{ - if (m_deviceState == newState) { - return; - } - - std::string errMsg; - - switch (resultCode) { - case AUDCLNT_E_NOT_INITIALIZED: errMsg = "AUDCLNT_E_NOT_INITIALIZED"; - break; - case AUDCLNT_E_ALREADY_INITIALIZED: errMsg = "AUDCLNT_E_ALREADY_INITIALIZED"; - break; - case AUDCLNT_E_WRONG_ENDPOINT_TYPE: errMsg = "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; - break; - case AUDCLNT_E_DEVICE_INVALIDATED: errMsg = "AUDCLNT_E_DEVICE_INVALIDATED"; - break; - case AUDCLNT_E_NOT_STOPPED: errMsg = "AUDCLNT_E_NOT_STOPPED"; - break; - case AUDCLNT_E_BUFFER_TOO_LARGE: errMsg = "AUDCLNT_E_BUFFER_TOO_LARGE"; - break; - case AUDCLNT_E_OUT_OF_ORDER: errMsg = "AUDCLNT_E_OUT_OF_ORDER"; - break; - case AUDCLNT_E_UNSUPPORTED_FORMAT: errMsg = "AUDCLNT_E_UNSUPPORTED_FORMAT"; - break; - case AUDCLNT_E_INVALID_SIZE: errMsg = "AUDCLNT_E_INVALID_SIZE"; - break; - case AUDCLNT_E_DEVICE_IN_USE: errMsg = "AUDCLNT_E_DEVICE_IN_USE"; - break; - case AUDCLNT_E_BUFFER_OPERATION_PENDING: errMsg = "AUDCLNT_E_BUFFER_OPERATION_PENDING"; - break; - case AUDCLNT_E_THREAD_NOT_REGISTERED: errMsg = "AUDCLNT_E_THREAD_NOT_REGISTERED"; - break; - case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: errMsg = "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; - break; - case AUDCLNT_E_ENDPOINT_CREATE_FAILED: errMsg = "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; - break; - case AUDCLNT_E_SERVICE_NOT_RUNNING: errMsg = "AUDCLNT_E_SERVICE_NOT_RUNNING"; - break; - case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: errMsg = "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; - break; - case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: errMsg = "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; - break; - case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: errMsg = "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; - break; - case AUDCLNT_E_EVENTHANDLE_NOT_SET: errMsg = "AUDCLNT_E_EVENTHANDLE_NOT_SET"; - break; - case AUDCLNT_E_INCORRECT_BUFFER_SIZE: errMsg = "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; - break; - case AUDCLNT_E_BUFFER_SIZE_ERROR: errMsg = "AUDCLNT_E_BUFFER_SIZE_ERROR"; - break; - case AUDCLNT_E_CPUUSAGE_EXCEEDED: errMsg = "AUDCLNT_E_CPUUSAGE_EXCEEDED"; - break; - case AUDCLNT_E_BUFFER_ERROR: errMsg = "AUDCLNT_E_BUFFER_ERROR"; - break; - case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: errMsg = "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; - break; - case AUDCLNT_E_INVALID_DEVICE_PERIOD: errMsg = "AUDCLNT_E_INVALID_DEVICE_PERIOD"; - break; - case AUDCLNT_E_INVALID_STREAM_FLAG: errMsg = "AUDCLNT_E_INVALID_STREAM_FLAG"; - break; - case AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE: errMsg = "AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE"; - break; - case AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES: errMsg = "AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES"; - break; - case AUDCLNT_E_OFFLOAD_MODE_ONLY: errMsg = "AUDCLNT_E_OFFLOAD_MODE_ONLY"; - break; - case AUDCLNT_E_NONOFFLOAD_MODE_ONLY: errMsg = "AUDCLNT_E_NONOFFLOAD_MODE_ONLY"; - break; - case AUDCLNT_E_RESOURCES_INVALIDATED: errMsg = "AUDCLNT_E_RESOURCES_INVALIDATED"; - break; - case AUDCLNT_E_RAW_MODE_UNSUPPORTED: errMsg = "AUDCLNT_E_RAW_MODE_UNSUPPORTED"; - break; - case AUDCLNT_E_ENGINE_PERIODICITY_LOCKED: errMsg = "AUDCLNT_E_ENGINE_PERIODICITY_LOCKED"; - break; - case AUDCLNT_E_ENGINE_FORMAT_LOCKED: errMsg = "AUDCLNT_E_ENGINE_FORMAT_LOCKED"; - break; - case AUDCLNT_E_HEADTRACKING_ENABLED: errMsg = "AUDCLNT_E_HEADTRACKING_ENABLED"; - break; - case AUDCLNT_E_HEADTRACKING_UNSUPPORTED: errMsg = "AUDCLNT_E_HEADTRACKING_UNSUPPORTED"; - break; - case AUDCLNT_S_BUFFER_EMPTY: errMsg = "AUDCLNT_S_BUFFER_EMPTY"; - break; - case AUDCLNT_S_THREAD_ALREADY_REGISTERED: errMsg = "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; - break; - case AUDCLNT_S_POSITION_STALLED: errMsg = "AUDCLNT_S_POSITION_STALLED"; - break; - - case S_OK: - break; - - default: - errMsg = "ERROR: " + std::to_string(resultCode) + " " + to_string(hresult_error(resultCode).message()); - break; - } - - if (!errMsg.empty()) { - LOGE() << errMsg; - } - - m_deviceState = newState; -} diff --git a/src/framework/audio/driver/platform/win/wasapiaudioclient.h b/src/framework/audio/driver/platform/win/wasapiaudioclient.h deleted file mode 100644 index 98ad8b40463fc..0000000000000 --- a/src/framework/audio/driver/platform/win/wasapiaudioclient.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2022 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include "wasapitypes.h" - -namespace winrt { -struct WasapiAudioClient : implements -{ -public: - WasapiAudioClient(HANDLE clientStartedEvent, HANDLE clientFailedToStartEvent, HANDLE clientStoppedEvent); - ~WasapiAudioClient(); - - void setHardWareOffload(bool value); - void setBackgroundAudio(bool value); - void setRawAudio(bool value); - void setLowLatency(bool value); - void setBufferDuration(REFERENCE_TIME value); - void setSampleRequestCallback(SampleRequestCallback callback); - - unsigned int lowLatencyUpperBound() const; - - unsigned int sampleRate() const; - unsigned int channelCount() const; - unsigned int minPeriodInFrames() const; - - void setFallbackDevice(const hstring& deviceId); - - void asyncInitializeAudioDevice(const hstring& deviceId, bool useClosestSupportedFormat = false) noexcept; - void startPlayback() noexcept; - HRESULT stopPlaybackAsync() noexcept; - - // IActivateAudioInterfaceCompletionHandler - STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation * operation) noexcept; -private: - HRESULT onStartPlayback(IMFAsyncResult* pResult) noexcept; - HRESULT onStopPlayback(IMFAsyncResult* pResult); - HRESULT onSampleReady(IMFAsyncResult* pResult); - - EmbeddedMFAsyncCallback<&WasapiAudioClient::onStartPlayback> m_startPlaybackCallback{ this }; - EmbeddedMFAsyncCallback<&WasapiAudioClient::onStopPlayback> m_stopPlaybackCallback{ this }; - EmbeddedMFAsyncCallback<&WasapiAudioClient::onSampleReady> m_sampleReadyCallback{ this }; - - HRESULT configureDeviceInternal() noexcept; - void validateBufferValue(); - void onAudioSampleRequested(bool IsSilence = false); - UINT32 getBufferFramesPerPeriod() noexcept; - - void getSamples(uint32_t framesAvailable); - void setState(const DeviceState newState); - void setStateAndNotify(const DeviceState newState, hresult resultCode); - - std::vector m_surroundAudioBuffer; //! NOTE: See #17648 - - hstring m_deviceIdString; - hstring m_fallbackDeviceIdString; - uint32_t m_bufferFrames = 0; - - // Event for sample ready or user stop - handle m_sampleReadyEvent{ check_pointer(CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS)) }; - - MFWORKITEM_KEY m_sampleReadyKey; - mutable slim_mutex m_lock; - - mutable unique_cotaskmem_ptr m_mixFormat; - uint32_t m_defaultPeriodInFrames; - uint32_t m_fundamentalPeriodInFrames; - uint32_t m_maxPeriodInFrames; - uint32_t m_minPeriodInFrames; - - com_ptr m_audioClient; - com_ptr m_audioRenderClient; - com_ptr m_sampleReadyAsyncResult; - - bool m_isHWOffload = false; - bool m_isBackground = false; - bool m_isRawAudio = false; - bool m_isLowLatency = false; - REFERENCE_TIME m_hnsBufferDuration = 0; - - DeviceState m_deviceState = DeviceState::Uninitialized; - SampleRequestCallback m_sampleRequestCallback; - HANDLE m_clientStartedEvent; - HANDLE m_clientFailedToStartEvent; - HANDLE m_clientStoppedEvent; - - bool m_useClosestSupportedFormat = false; -}; -} diff --git a/src/framework/audio/driver/platform/win/wasapiaudiodriver.cpp b/src/framework/audio/driver/platform/win/wasapiaudiodriver.cpp index beb066660d396..fdb2668dc6640 100644 --- a/src/framework/audio/driver/platform/win/wasapiaudiodriver.cpp +++ b/src/framework/audio/driver/platform/win/wasapiaudiodriver.cpp @@ -5,7 +5,7 @@ * MuseScore * Music Composition & Notation * - * Copyright (C) 2022 MuseScore Limited and others + * Copyright (C) 2025 MuseScore Limited and others * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -19,370 +19,585 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - #include "wasapiaudiodriver.h" -#include "muse_framework_config.h" -#ifdef QT_CONCURRENT_SUPPORTED -#include "concurrency/concurrent.h" -#endif +#include +#include +#include +#include -#include "wasapitypes.h" -#include "wasapiaudioclient.h" -#include "audiodeviceslistener.h" +#include "global/defer.h" -#include "translation.h" #include "log.h" -using namespace winrt; using namespace muse; using namespace muse::audio; -inline int refTimeToSamples(const REFERENCE_TIME& t, double sampleRate) noexcept +struct WasapiAudioDriver::DeviceListener : public IMMNotificationClient { - return sampleRate * ((double)t) * 0.0000001; -} + DeviceListener(WasapiAudioDriver* driver) + : m_driver(driver) {} -inline REFERENCE_TIME samplesToRefTime(int numSamples, double sampleRate) noexcept -{ - return (REFERENCE_TIME)((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); -} + STDMETHOD_(ULONG, AddRef)() { + return 1; + } + STDMETHOD_(ULONG, Release)() { + return 1; + } + STDMETHOD(QueryInterface)(REFIID, void**) { + return S_OK; + } + STDMETHOD(OnDeviceStateChanged)(LPCWSTR, DWORD) { + return S_OK; + } + STDMETHOD(OnDeviceAdded)(LPCWSTR) { + m_driver->updateAudioDeviceList(); + return S_OK; + } + STDMETHOD(OnDeviceRemoved)(LPCWSTR) { + m_driver->updateAudioDeviceList(); + return S_OK; + } + STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role, LPCWSTR) { + if ((role != eConsole && role != eCommunications) || (flow != eRender)) { + return S_OK; + } + + m_driver->updateAudioDeviceList(); -struct WasapiData { - HANDLE clientStartedEvent; - HANDLE clientFailedToStartEvent; - HANDLE clientStoppedEvent; + return S_OK; + } + STDMETHOD(OnPropertyValueChanged)(LPCWSTR, const PROPERTYKEY) { + return S_OK; + } - winrt::com_ptr wasapiClient; + WasapiAudioDriver* m_driver = nullptr; }; -static WasapiData s_wdata; +struct WasapiAudioDriver::Data { + IMMDeviceEnumerator* enumerator = nullptr; + IAudioClient* audioClient = nullptr; + IAudioRenderClient* renderClient = nullptr; + WAVEFORMATEX* mixFormat = nullptr; + HANDLE audioEvent = nullptr; + + void releaseClient() + { + if (audioEvent) { + CloseHandle(audioEvent); + audioEvent = nullptr; + } -WasapiAudioDriver::WasapiAudioDriver() -{ - s_wdata.clientStartedEvent = CreateEvent(NULL, FALSE, FALSE, L"WASAPI_Client_Started"); - s_wdata.clientFailedToStartEvent = CreateEvent(NULL, FALSE, FALSE, L"WASAPI_Client_Failed_To_Start"); - s_wdata.clientStoppedEvent = CreateEvent(NULL, FALSE, FALSE, L"WASAPI_Client_Stopped"); - - m_devicesListener = std::make_unique(); - m_devicesListener->devicesChanged().onNotify(this, [this]() { - updateAvailableOutputDevices(); - }); - - m_devicesListener->defaultDeviceChanged().onNotify(this, [this]() { - if (s_wdata.wasapiClient.get()) { - s_wdata.wasapiClient->setFallbackDevice(to_hstring(this->defaultDeviceId())); + if (renderClient) { + renderClient->Release(); + renderClient = nullptr; } - if (m_deviceId == DEFAULT_DEVICE_ID) { - reopen(); + if (audioClient) { + audioClient->Release(); + audioClient = nullptr; } - }); -} -void WasapiAudioDriver::init() + if (mixFormat) { + CoTaskMemFree(mixFormat); + mixFormat = nullptr; + } + } + + void releaseAll() + { + releaseClient(); + if (enumerator) { + enumerator->Release(); + enumerator = nullptr; + } + } +}; + +static REFERENCE_TIME to_reftime(int numSamples, double sampleRate) { - updateAvailableOutputDevices(); + return (REFERENCE_TIME)((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); } -std::string WasapiAudioDriver::name() const +static samples_t to_samples(const REFERENCE_TIME& t, double sampleRate) { - return "WASAPI_driver"; + return sampleRate * ((double)t) * 0.0000001; } -bool WasapiAudioDriver::open(const Spec& spec, Spec* activeSpec) +static std::wstring to_wstring(const std::string& str) { - if (!s_wdata.wasapiClient.get()) { - s_wdata.wasapiClient = make_self(s_wdata.clientStartedEvent, - s_wdata.clientFailedToStartEvent, - s_wdata.clientStoppedEvent); + if (str.empty()) { + return L""; } - m_desiredSpec = spec; - - s_wdata.wasapiClient->setBufferDuration(samplesToRefTime(spec.output.samplesPerChannel, spec.output.sampleRate)); - - bool lowLatencyModeRequired = spec.output.samplesPerChannel <= s_wdata.wasapiClient->lowLatencyUpperBound(); - - s_wdata.wasapiClient->setLowLatency(lowLatencyModeRequired); - s_wdata.wasapiClient->setSampleRequestCallback(spec.callback); - - LOGI() << "WASAPI: trying to open the audio end-point with" - << " the following sample rate - " << spec.output.sampleRate; - LOGI() << "WASAPI: trying to open the audio end-point with" - << " the following samples per channel number - " << spec.output.samplesPerChannel; - - hstring deviceId; - hstring defaultDeviceId = to_hstring(this->defaultDeviceId()); - - if (m_deviceId.empty() || m_deviceId == DEFAULT_DEVICE_ID) { - deviceId = defaultDeviceId; - } else { - deviceId = to_hstring(m_deviceId); + int wide_len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); + if (wide_len == 0) { + return L""; } - s_wdata.wasapiClient->setFallbackDevice(defaultDeviceId); - s_wdata.wasapiClient->asyncInitializeAudioDevice(deviceId); - - static constexpr DWORD handleCount = 2; - const HANDLE handles[handleCount] = { s_wdata.clientStartedEvent, s_wdata.clientFailedToStartEvent }; - - DWORD waitResult = WaitForMultipleObjects(handleCount, handles, false, INFINITE); - if (waitResult != WAIT_OBJECT_0) { - // Either the event was the second event (namely s_data.clientFailedToStartEvent) - // Or some wait error occurred + std::wstring result(wide_len - 1, 0); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], wide_len); - LOGE() << "WASAPI: error open the device " << to_string(deviceId) << ", trying to use closest supported format"; - - static constexpr bool USE_CLOSEST_SUPPORTED_FORMAT = true; - s_wdata.wasapiClient->asyncInitializeAudioDevice(deviceId, USE_CLOSEST_SUPPORTED_FORMAT); + return result; +} - waitResult = WaitForMultipleObjects(handleCount, handles, false, INFINITE); - if (waitResult != WAIT_OBJECT_0) { - return false; - } +static std::string to_string(LPWSTR wstr) +{ + if (!wstr || wstr[0] == L'\0') { + return ""; } - m_activeSpec = m_desiredSpec; - m_activeSpec.output.sampleRate = s_wdata.wasapiClient->sampleRate(); - m_activeSpec.output.samplesPerChannel = std::max(m_activeSpec.output.samplesPerChannel, - static_cast(minSupportedBufferSize())); - *activeSpec = m_activeSpec; + int multi_len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); + if (multi_len == 0) { + return ""; + } - m_isOpened = true; + std::string result(multi_len - 1, 0); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &result[0], multi_len, nullptr, nullptr); - return true; + return result; } -void WasapiAudioDriver::close() +static std::string get_deviceId(IMMDevice* device) { - if (!s_wdata.wasapiClient.get()) { - return; + LPWSTR devId; + HRESULT hr = device->GetId(&devId); + if (FAILED(hr)) { + LOGE() << "failed get deviceId"; + return std::string(); } + std::string str = to_string(devId); + CoTaskMemFree(devId); + return str; +} - s_wdata.wasapiClient->stopPlaybackAsync(); - - WaitForSingleObject(s_wdata.clientStoppedEvent, INFINITE); - s_wdata.wasapiClient = nullptr; +static std::string hrToString(HRESULT hr) +{ + if (SUCCEEDED(hr)) { + return "SUCCEEDED"; + } - m_isOpened = false; + switch (hr) { + // Common WASAPI errors + case AUDCLNT_E_NOT_INITIALIZED: return "AUDCLNT_E_NOT_INITIALIZED - Audio client not initialized"; + case AUDCLNT_E_ALREADY_INITIALIZED: return "AUDCLNT_E_ALREADY_INITIALIZED - Audio client already initialized"; + case AUDCLNT_E_WRONG_ENDPOINT_TYPE: return "AUDCLNT_E_WRONG_ENDPOINT_TYPE - Wrong endpoint type"; + case AUDCLNT_E_DEVICE_INVALIDATED: return "AUDCLNT_E_DEVICE_INVALIDATED - Device invalidated"; + case AUDCLNT_E_NOT_STOPPED: return "AUDCLNT_E_NOT_STOPPED - Audio client not stopped"; + case AUDCLNT_E_BUFFER_TOO_LARGE: return "AUDCLNT_E_BUFFER_TOO_LARGE - Buffer too large"; + case AUDCLNT_E_OUT_OF_ORDER: return "AUDCLNT_E_OUT_OF_ORDER - Operation out of order"; + case AUDCLNT_E_UNSUPPORTED_FORMAT: return "AUDCLNT_E_UNSUPPORTED_FORMAT - Unsupported format"; + case AUDCLNT_E_INVALID_SIZE: return "AUDCLNT_E_INVALID_SIZE - Invalid size"; + case AUDCLNT_E_DEVICE_IN_USE: return "AUDCLNT_E_DEVICE_IN_USE - Device in use"; + case AUDCLNT_E_BUFFER_OPERATION_PENDING: return "AUDCLNT_E_BUFFER_OPERATION_PENDING - Buffer operation pending"; + case AUDCLNT_E_THREAD_NOT_REGISTERED: return "AUDCLNT_E_THREAD_NOT_REGISTERED - Thread not registered"; + case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: return "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED - Exclusive mode not allowed"; + case AUDCLNT_E_ENDPOINT_CREATE_FAILED: return "AUDCLNT_E_ENDPOINT_CREATE_FAILED - Endpoint create failed"; + case AUDCLNT_E_SERVICE_NOT_RUNNING: return "AUDCLNT_E_SERVICE_NOT_RUNNING - Audio service not running"; + case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: return "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED - Event handle not expected"; + case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: return "AUDCLNT_E_EXCLUSIVE_MODE_ONLY - Exclusive mode only"; + case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: return "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL - Buffer duration/period not equal"; + case AUDCLNT_E_EVENTHANDLE_NOT_SET: return "AUDCLNT_E_EVENTHANDLE_NOT_SET - Event handle not set"; + case AUDCLNT_E_INCORRECT_BUFFER_SIZE: return "AUDCLNT_E_INCORRECT_BUFFER_SIZE - Incorrect buffer size"; + case AUDCLNT_E_BUFFER_SIZE_ERROR: return "AUDCLNT_E_BUFFER_SIZE_ERROR - Buffer size error"; + case AUDCLNT_E_CPUUSAGE_EXCEEDED: return "AUDCLNT_E_CPUUSAGE_EXCEEDED - CPU usage exceeded"; + + // Common COM errors + case E_INVALIDARG: return "E_INVALIDARG - Invalid arguments"; + case E_OUTOFMEMORY: return "E_OUTOFMEMORY - Out of memory"; + case E_NOINTERFACE: return "E_NOINTERFACE - No interface"; + case E_POINTER: return "E_POINTER - Invalid pointer"; + case E_ACCESSDENIED: return "E_ACCESSDENIED - Access denied"; + case E_FAIL: return "E_FAIL - Unspecified error"; + + default: + return "Unknown error: " + std::to_string(hr); + } } -bool WasapiAudioDriver::isOpened() const +WasapiAudioDriver::WasapiAudioDriver() { - return m_isOpened; + m_data = std::make_shared(); + m_deviceListener = std::make_shared(this); } -const WasapiAudioDriver::Spec& WasapiAudioDriver::activeSpec() const +WasapiAudioDriver::~WasapiAudioDriver() { - return m_activeSpec; + close(); + + if (m_data->enumerator) { + m_data->enumerator->UnregisterEndpointNotificationCallback(m_deviceListener.get()); + } + + m_data->releaseAll(); + + CoUninitialize(); } -AudioDeviceID WasapiAudioDriver::outputDevice() const +std::string WasapiAudioDriver::name() const { - return m_deviceId; + return "WASAPI"; } -bool WasapiAudioDriver::selectOutputDevice(const AudioDeviceID& id) +void WasapiAudioDriver::init() { - bool result = true; - - if (m_deviceId == id) { - return result; + LOGI() << "begin driver init"; + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr)) { + LOGE() << "failed CoInitializeEx, error: " << hrToString(hr); } - m_deviceId = id; + hr = CoCreateInstance( + __uuidof(MMDeviceEnumerator), + nullptr, + CLSCTX_ALL, + __uuidof(IMMDeviceEnumerator), + (void**)&m_data->enumerator + ); - if (isOpened()) { - close(); - result = open(m_activeSpec, &m_activeSpec); + if (FAILED(hr)) { + LOGE() << "failed create DeviceEnumerator, error: " << hrToString(hr); + return; } - if (result) { - m_outputDeviceChanged.notify(); + hr = m_data->enumerator->RegisterEndpointNotificationCallback(m_deviceListener.get()); + if (FAILED(hr)) { + LOGE() << "failed RegisterEndpointNotificationCallback, error: " << hrToString(hr); } - return result; -} + updateAudioDeviceList(); -bool WasapiAudioDriver::resetToDefaultOutputDevice() -{ - return selectOutputDevice(DEFAULT_DEVICE_ID); + LOGI() << "success driver init"; } -async::Notification WasapiAudioDriver::outputDeviceChanged() const +static std::pair, std::string /*defaultId*/> audioDevices(IMMDeviceEnumerator* enumerator) { - return m_outputDeviceChanged; -} + IF_ASSERT_FAILED(enumerator) { + return {}; + } -AudioDeviceList WasapiAudioDriver::availableOutputDevices() const -{ - std::lock_guard lock(m_availableOutputDevicesMutex); - return m_availableOutputDevices; -} + IMMDeviceCollection* pCollection = nullptr; + HRESULT hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pCollection); + if (FAILED(hr)) { + LOGE() << "failed get EnumAudioEndpoints, error: " << hrToString(hr); + return {}; + } -async::Notification WasapiAudioDriver::availableOutputDevicesChanged() const -{ - return m_availableOutputDevicesChanged; -} + UINT count = 0; + pCollection->GetCount(&count); -bool WasapiAudioDriver::setOutputDeviceBufferSize(unsigned int bufferSize) -{ - bool result = true; + std::pair, std::string> devices; + + for (UINT i = 0; i < count; i++) { + IMMDevice* pDevice = nullptr; + hr = pCollection->Item(i, &pDevice); + if (SUCCEEDED(hr)) { + AudioDevice deviceInfo; + deviceInfo.id = get_deviceId(pDevice); - if (isOpened()) { - close(); + IPropertyStore* pProps = nullptr; + hr = pDevice->OpenPropertyStore(STGM_READ, &pProps); + if (SUCCEEDED(hr)) { + PROPVARIANT varName; + PropVariantInit(&varName); + + hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName); + if (SUCCEEDED(hr) && varName.vt == VT_LPWSTR) { + deviceInfo.name = to_string(varName.pwszVal); + } + + PropVariantClear(&varName); + pProps->Release(); + } - m_activeSpec.output.samplesPerChannel = bufferSize; - result = open(m_activeSpec, &m_activeSpec); - } else { - m_desiredSpec.output.samplesPerChannel = bufferSize; + devices.first.push_back(deviceInfo); + pDevice->Release(); + } } - m_outputDeviceBufferSizeChanged.notify(); + pCollection->Release(); - return result; -} + IMMDevice* defaultDevice = nullptr; + enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice); + if (defaultDevice) { + devices.second = get_deviceId(defaultDevice); + defaultDevice->Release(); + } -async::Notification WasapiAudioDriver::outputDeviceBufferSizeChanged() const -{ - return m_outputDeviceBufferSizeChanged; + return devices; } -std::vector WasapiAudioDriver::availableOutputDeviceBufferSizes() const +void WasapiAudioDriver::updateAudioDeviceList() { - std::vector result; + TRACEFUNC; - unsigned int n = MAXIMUM_BUFFER_SIZE; - unsigned int min = minSupportedBufferSize(); - - while (n >= min) { - result.push_back(n); - n /= 2; + IF_ASSERT_FAILED(m_data->enumerator) { + return; } - return result; + auto devices = audioDevices(m_data->enumerator); + m_deviceList = devices.first; + m_defaultDeviceId = devices.second; + m_deviceListChanged.notify(); } -bool WasapiAudioDriver::setOutputDeviceSampleRate(unsigned int sampleRate) +static IAudioClient* audioClientForDevice(IMMDeviceEnumerator* enumerator, const std::string& deviceId) { - bool result = true; + std::wstring wdeviceId = to_wstring(deviceId); + IMMDevice* device = nullptr; + HRESULT hr = enumerator->GetDevice(wdeviceId.c_str(), &device); + if (FAILED(hr)) { + LOGE() << "failed get device with id: " << deviceId << ", error: " << hrToString(hr); + return nullptr; + } - if (isOpened()) { - close(); + DEFER { + device->Release(); + }; - m_activeSpec.output.sampleRate = sampleRate; - result = open(m_activeSpec, &m_activeSpec); - } else { - m_desiredSpec.output.sampleRate = sampleRate; + IAudioClient* audioClient = nullptr; + hr = device->Activate( + __uuidof(IAudioClient), + CLSCTX_ALL, + nullptr, + (void**)&audioClient + ); + + if (FAILED(hr)) { + LOGE() << "failed get audioClient, error: " << hrToString(hr); + return nullptr; } - m_outputDeviceSampleRateChanged.notify(); - - return result; + return audioClient; } -async::Notification WasapiAudioDriver::outputDeviceSampleRateChanged() const +AudioDeviceID WasapiAudioDriver::defaultDevice() const { - return m_outputDeviceSampleRateChanged; + return m_defaultDeviceId; } -std::vector WasapiAudioDriver::availableOutputDeviceSampleRates() const +bool WasapiAudioDriver::open(const Spec& spec, Spec* activeSpec) { - return { - 44100, - 48000, - 88200, - 96000, - }; -} + TRACEFUNC; -void WasapiAudioDriver::resume() -{ + IF_ASSERT_FAILED(!spec.deviceId.empty()) { + return false; + } + + IF_ASSERT_FAILED(m_data->enumerator) { + return false; + } + + LOGI() << "try open driver, device: " << spec.deviceId; + + m_data->audioClient = audioClientForDevice(m_data->enumerator, spec.deviceId); + if (!m_data->audioClient) { + LOGE() << "failed get audio client for device: " << spec.deviceId; + return false; + } + + HRESULT hr = m_data->audioClient->GetMixFormat(&m_data->mixFormat); + if (FAILED(hr)) { + LOGE() << "failed get mixFormat, error: " << hrToString(hr); + return false; + } + + m_activeSpec = spec; + m_activeSpec.output.sampleRate = m_data->mixFormat->nSamplesPerSec; + m_activeSpecChanged.send(m_activeSpec); + + if (activeSpec) { + *activeSpec = m_activeSpec; + } + + m_audioThread = std::thread(&WasapiAudioDriver::th_audioThread, this); + + return true; } -void WasapiAudioDriver::suspend() +void WasapiAudioDriver::th_audioThread() { + m_opened = th_audioInitialize(); + + while (m_opened) { + DWORD waitResult = WaitForSingleObject(m_data->audioEvent, 100); + if (waitResult == WAIT_OBJECT_0) { + th_processAudioData(); + } else if (waitResult == WAIT_TIMEOUT) { + continue; + } else { + // error + break; + } + } } -void WasapiAudioDriver::reopen() +bool WasapiAudioDriver::th_audioInitialize() { - close(); + TRACEFUNC; + + REFERENCE_TIME bufferDuration = to_reftime(m_activeSpec.output.samplesPerChannel, + m_activeSpec.output.sampleRate); + + HRESULT hr = m_data->audioClient->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + bufferDuration, + 0, + m_data->mixFormat, + nullptr + ); + + if (FAILED(hr)) { + LOGE() << "failed audioClient->Initialize, error: " << hrToString(hr); + return false; + } - open(m_activeSpec, &m_activeSpec); -} + m_data->audioClient->GetBufferSize(&m_bufferFrames); + REFERENCE_TIME defaultPeriod = 0; + REFERENCE_TIME minimumPeriod = 0; + m_data->audioClient->GetDevicePeriod(&defaultPeriod, &minimumPeriod); + m_defaultPeriod = to_samples(defaultPeriod, m_activeSpec.output.sampleRate); + m_minimumPeriod = to_samples(minimumPeriod, m_activeSpec.output.sampleRate); + + hr = m_data->audioClient->GetService( + __uuidof(IAudioRenderClient), + (void**)&m_data->renderClient + ); + + if (FAILED(hr)) { + LOGE() << "failed get renderClient, error: " << hrToString(hr); + return false; + } -AudioDeviceID WasapiAudioDriver::defaultDeviceId() const -{ - using namespace winrt::Windows::Media::Devices; + m_data->audioEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (!m_data->audioEvent) { + LOGE() << "failed create audioEvent"; + return false; + } - AudioDeviceID result; + hr = m_data->audioClient->SetEventHandle(m_data->audioEvent); + if (FAILED(hr)) { + LOGE() << "failed set audioEvent, error: " << hrToString(hr); + return false; + } - try { - result = to_string(MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default)); - } catch (...) { - LOGE() << to_string(hresult_error(to_hresult()).message()); + hr = m_data->audioClient->Start(); + if (FAILED(hr)) { + LOGE() << "failed audioClient->Start(), error: " << hrToString(hr); + return false; } - return result; + return true; } -void WasapiAudioDriver::updateAvailableOutputDevices() +void WasapiAudioDriver::th_processAudioData() { -#ifdef QT_CONCURRENT_SUPPORTED - Concurrent::run([this](){ - using namespace Windows::Devices::Enumeration; - using namespace winrt::Windows::Media::Devices; + // Get padding in existing buffer + UINT32 paddingFrames = 0; + m_data->audioClient->GetCurrentPadding(&paddingFrames); - AudioDeviceList result; + // GetCurrentPadding represents the number of queued frames + // so we can subtract that from the overall number of frames we have + uint32_t framesAvailable = m_bufferFrames - paddingFrames; - result.push_back({ DEFAULT_DEVICE_ID, muse::trc("audio", "System default") }); + // Only continue if we have buffer to write data + if (framesAvailable == 0) { + return; + } - DeviceInformationCollection devices = nullptr; + uint8_t* data = nullptr; - try { - // Get the string identifier of the audio renderer - hstring AudioSelector = MediaDevice::GetAudioRenderSelector(); + uint32_t actualFramesToRead = framesAvailable; - winrt::Windows::Foundation::IAsyncOperation deviceRequest - = DeviceInformation::FindAllAsync(AudioSelector, {}); + // WASAPI: "nBlockAlign must be equal to the product of nChannels and wBitsPerSample divided by 8 (bits per byte)" + const uint32_t clientFrameSize = m_data->mixFormat->nBlockAlign; - devices = deviceRequest.get(); - } catch (...) { - LOGE() << to_string(hresult_error(to_hresult()).message()); - } + // MuseScore assumes only 2 audio channels (same calculation as above to determine frame size) + const uint32_t muFrameSize = 2 * m_data->mixFormat->wBitsPerSample / 8; - if (devices) { - for (const auto& deviceInfo : devices) { - AudioDevice device { to_string(deviceInfo.Id()), to_string(deviceInfo.Name()) }; - result.emplace_back(std::move(device)); + m_data->renderClient->GetBuffer(actualFramesToRead, &data); + if (actualFramesToRead > 0) { + // Based on the previous calculations, the only way that clientFrameSize will be larger than muFrameSize is + // if the client specifies more than 2 channels. MuseScore doesn't support this (yet), so we use a workaround + // where the missing channels are padded with zeroes... + if (clientFrameSize > muFrameSize) { + const size_t surroundBufferDesiredSize = actualFramesToRead * muFrameSize; + if (m_surroundAudioBuffer.size() < surroundBufferDesiredSize) { + m_surroundAudioBuffer.resize(surroundBufferDesiredSize, 0); } - } - std::lock_guard lock(m_availableOutputDevicesMutex); + m_activeSpec.callback(m_surroundAudioBuffer.data(), + (int)surroundBufferDesiredSize); - if (result == m_availableOutputDevices) { - return; + for (uint32_t i = 0; i < actualFramesToRead; ++i) { + uint8_t* frameStartPos = data + i * clientFrameSize; + std::memcpy(frameStartPos, m_surroundAudioBuffer.data() + i * muFrameSize, muFrameSize); + std::memset(frameStartPos + muFrameSize, 0, clientFrameSize - muFrameSize); + } + } else { + m_activeSpec.callback(data, actualFramesToRead * clientFrameSize); } - - m_availableOutputDevices = result; - m_availableOutputDevicesChanged.notify(); - }); -#endif + } + m_data->renderClient->ReleaseBuffer(actualFramesToRead, 0); } -unsigned int WasapiAudioDriver::minSupportedBufferSize() const +void WasapiAudioDriver::close() { - IF_ASSERT_FAILED(s_wdata.wasapiClient.get()) { - return MINIMUM_BUFFER_SIZE; + m_opened = false; + if (m_audioThread.joinable()) { + m_audioThread.join(); } - unsigned int minPeriod = s_wdata.wasapiClient->minPeriodInFrames(); - unsigned int closestBufferSize = MINIMUM_BUFFER_SIZE; + m_data->releaseClient(); +} + +bool WasapiAudioDriver::isOpened() const +{ + return m_opened; +} + +const IAudioDriver::Spec& WasapiAudioDriver::activeSpec() const +{ + return m_activeSpec; +} + +async::Channel WasapiAudioDriver::activeSpecChanged() const +{ + return m_activeSpecChanged; +} + +AudioDeviceList WasapiAudioDriver::availableOutputDevices() const +{ + return m_deviceList; +} + +async::Notification WasapiAudioDriver::availableOutputDevicesChanged() const +{ + return m_deviceListChanged; +} - while (closestBufferSize < minPeriod) { - closestBufferSize *= 2; +std::vector WasapiAudioDriver::availableOutputDeviceBufferSizes() const +{ + std::vector result; + + samples_t n = MAXIMUM_BUFFER_SIZE; + samples_t min = std::max(m_minimumPeriod, MINIMUM_BUFFER_SIZE); + + while (n >= min) { + result.push_back(n); + n /= 2; } - return closestBufferSize; + return result; +} + +std::vector WasapiAudioDriver::availableOutputDeviceSampleRates() const +{ + return { + 44100, + 48000, + 88200, + 96000, + }; } diff --git a/src/framework/audio/driver/platform/win/wasapiaudiodriver.h b/src/framework/audio/driver/platform/win/wasapiaudiodriver.h index 90bb3afe94bea..cee4980523e66 100644 --- a/src/framework/audio/driver/platform/win/wasapiaudiodriver.h +++ b/src/framework/audio/driver/platform/win/wasapiaudiodriver.h @@ -5,7 +5,7 @@ * MuseScore * Music Composition & Notation * - * Copyright (C) 2022 MuseScore Limited and others + * Copyright (C) 2025 MuseScore Limited and others * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -22,72 +22,61 @@ #pragma once #include - -#include "global/async/asyncable.h" +#include +#include #include "iaudiodriver.h" namespace muse::audio { -class AudioDevicesListener; -class WasapiAudioDriver : public IAudioDriver, public async::Asyncable +class WasapiAudioDriver : public IAudioDriver { public: WasapiAudioDriver(); + ~WasapiAudioDriver(); void init() override; std::string name() const override; + + AudioDeviceID defaultDevice() const override; bool open(const Spec& spec, Spec* activeSpec) override; void close() override; bool isOpened() const override; const Spec& activeSpec() const override; - - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& id) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; + async::Channel activeSpecChanged() const override; AudioDeviceList availableOutputDevices() const override; async::Notification availableOutputDevicesChanged() const override; - - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - - std::vector availableOutputDeviceBufferSizes() const override; - - bool setOutputDeviceSampleRate(unsigned int sampleRate) override; - async::Notification outputDeviceSampleRateChanged() const override; - std::vector availableOutputDeviceSampleRates() const override; - - void resume() override; - void suspend() override; + std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceSampleRates() const override; private: - void reopen(); - - AudioDeviceID defaultDeviceId() const; - - void updateAvailableOutputDevices(); - - unsigned int minSupportedBufferSize() const; - - bool m_isOpened = false; - - AudioDeviceID m_deviceId; + void updateAudioDeviceList(); - mutable std::mutex m_availableOutputDevicesMutex; - mutable AudioDeviceList m_availableOutputDevices; + void th_audioThread(); + bool th_audioInitialize(); + void th_processAudioData(); - std::unique_ptr m_devicesListener; + struct Data; + std::shared_ptr m_data; + AudioDeviceID m_defaultDeviceId; async::Notification m_outputDeviceChanged; - async::Notification m_availableOutputDevicesChanged; - async::Notification m_outputDeviceBufferSizeChanged; - async::Notification m_outputDeviceSampleRateChanged; - Spec m_desiredSpec; - Spec m_activeSpec; + struct DeviceListener; + std::shared_ptr m_deviceListener; + AudioDeviceList m_deviceList; + async::Notification m_deviceListChanged; + + std::thread m_audioThread; + IAudioDriver::Spec m_activeSpec; + async::Channel m_activeSpecChanged; + uint32_t m_bufferFrames = 0; + samples_t m_defaultPeriod = 0; + samples_t m_minimumPeriod = 0; + std::vector m_surroundAudioBuffer; //! NOTE: See #17648 + std::atomic m_opened; }; } diff --git a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp deleted file mode 100644 index 9341177fa96da..0000000000000 --- a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.cpp +++ /dev/null @@ -1,603 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2025 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "wasapiaudiodriver2.h" - -#include -#include -#include -#include - -#include "global/defer.h" - -#include "log.h" - -using namespace muse; -using namespace muse::audio; - -struct WasapiAudioDriver2::DeviceListener : public IMMNotificationClient -{ - DeviceListener(WasapiAudioDriver2* driver) - : m_driver(driver) {} - - STDMETHOD_(ULONG, AddRef)() { - return 1; - } - STDMETHOD_(ULONG, Release)() { - return 1; - } - STDMETHOD(QueryInterface)(REFIID, void**) { - return S_OK; - } - STDMETHOD(OnDeviceStateChanged)(LPCWSTR, DWORD) { - return S_OK; - } - STDMETHOD(OnDeviceAdded)(LPCWSTR) { - m_driver->updateAudioDeviceList(); - return S_OK; - } - STDMETHOD(OnDeviceRemoved)(LPCWSTR) { - m_driver->updateAudioDeviceList(); - return S_OK; - } - STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role, LPCWSTR) { - if ((role != eConsole && role != eCommunications) || (flow != eRender)) { - return S_OK; - } - - m_driver->updateAudioDeviceList(); - - return S_OK; - } - STDMETHOD(OnPropertyValueChanged)(LPCWSTR, const PROPERTYKEY) { - return S_OK; - } - - WasapiAudioDriver2* m_driver = nullptr; -}; - -struct WasapiAudioDriver2::Data { - IMMDeviceEnumerator* enumerator = nullptr; - IAudioClient* audioClient = nullptr; - IAudioRenderClient* renderClient = nullptr; - WAVEFORMATEX* mixFormat = nullptr; - HANDLE audioEvent = nullptr; - - void releaseClient() - { - if (audioEvent) { - CloseHandle(audioEvent); - audioEvent = nullptr; - } - - if (renderClient) { - renderClient->Release(); - renderClient = nullptr; - } - - if (audioClient) { - audioClient->Release(); - audioClient = nullptr; - } - - if (mixFormat) { - CoTaskMemFree(mixFormat); - mixFormat = nullptr; - } - } - - void releaseAll() - { - releaseClient(); - if (enumerator) { - enumerator->Release(); - enumerator = nullptr; - } - } -}; - -static REFERENCE_TIME to_reftime(int numSamples, double sampleRate) -{ - return (REFERENCE_TIME)((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); -} - -static samples_t to_samples(const REFERENCE_TIME& t, double sampleRate) -{ - return sampleRate * ((double)t) * 0.0000001; -} - -static std::wstring to_wstring(const std::string& str) -{ - if (str.empty()) { - return L""; - } - - int wide_len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); - if (wide_len == 0) { - return L""; - } - - std::wstring result(wide_len - 1, 0); - MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], wide_len); - - return result; -} - -static std::string to_string(LPWSTR wstr) -{ - if (!wstr || wstr[0] == L'\0') { - return ""; - } - - int multi_len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); - if (multi_len == 0) { - return ""; - } - - std::string result(multi_len - 1, 0); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &result[0], multi_len, nullptr, nullptr); - - return result; -} - -static std::string get_deviceId(IMMDevice* device) -{ - LPWSTR devId; - HRESULT hr = device->GetId(&devId); - if (FAILED(hr)) { - LOGE() << "failed get deviceId"; - return std::string(); - } - std::string str = to_string(devId); - CoTaskMemFree(devId); - return str; -} - -static std::string hrToString(HRESULT hr) -{ - if (SUCCEEDED(hr)) { - return "SUCCEEDED"; - } - - switch (hr) { - // Common WASAPI errors - case AUDCLNT_E_NOT_INITIALIZED: return "AUDCLNT_E_NOT_INITIALIZED - Audio client not initialized"; - case AUDCLNT_E_ALREADY_INITIALIZED: return "AUDCLNT_E_ALREADY_INITIALIZED - Audio client already initialized"; - case AUDCLNT_E_WRONG_ENDPOINT_TYPE: return "AUDCLNT_E_WRONG_ENDPOINT_TYPE - Wrong endpoint type"; - case AUDCLNT_E_DEVICE_INVALIDATED: return "AUDCLNT_E_DEVICE_INVALIDATED - Device invalidated"; - case AUDCLNT_E_NOT_STOPPED: return "AUDCLNT_E_NOT_STOPPED - Audio client not stopped"; - case AUDCLNT_E_BUFFER_TOO_LARGE: return "AUDCLNT_E_BUFFER_TOO_LARGE - Buffer too large"; - case AUDCLNT_E_OUT_OF_ORDER: return "AUDCLNT_E_OUT_OF_ORDER - Operation out of order"; - case AUDCLNT_E_UNSUPPORTED_FORMAT: return "AUDCLNT_E_UNSUPPORTED_FORMAT - Unsupported format"; - case AUDCLNT_E_INVALID_SIZE: return "AUDCLNT_E_INVALID_SIZE - Invalid size"; - case AUDCLNT_E_DEVICE_IN_USE: return "AUDCLNT_E_DEVICE_IN_USE - Device in use"; - case AUDCLNT_E_BUFFER_OPERATION_PENDING: return "AUDCLNT_E_BUFFER_OPERATION_PENDING - Buffer operation pending"; - case AUDCLNT_E_THREAD_NOT_REGISTERED: return "AUDCLNT_E_THREAD_NOT_REGISTERED - Thread not registered"; - case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: return "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED - Exclusive mode not allowed"; - case AUDCLNT_E_ENDPOINT_CREATE_FAILED: return "AUDCLNT_E_ENDPOINT_CREATE_FAILED - Endpoint create failed"; - case AUDCLNT_E_SERVICE_NOT_RUNNING: return "AUDCLNT_E_SERVICE_NOT_RUNNING - Audio service not running"; - case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: return "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED - Event handle not expected"; - case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: return "AUDCLNT_E_EXCLUSIVE_MODE_ONLY - Exclusive mode only"; - case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: return "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL - Buffer duration/period not equal"; - case AUDCLNT_E_EVENTHANDLE_NOT_SET: return "AUDCLNT_E_EVENTHANDLE_NOT_SET - Event handle not set"; - case AUDCLNT_E_INCORRECT_BUFFER_SIZE: return "AUDCLNT_E_INCORRECT_BUFFER_SIZE - Incorrect buffer size"; - case AUDCLNT_E_BUFFER_SIZE_ERROR: return "AUDCLNT_E_BUFFER_SIZE_ERROR - Buffer size error"; - case AUDCLNT_E_CPUUSAGE_EXCEEDED: return "AUDCLNT_E_CPUUSAGE_EXCEEDED - CPU usage exceeded"; - - // Common COM errors - case E_INVALIDARG: return "E_INVALIDARG - Invalid arguments"; - case E_OUTOFMEMORY: return "E_OUTOFMEMORY - Out of memory"; - case E_NOINTERFACE: return "E_NOINTERFACE - No interface"; - case E_POINTER: return "E_POINTER - Invalid pointer"; - case E_ACCESSDENIED: return "E_ACCESSDENIED - Access denied"; - case E_FAIL: return "E_FAIL - Unspecified error"; - - default: - return "Unknown error: " + std::to_string(hr); - } -} - -WasapiAudioDriver2::WasapiAudioDriver2() -{ - m_data = std::make_shared(); - m_deviceListener = std::make_shared(this); -} - -WasapiAudioDriver2::~WasapiAudioDriver2() -{ - close(); - - if (m_data->enumerator) { - m_data->enumerator->UnregisterEndpointNotificationCallback(m_deviceListener.get()); - } - - m_data->releaseAll(); - - CoUninitialize(); -} - -std::string WasapiAudioDriver2::name() const -{ - return "WASAPI"; -} - -void WasapiAudioDriver2::init() -{ - LOGI() << "begin driver init"; - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if (FAILED(hr)) { - LOGE() << "failed CoInitializeEx, error: " << hrToString(hr); - } - - hr = CoCreateInstance( - __uuidof(MMDeviceEnumerator), - nullptr, - CLSCTX_ALL, - __uuidof(IMMDeviceEnumerator), - (void**)&m_data->enumerator - ); - - if (FAILED(hr)) { - LOGE() << "failed create DeviceEnumerator, error: " << hrToString(hr); - return; - } - - hr = m_data->enumerator->RegisterEndpointNotificationCallback(m_deviceListener.get()); - if (FAILED(hr)) { - LOGE() << "failed RegisterEndpointNotificationCallback, error: " << hrToString(hr); - } - - updateAudioDeviceList(); - - LOGI() << "success driver init"; -} - -static std::pair, std::string /*defaultId*/> audioDevices(IMMDeviceEnumerator* enumerator) -{ - IF_ASSERT_FAILED(enumerator) { - return {}; - } - - IMMDeviceCollection* pCollection = nullptr; - HRESULT hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pCollection); - if (FAILED(hr)) { - LOGE() << "failed get EnumAudioEndpoints, error: " << hrToString(hr); - return {}; - } - - UINT count = 0; - pCollection->GetCount(&count); - - std::pair, std::string> devices; - - for (UINT i = 0; i < count; i++) { - IMMDevice* pDevice = nullptr; - hr = pCollection->Item(i, &pDevice); - if (SUCCEEDED(hr)) { - AudioDevice deviceInfo; - deviceInfo.id = get_deviceId(pDevice); - - IPropertyStore* pProps = nullptr; - hr = pDevice->OpenPropertyStore(STGM_READ, &pProps); - if (SUCCEEDED(hr)) { - PROPVARIANT varName; - PropVariantInit(&varName); - - hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName); - if (SUCCEEDED(hr) && varName.vt == VT_LPWSTR) { - deviceInfo.name = to_string(varName.pwszVal); - } - - PropVariantClear(&varName); - pProps->Release(); - } - - devices.first.push_back(deviceInfo); - pDevice->Release(); - } - } - - pCollection->Release(); - - IMMDevice* defaultDevice = nullptr; - enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice); - if (defaultDevice) { - devices.second = get_deviceId(defaultDevice); - defaultDevice->Release(); - } - - return devices; -} - -void WasapiAudioDriver2::updateAudioDeviceList() -{ - TRACEFUNC; - - IF_ASSERT_FAILED(m_data->enumerator) { - return; - } - - auto devices = audioDevices(m_data->enumerator); - m_deviceList = devices.first; - m_defaultDeviceId = devices.second; - m_deviceListChanged.notify(); -} - -static IAudioClient* audioClientForDevice(IMMDeviceEnumerator* enumerator, const std::string& deviceId) -{ - std::wstring wdeviceId = to_wstring(deviceId); - IMMDevice* device = nullptr; - HRESULT hr = enumerator->GetDevice(wdeviceId.c_str(), &device); - if (FAILED(hr)) { - LOGE() << "failed get device with id: " << deviceId << ", error: " << hrToString(hr); - return nullptr; - } - - DEFER { - device->Release(); - }; - - IAudioClient* audioClient = nullptr; - hr = device->Activate( - __uuidof(IAudioClient), - CLSCTX_ALL, - nullptr, - (void**)&audioClient - ); - - if (FAILED(hr)) { - LOGE() << "failed get audioClient, error: " << hrToString(hr); - return nullptr; - } - - return audioClient; -} - -AudioDeviceID WasapiAudioDriver2::defaultDevice() const -{ - return m_defaultDeviceId; -} - -bool WasapiAudioDriver2::open(const Spec& spec, Spec* activeSpec) -{ - TRACEFUNC; - - IF_ASSERT_FAILED(!spec.deviceId.empty()) { - return false; - } - - IF_ASSERT_FAILED(m_data->enumerator) { - return false; - } - - LOGI() << "try open driver, device: " << spec.deviceId; - - m_data->audioClient = audioClientForDevice(m_data->enumerator, spec.deviceId); - if (!m_data->audioClient) { - LOGE() << "failed get audio client for device: " << spec.deviceId; - return false; - } - - HRESULT hr = m_data->audioClient->GetMixFormat(&m_data->mixFormat); - if (FAILED(hr)) { - LOGE() << "failed get mixFormat, error: " << hrToString(hr); - return false; - } - - m_activeSpec = spec; - m_activeSpec.output.sampleRate = m_data->mixFormat->nSamplesPerSec; - m_activeSpecChanged.send(m_activeSpec); - - if (activeSpec) { - *activeSpec = m_activeSpec; - } - - m_audioThread = std::thread(&WasapiAudioDriver2::th_audioThread, this); - - return true; -} - -void WasapiAudioDriver2::th_audioThread() -{ - m_opened = th_audioInitialize(); - - while (m_opened) { - DWORD waitResult = WaitForSingleObject(m_data->audioEvent, 100); - if (waitResult == WAIT_OBJECT_0) { - th_processAudioData(); - } else if (waitResult == WAIT_TIMEOUT) { - continue; - } else { - // error - break; - } - } -} - -bool WasapiAudioDriver2::th_audioInitialize() -{ - TRACEFUNC; - - REFERENCE_TIME bufferDuration = to_reftime(m_activeSpec.output.samplesPerChannel, - m_activeSpec.output.sampleRate); - - HRESULT hr = m_data->audioClient->Initialize( - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - bufferDuration, - 0, - m_data->mixFormat, - nullptr - ); - - if (FAILED(hr)) { - LOGE() << "failed audioClient->Initialize, error: " << hrToString(hr); - return false; - } - - m_data->audioClient->GetBufferSize(&m_bufferFrames); - REFERENCE_TIME defaultPeriod = 0; - REFERENCE_TIME minimumPeriod = 0; - m_data->audioClient->GetDevicePeriod(&defaultPeriod, &minimumPeriod); - m_defaultPeriod = to_samples(defaultPeriod, m_activeSpec.output.sampleRate); - m_minimumPeriod = to_samples(minimumPeriod, m_activeSpec.output.sampleRate); - - hr = m_data->audioClient->GetService( - __uuidof(IAudioRenderClient), - (void**)&m_data->renderClient - ); - - if (FAILED(hr)) { - LOGE() << "failed get renderClient, error: " << hrToString(hr); - return false; - } - - m_data->audioEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (!m_data->audioEvent) { - LOGE() << "failed create audioEvent"; - return false; - } - - hr = m_data->audioClient->SetEventHandle(m_data->audioEvent); - if (FAILED(hr)) { - LOGE() << "failed set audioEvent, error: " << hrToString(hr); - return false; - } - - hr = m_data->audioClient->Start(); - if (FAILED(hr)) { - LOGE() << "failed audioClient->Start(), error: " << hrToString(hr); - return false; - } - - return true; -} - -void WasapiAudioDriver2::th_processAudioData() -{ - // Get padding in existing buffer - UINT32 paddingFrames = 0; - m_data->audioClient->GetCurrentPadding(&paddingFrames); - - // GetCurrentPadding represents the number of queued frames - // so we can subtract that from the overall number of frames we have - uint32_t framesAvailable = m_bufferFrames - paddingFrames; - - // Only continue if we have buffer to write data - if (framesAvailable == 0) { - return; - } - - uint8_t* data = nullptr; - - uint32_t actualFramesToRead = framesAvailable; - - // WASAPI: "nBlockAlign must be equal to the product of nChannels and wBitsPerSample divided by 8 (bits per byte)" - const uint32_t clientFrameSize = m_data->mixFormat->nBlockAlign; - - // MuseScore assumes only 2 audio channels (same calculation as above to determine frame size) - const uint32_t muFrameSize = 2 * m_data->mixFormat->wBitsPerSample / 8; - - m_data->renderClient->GetBuffer(actualFramesToRead, &data); - if (actualFramesToRead > 0) { - // Based on the previous calculations, the only way that clientFrameSize will be larger than muFrameSize is - // if the client specifies more than 2 channels. MuseScore doesn't support this (yet), so we use a workaround - // where the missing channels are padded with zeroes... - if (clientFrameSize > muFrameSize) { - const size_t surroundBufferDesiredSize = actualFramesToRead * muFrameSize; - if (m_surroundAudioBuffer.size() < surroundBufferDesiredSize) { - m_surroundAudioBuffer.resize(surroundBufferDesiredSize, 0); - } - - m_activeSpec.callback(m_surroundAudioBuffer.data(), - (int)surroundBufferDesiredSize); - - for (uint32_t i = 0; i < actualFramesToRead; ++i) { - uint8_t* frameStartPos = data + i * clientFrameSize; - std::memcpy(frameStartPos, m_surroundAudioBuffer.data() + i * muFrameSize, muFrameSize); - std::memset(frameStartPos + muFrameSize, 0, clientFrameSize - muFrameSize); - } - } else { - m_activeSpec.callback(data, actualFramesToRead * clientFrameSize); - } - } - m_data->renderClient->ReleaseBuffer(actualFramesToRead, 0); -} - -void WasapiAudioDriver2::close() -{ - m_opened = false; - if (m_audioThread.joinable()) { - m_audioThread.join(); - } - - m_data->releaseClient(); -} - -bool WasapiAudioDriver2::isOpened() const -{ - return m_opened; -} - -const IAudioDriver::Spec& WasapiAudioDriver2::activeSpec() const -{ - return m_activeSpec; -} - -async::Channel WasapiAudioDriver2::activeSpecChanged() const -{ - return m_activeSpecChanged; -} - -AudioDeviceList WasapiAudioDriver2::availableOutputDevices() const -{ - return m_deviceList; -} - -async::Notification WasapiAudioDriver2::availableOutputDevicesChanged() const -{ - return m_deviceListChanged; -} - -std::vector WasapiAudioDriver2::availableOutputDeviceBufferSizes() const -{ - std::vector result; - - samples_t n = MAXIMUM_BUFFER_SIZE; - samples_t min = std::max(m_minimumPeriod, MINIMUM_BUFFER_SIZE); - - while (n >= min) { - result.push_back(n); - n /= 2; - } - - return result; -} - -std::vector WasapiAudioDriver2::availableOutputDeviceSampleRates() const -{ - return { - 44100, - 48000, - 88200, - 96000, - }; -} diff --git a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.h b/src/framework/audio/driver/platform/win/wasapiaudiodriver2.h deleted file mode 100644 index 055b9724b0c49..0000000000000 --- a/src/framework/audio/driver/platform/win/wasapiaudiodriver2.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2025 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include -#include -#include - -#include "iaudiodriver.h" - -namespace muse::audio { -class WasapiAudioDriver2 : public IAudioDriver -{ -public: - WasapiAudioDriver2(); - ~WasapiAudioDriver2(); - - void init() override; - - std::string name() const override; - - AudioDeviceID defaultDevice() const override; - bool open(const Spec& spec, Spec* activeSpec) override; - void close() override; - bool isOpened() const override; - - const Spec& activeSpec() const override; - async::Channel activeSpecChanged() const override; - - AudioDeviceList availableOutputDevices() const override; - async::Notification availableOutputDevicesChanged() const override; - std::vector availableOutputDeviceBufferSizes() const override; - std::vector availableOutputDeviceSampleRates() const override; - -private: - - void updateAudioDeviceList(); - - void th_audioThread(); - bool th_audioInitialize(); - void th_processAudioData(); - - struct Data; - std::shared_ptr m_data; - - AudioDeviceID m_defaultDeviceId; - async::Notification m_outputDeviceChanged; - - struct DeviceListener; - std::shared_ptr m_deviceListener; - AudioDeviceList m_deviceList; - async::Notification m_deviceListChanged; - - std::thread m_audioThread; - IAudioDriver::Spec m_activeSpec; - async::Channel m_activeSpecChanged; - uint32_t m_bufferFrames = 0; - samples_t m_defaultPeriod = 0; - samples_t m_minimumPeriod = 0; - std::vector m_surroundAudioBuffer; //! NOTE: See #17648 - std::atomic m_opened; -}; -} diff --git a/src/framework/audio/driver/platform/win/wasapitypes.h b/src/framework/audio/driver/platform/win/wasapitypes.h deleted file mode 100644 index 20504e3ed7b79..0000000000000 --- a/src/framework/audio/driver/platform/win/wasapitypes.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2022 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace winrt { -enum class DeviceState : int32_t -{ - Uninitialized = 0, - Error = 1, - Discontinuity = 2, - Flushing = 3, - Activated = 4, - Initialized = 5, - Starting = 6, - Playing = 7, - Capturing = 8, - Pausing = 9, - Paused = 10, - Stopping = 11, - Stopped = 12, -}; - -template -struct unique_cotaskmem_ptr -{ - unique_cotaskmem_ptr(T* p = nullptr) - : m_p(p) {} - ~unique_cotaskmem_ptr() { CoTaskMemFree(m_p); } - - unique_cotaskmem_ptr(unique_cotaskmem_ptr const&) = delete; - unique_cotaskmem_ptr(unique_cotaskmem_ptr&& other) - : m_p(std::exchange(other.m_p, nullptr)) {} - - unique_cotaskmem_ptr& operator=(const unique_cotaskmem_ptr& other) = delete; - unique_cotaskmem_ptr& operator=(unique_cotaskmem_ptr&& other) - { - CoTaskMemFree(std::exchange(m_p, std::exchange(other.m_p, nullptr))); - return *this; - } - - operator bool() const { - return static_cast(m_p); - } - - T* operator->() { return m_p; } - T* get() { return m_p; } - T** put() { return &m_p; } - - T* m_p; -}; - -template -struct EmbeddedMFAsyncCallback : ::IMFAsyncCallback -{ - template static Parent* parent_finder(HRESULT (Parent::*)(IMFAsyncResult*)) { return nullptr; } - using ParentPtr = decltype(parent_finder(Callback)); - - ParentPtr m_parent; - DWORD m_queueId = 0; - - EmbeddedMFAsyncCallback(ParentPtr parent) - : m_parent(parent) {} - - STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) final - { - if (is_guid_of<::IMFAsyncCallback, ::IUnknown>(riid)) { - (*ppvObject) = this; - AddRef(); - return S_OK; - } - *ppvObject = nullptr; - return E_NOINTERFACE; - } - - STDMETHOD_(ULONG, AddRef)() final { - return m_parent->AddRef(); - } - STDMETHOD_(ULONG, Release)() final { - return m_parent->Release(); - } - - STDMETHOD(GetParameters)(DWORD * flags, DWORD* queueId) final - { - *flags = 0; - *queueId = m_queueId; - return S_OK; - } - - STDMETHOD(Invoke)(IMFAsyncResult * result) final - { - return (m_parent->*Callback)(result); - } - - void SetQueueID(DWORD queueId) { m_queueId = queueId; } -}; - -typedef std::function SampleRequestCallback; -} diff --git a/src/framework/audio/driver/platform/win/wincoreaudiodriver.cpp b/src/framework/audio/driver/platform/win/wincoreaudiodriver.cpp deleted file mode 100644 index 7cd671fe58e9a..0000000000000 --- a/src/framework/audio/driver/platform/win/wincoreaudiodriver.cpp +++ /dev/null @@ -1,543 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "wincoreaudiodriver.h" - -#include - -#include -#include -#include - -#if defined __MINGW32__ // hack against "undefined reference" when linking in MinGW -#define INITGUID -#endif -#include - -#include "translation.h" -#include "log.h" - -#define REFTIMES_PER_SEC 10000000 -#define REFTIMES_PER_MILLISEC 10000 - -#define CHECK_HRESULT(hr, errorValue); if (hr != S_OK) { \ - logError(hr); \ - return errorValue; \ -} \ - -using namespace muse::audio; - -struct WinCoreData { - IAudioClient* audioClient = nullptr; - IAudioRenderClient* renderClient = nullptr; - IAudioDriver::Callback callback; - WAVEFORMATEX pFormat; - HANDLE hEvent; -}; - -static std::wstring stringToWString(const std::string& str) -{ - return QString::fromStdString(str).toStdWString(); -} - -static std::string lpwstrToString(const LPWSTR& lpwstr) -{ - return QString::fromStdWString(lpwstr).toStdString(); -} - -static int refTimeToSamples(const REFERENCE_TIME& t, double sampleRate) noexcept -{ - return sampleRate * ((double)t) * 0.0000001; -} - -static REFERENCE_TIME samplesToRefTime(int numSamples, double sampleRate) noexcept -{ - return (REFERENCE_TIME)((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); -} - -void copyWavFormat(WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept -{ - memcpy(&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof(WAVEFORMATEXTENSIBLE) - : sizeof(WAVEFORMATEX)); -} - -static void logError(HRESULT hr); - -static WinCoreData* s_data = nullptr; -static IAudioDriver::Spec s_activeSpec; -static HRESULT s_lastError = S_OK; - -const IID MU_IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); -const IID MU_IID_IAudioClient = __uuidof(IAudioClient); -const IID MU_IID_IAudioRenderClient = __uuidof(IAudioRenderClient); - -CoreAudioDriver::CoreAudioDriver() -{ - m_deviceId = DEFAULT_DEVICE_ID; - CoInitialize(NULL); -} - -CoreAudioDriver::~CoreAudioDriver() -{ - close(); -} - -void CoreAudioDriver::init() -{ - m_devicesListener.startWithCallback([this]() { - return availableOutputDevices(); - }); - - m_devicesListener.devicesChanged().onNotify(this, [this]() { - m_availableOutputDevicesChanged.notify(); - }); - - CoInitialize(NULL); -} - -std::string CoreAudioDriver::name() const -{ - return "MUAUDIO(WinCoreAudio)"; -} - -bool CoreAudioDriver::open(const IAudioDriver::Spec& spec, IAudioDriver::Spec* activeSpec) -{ - if (s_data) { - delete s_data; - } - - s_data = new WinCoreData(); - - HRESULT hr = S_OK; - - IMMDeviceEnumerator* pEnumerator = nullptr; - hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, MU_IID_IMMDeviceEnumerator, (void**)&pEnumerator); - CHECK_HRESULT(hr, false); - - IMMDevice* pDdevice = nullptr; - AudioDeviceID outputDeviceId = outputDevice(); - if (outputDeviceId == DEFAULT_DEVICE_ID) { - hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDdevice); - } else { - std::wstring wstr = stringToWString(outputDeviceId); - hr = pEnumerator->GetDevice(wstr.c_str(), &pDdevice); - } - pEnumerator->Release(); - CHECK_HRESULT(hr, false); - - hr = pDdevice->Activate(MU_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&s_data->audioClient); - pDdevice->Release(); - CHECK_HRESULT(hr, false); - - WAVEFORMATEX* deviceFormat; - hr = s_data->audioClient->GetMixFormat(&deviceFormat); - CHECK_HRESULT(hr, false); - - REFERENCE_TIME defaultTime, minimumTime; - hr = s_data->audioClient->GetDevicePeriod(&defaultTime, &minimumTime); - CHECK_HRESULT(hr, false); - - unsigned int minBufferSize = refTimeToSamples(minimumTime, spec.output.sampleRate); - m_bufferSizes = resolveBufferSizes(minBufferSize); - - s_data->pFormat = *deviceFormat; - s_data->pFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - s_data->pFormat.nChannels = spec.output.audioChannelCount; - s_data->pFormat.wBitsPerSample = 32; - s_data->pFormat.nAvgBytesPerSec = s_data->pFormat.nSamplesPerSec * s_data->pFormat.nChannels * sizeof(float); - s_data->pFormat.nBlockAlign = (s_data->pFormat.nChannels * s_data->pFormat.wBitsPerSample) / 8; - s_data->pFormat.cbSize = 0; - s_data->callback = spec.callback; - if (activeSpec) { - *activeSpec = spec; - activeSpec->format = Format::AudioF32; - activeSpec->output.sampleRate = s_data->pFormat.nSamplesPerSec; - s_activeSpec = *activeSpec; - } - - defaultTime = std::max(minimumTime, samplesToRefTime(spec.output.samplesPerChannel, s_data->pFormat.nSamplesPerSec)); - - hr = s_data->audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, - defaultTime, defaultTime, &s_data->pFormat, NULL); - CHECK_HRESULT(hr, false); - - UINT32 bufferFrameCount; - hr = s_data->audioClient->GetBufferSize(&bufferFrameCount); - CHECK_HRESULT(hr, false); - - hr = s_data->audioClient->GetService(MU_IID_IAudioRenderClient, (void**)&s_data->renderClient); - CHECK_HRESULT(hr, false); - - s_data->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - CHECK_HRESULT(hr, false); - hr = s_data->audioClient->SetEventHandle(s_data->hEvent); - - REFERENCE_TIME hnsActualDuration = (double)REFTIMES_PER_SEC - * bufferFrameCount / s_data->pFormat.nSamplesPerSec; - - m_active = true; - m_thread = std::thread([this, hnsActualDuration]() { - BYTE* pData; - HRESULT hr = S_OK; - do { - Sleep((DWORD)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2)); - - UINT32 bufferFrameCount, bufferPadding; - hr = s_data->audioClient->GetBufferSize(&bufferFrameCount); - logError(hr); - - hr = s_data->audioClient->GetCurrentPadding(&bufferPadding); - logError(hr); - - auto bufferSize = bufferFrameCount - bufferPadding; - hr = s_data->renderClient->GetBuffer(bufferSize, &pData); - logError(hr); - if (!pData || hr != S_OK) { - continue; - } - - s_data->callback(nullptr, reinterpret_cast(pData), - bufferSize * s_data->pFormat.wBitsPerSample * s_data->pFormat.nChannels / 8); - hr = s_data->renderClient->ReleaseBuffer(bufferSize, 0); - logError(hr); - - DWORD waitResult = WaitForSingleObject(s_data->hEvent, INFINITE); - if (waitResult != WAIT_OBJECT_0) { - LOGE() << "audio driver wait for signal failed: " << int32_t(waitResult); - break; - } - } while (m_active); - }); - - hr = s_data->audioClient->Start(); - CHECK_HRESULT(hr, false); - - LOGI() << "Core Audio driver started, id " << outputDeviceId << ", buffer size " << bufferFrameCount; - return true; -} - -void CoreAudioDriver::close() -{ - if (!s_data) { - return; - } - - if (s_data->audioClient) { - s_data->audioClient->Stop(); - } - m_active = false; - if (m_thread.joinable()) { - m_thread.join(); - } - clean(); - LOGI() << "Core Audio driver stoped"; -} - -bool CoreAudioDriver::isOpened() const -{ - return m_active || s_lastError != S_OK; -} - -void logError(HRESULT hr) -{ - if (hr == s_lastError) { - return; - } - - s_lastError = hr; - - switch (hr) { - case S_OK: return; - case S_FALSE: LOGE() << "S_FALSE"; - break; - case AUDCLNT_E_NOT_INITIALIZED: LOGE() << "AUDCLNT_E_NOT_INITIALIZED"; - break; - case AUDCLNT_E_ALREADY_INITIALIZED: LOGE() << "AUDCLNT_E_ALREADY_INITIALIZED"; - break; - case AUDCLNT_E_WRONG_ENDPOINT_TYPE: LOGE() << "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; - break; - case AUDCLNT_E_DEVICE_INVALIDATED: LOGE() << "AUDCLNT_E_DEVICE_INVALIDATED"; - break; - case AUDCLNT_E_NOT_STOPPED: LOGE() << "AUDCLNT_E_NOT_STOPPED"; - break; - case AUDCLNT_E_BUFFER_TOO_LARGE: LOGE() << "AUDCLNT_E_BUFFER_TOO_LARGE"; - break; - case AUDCLNT_E_OUT_OF_ORDER: LOGE() << "AUDCLNT_E_OUT_OF_ORDER"; - break; - case AUDCLNT_E_UNSUPPORTED_FORMAT: LOGE() << "AUDCLNT_E_UNSUPPORTED_FORMAT"; - break; - case AUDCLNT_E_INVALID_SIZE: LOGE() << "AUDCLNT_E_INVALID_SIZE"; - break; - case AUDCLNT_E_DEVICE_IN_USE: LOGE() << "AUDCLNT_E_DEVICE_IN_USE"; - break; - case AUDCLNT_E_BUFFER_OPERATION_PENDING: LOGE() << "AUDCLNT_E_BUFFER_OPERATION_PENDING"; - break; - case AUDCLNT_E_THREAD_NOT_REGISTERED: LOGE() << "AUDCLNT_E_THREAD_NOT_REGISTERED"; - break; - case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: LOGE() << "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; - break; - case AUDCLNT_E_ENDPOINT_CREATE_FAILED: LOGE() << "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; - break; - case AUDCLNT_E_SERVICE_NOT_RUNNING: LOGE() << "AUDCLNT_E_SERVICE_NOT_RUNNING"; - break; - case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: LOGE() << "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; - break; - case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: LOGE() << "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; - break; - case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: LOGE() << "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; - break; - case AUDCLNT_E_EVENTHANDLE_NOT_SET: LOGE() << "AUDCLNT_E_EVENTHANDLE_NOT_SET"; - break; - case AUDCLNT_E_INCORRECT_BUFFER_SIZE: LOGE() << "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; - break; - case AUDCLNT_E_BUFFER_SIZE_ERROR: LOGE() << "AUDCLNT_E_BUFFER_SIZE_ERROR"; - break; - case AUDCLNT_E_CPUUSAGE_EXCEEDED: LOGE() << "AUDCLNT_E_CPUUSAGE_EXCEEDED"; - break; - case AUDCLNT_E_BUFFER_ERROR: LOGE() << "AUDCLNT_E_BUFFER_ERROR"; - break; - case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: LOGE() << "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; - break; - case AUDCLNT_E_INVALID_DEVICE_PERIOD: LOGE() << "AUDCLNT_E_INVALID_DEVICE_PERIOD"; - break; - case AUDCLNT_E_INVALID_STREAM_FLAG: LOGE() << "AUDCLNT_E_INVALID_STREAM_FLAG"; - break; - case AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE: LOGE() << "AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE"; - break; - case AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES: LOGE() << "AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES"; - break; - case AUDCLNT_E_OFFLOAD_MODE_ONLY: LOGE() << "AUDCLNT_E_OFFLOAD_MODE_ONLY"; - break; - case AUDCLNT_E_NONOFFLOAD_MODE_ONLY: LOGE() << "AUDCLNT_E_NONOFFLOAD_MODE_ONLY"; - break; - case AUDCLNT_E_RESOURCES_INVALIDATED: LOGE() << "AUDCLNT_E_RESOURCES_INVALIDATED"; - break; - case AUDCLNT_S_BUFFER_EMPTY: LOGE() << "AUDCLNT_S_BUFFER_EMPTY"; - break; - case AUDCLNT_S_THREAD_ALREADY_REGISTERED: LOGE() << "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; - break; - case AUDCLNT_S_POSITION_STALLED: LOGE() << "AUDCLNT_S_POSITION_STALLED"; - break; - case E_POINTER: LOGE() << "E_POINTER"; - break; - case E_INVALIDARG: LOGE() << "E_INVALIDARG"; - break; - default: - LOGE() << std::system_category().message(hr); - } -} - -void CoreAudioDriver::clean() -{ - if (s_data->audioClient) { - s_data->audioClient->Release(); - } - if (s_data->renderClient) { - s_data->renderClient->Release(); - } - - s_lastError = S_OK; - - delete s_data; - s_data = nullptr; -} - -void CoreAudioDriver::resume() -{ -} - -void CoreAudioDriver::suspend() -{ -} - -AudioDeviceID CoreAudioDriver::outputDevice() const -{ - return m_deviceId; -} - -bool CoreAudioDriver::selectOutputDevice(const AudioDeviceID& id) -{ - if (m_deviceId == id) { - return true; - } - - bool reopen = isOpened(); - close(); - m_deviceId = id; - - bool ok = true; - if (reopen) { - ok = open(s_activeSpec, &s_activeSpec); - } - - if (ok) { - m_outputDeviceChanged.notify(); - } - - return ok; -} - -bool muse::audio::CoreAudioDriver::resetToDefaultOutputDevice() -{ - return selectOutputDevice(defaultDeviceId()); -} - -std::string CoreAudioDriver::defaultDeviceId() const -{ - HRESULT hr; - LPWSTR devId; - IMMDevice* device; - - IMMDeviceEnumerator* pEnumerator = nullptr; - hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, MU_IID_IMMDeviceEnumerator, (void**)&pEnumerator); - CHECK_HRESULT(hr, std::string()); - - EDataFlow dataFlow = eRender; - ERole role = eConsole; - hr = pEnumerator->GetDefaultAudioEndpoint(dataFlow, role, &device); - CHECK_HRESULT(hr, std::string()); - - hr = device->GetId(&devId); - CHECK_HRESULT(hr, std::string()); - - std::wstring ws(devId); - return std::string(ws.begin(), ws.end()); -} - -std::vector CoreAudioDriver::resolveBufferSizes(unsigned int minBufferSize) -{ - std::vector result; - - unsigned int minimum = std::max(static_cast(minBufferSize), MINIMUM_BUFFER_SIZE); - - unsigned int n = MAXIMUM_BUFFER_SIZE; - while (n >= minimum) { - result.push_back(n); - n /= 2; - } - - std::sort(result.begin(), result.end()); - - return result; -} - -async::Notification CoreAudioDriver::outputDeviceChanged() const -{ - return m_outputDeviceChanged; -} - -AudioDeviceList CoreAudioDriver::availableOutputDevices() const -{ - std::lock_guard lock(m_devicesMutex); - - //! NOTE: Required because the method can be called from another thread - CoInitialize(NULL); - - AudioDeviceList result; - result.push_back({ DEFAULT_DEVICE_ID, muse::trc("audio", "System default") }); - - HRESULT hr; - IMMDeviceCollection* devices; - - IMMDeviceEnumerator* enumerator = nullptr; - hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, MU_IID_IMMDeviceEnumerator, (void**)&enumerator); - CHECK_HRESULT(hr, {}); - - hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); - CHECK_HRESULT(hr, {}); - - UINT deviceCount; - - hr = devices->GetCount(&deviceCount); - CHECK_HRESULT(hr, {}); - - for (UINT i = 0; i < deviceCount; ++i) { - IMMDevice* device; - - hr = devices->Item(i, &device); - CHECK_HRESULT(hr, {}); - - LPWSTR devId; - hr = device->GetId(&devId); - CHECK_HRESULT(hr, {}); - - IPropertyStore* pProps = NULL; - - hr = device->OpenPropertyStore(STGM_READ, &pProps); - CHECK_HRESULT(hr, {}); - - PROPVARIANT name; - PropVariantInit(&name); - - hr = pProps->GetValue(PKEY_Device_FriendlyName, &name); - CHECK_HRESULT(hr, {}); - - AudioDevice deviceInfo; - deviceInfo.id = lpwstrToString(devId); - deviceInfo.name = lpwstrToString(name.pwszVal); - result.push_back(deviceInfo); - - PropVariantClear(&name); - device->Release(); - } - - devices->Release(); - - return result; -} - -async::Notification CoreAudioDriver::availableOutputDevicesChanged() const -{ - return m_availableOutputDevicesChanged; -} - -bool CoreAudioDriver::setOutputDeviceBufferSize(unsigned int bufferSize) -{ - if (s_activeSpec.output.samplesPerChannel == bufferSize) { - return true; - } - - bool reopen = isOpened(); - close(); - s_activeSpec.output.samplesPerChannel = bufferSize; - - bool ok = true; - if (reopen) { - ok = open(s_activeSpec, &s_activeSpec); - } - - if (ok) { - m_bufferSizeChanged.notify(); - } - - return ok; -} - -async::Notification CoreAudioDriver::outputDeviceBufferSizeChanged() const -{ - return m_bufferSizeChanged; -} - -std::vector CoreAudioDriver::availableOutputDeviceBufferSizes() const -{ - return m_bufferSizes; -} diff --git a/src/framework/audio/driver/platform/win/wincoreaudiodriver.h b/src/framework/audio/driver/platform/win/wincoreaudiodriver.h deleted file mode 100755 index 0ebe818a05072..0000000000000 --- a/src/framework/audio/driver/platform/win/wincoreaudiodriver.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include -#include - -#include "async/asyncable.h" - -#include "iaudiodriver.h" -#include "internal/audiodeviceslistener.h" - -#include "audioclient.h" - -namespace muse::audio { -class CoreAudioDriver : public IAudioDriver, public async::Asyncable -{ -public: - CoreAudioDriver(); - ~CoreAudioDriver(); - - void init() override; - - std::string name() const override; - bool open(const Spec& spec, Spec* activeSpec) override; - void close() override; - bool isOpened() const override; - - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& id) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; - - AudioDeviceList availableOutputDevices() const override; - async::Notification availableOutputDevicesChanged() const override; - - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - - std::vector availableOutputDeviceBufferSizes() const override; - - void resume() override; - void suspend() override; - -private: - void clean(); - - std::string defaultDeviceId() const; - - std::vector resolveBufferSizes(unsigned int minBufferSize); - - std::atomic m_active { false }; - std::thread m_thread; - - async::Notification m_outputDeviceChanged; - - mutable std::mutex m_devicesMutex; - AudioDevicesListener m_devicesListener; - async::Notification m_availableOutputDevicesChanged; - std::string m_deviceId; - async::Notification m_bufferSizeChanged; - async::Notification m_sampleRateChanged; - std::vector m_bufferSizes; -}; -} diff --git a/src/framework/audio/driver/platform/win/winmmdriver.cpp b/src/framework/audio/driver/platform/win/winmmdriver.cpp deleted file mode 100644 index 4c2dc0c601c09..0000000000000 --- a/src/framework/audio/driver/platform/win/winmmdriver.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "winmmdriver.h" - -#include -#include - -#ifdef _MSC_VER -#pragma comment(lib, "winmm.lib") -#endif - -#include "log.h" - -using namespace muse::audio; - -namespace { -static const int BUFFER_COUNT = 4; - -struct WinMMData -{ - short* sampleBuffer[BUFFER_COUNT]; - WAVEHDR header[BUFFER_COUNT]; - HWAVEOUT waveOut = 0; - HANDLE bufferEndEvent = 0; - HANDLE audioProcessingDoneEvent = 0; - int samples = 0; - int channels = 0; - HANDLE threadHandle = 0; - IAudioDriver::Callback callback; - void* userdata = nullptr; - - WinMMData() - { - for (int i = 0; i < BUFFER_COUNT; i++) { - sampleBuffer[i] = 0; - memset(&header[i], 0, sizeof(WAVEHDR)); - } - } -}; - -static WinMMData* s_winMMData = nullptr; - -static DWORD WINAPI winMMThread(LPVOID aParam) -{ - WinMMData* data = static_cast(aParam); - - while (WAIT_OBJECT_0 != WaitForSingleObject(data->audioProcessingDoneEvent, 0)) { - for (int i = 0; i < BUFFER_COUNT; ++i) { - if (0 != (data->header[i].dwFlags & WHDR_INQUEUE)) { - continue; - } - - short* tgtBuf = data->sampleBuffer[i]; - - //data->soloud->mixSigned16(tgtBuf, data->samples); - uint8_t* stream = (uint8_t*)tgtBuf; - data->callback(data->userdata, stream, data->samples * data->channels * sizeof(short)); - - MMRESULT res = waveOutWrite(data->waveOut, &data->header[i], sizeof(WAVEHDR)); - if (MMSYSERR_NOERROR != res) { - LOGE() << "failed wave out write, err: " << res; - return 0; - } - } - - WaitForSingleObject(data->bufferEndEvent, INFINITE); - } - - return 0; -} - -static void winMMCleanup() -{ - WinMMData* data = s_winMMData; - if (data->audioProcessingDoneEvent) { - SetEvent(data->audioProcessingDoneEvent); - } - - if (data->bufferEndEvent) { - SetEvent(data->bufferEndEvent); - } - - if (data->threadHandle) { - WaitForSingleObject(data->threadHandle, INFINITE); - CloseHandle(data->threadHandle); - } - - if (data->waveOut) { - waveOutReset(data->waveOut); - - for (int i = 0; i < BUFFER_COUNT; ++i) { - waveOutUnprepareHeader(data->waveOut, &data->header[i], sizeof(WAVEHDR)); - if (0 != data->sampleBuffer[i]) { - delete[] data->sampleBuffer[i]; - } - } - waveOutClose(data->waveOut); - } - - if (data->audioProcessingDoneEvent) { - CloseHandle(data->audioProcessingDoneEvent); - } - - if (data->bufferEndEvent) { - CloseHandle(data->bufferEndEvent); - } - - delete s_winMMData; - s_winMMData = nullptr; -} -} - -std::string WinmmDriver::name() const -{ - return "MUAUDIO(WinMM)"; -} - -bool WinmmDriver::open(const Spec& spec, Spec* activeSpec) -{ - s_winMMData = new WinMMData; - ZeroMemory(s_winMMData, sizeof(WinMMData)); - - s_winMMData->samples = spec.output.samplesPerChannel; - s_winMMData->channels = spec.output.audioChannelCount; - s_winMMData->callback = spec.callback; - s_winMMData->userdata = spec.userdata; - - s_winMMData->bufferEndEvent = CreateEvent(0, FALSE, FALSE, 0); - if (0 == s_winMMData->bufferEndEvent) { - LOGE() << "failed create bufferEndEvent"; - winMMCleanup(); - return false; - } - - s_winMMData->audioProcessingDoneEvent = CreateEvent(0, FALSE, FALSE, 0); - if (0 == s_winMMData->audioProcessingDoneEvent) { - LOGE() << "failed create audioProcessingDoneEvent"; - winMMCleanup(); - return false; - } - - WAVEFORMATEX format; - ZeroMemory(&format, sizeof(WAVEFORMATEX)); - - format.nChannels = static_cast(spec.output.audioChannelCount); - format.nSamplesPerSec = static_cast(spec.output.sampleRate); - format.wFormatTag = WAVE_FORMAT_PCM; - format.wBitsPerSample = sizeof(short) * 8; - format.nBlockAlign = (format.nChannels * format.wBitsPerSample) / 8; - format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - - MMRESULT res = waveOutOpen(&s_winMMData->waveOut, WAVE_MAPPER, &format, - reinterpret_cast(s_winMMData->bufferEndEvent), 0, CALLBACK_EVENT); - - if (MMSYSERR_NOERROR != res) { - LOGE() << "failed wave out open, err: " << res; - winMMCleanup(); - return false; - } - - for (int i = 0; i < BUFFER_COUNT; ++i) { - s_winMMData->sampleBuffer[i] = new short[s_winMMData->samples * format.nChannels]; - ZeroMemory(&s_winMMData->header[i], sizeof(WAVEHDR)); - - s_winMMData->header[i].dwBufferLength = s_winMMData->samples * sizeof(short) * format.nChannels; - s_winMMData->header[i].lpData = reinterpret_cast(s_winMMData->sampleBuffer[i]); - - MMRESULT phres = waveOutPrepareHeader(s_winMMData->waveOut, &s_winMMData->header[i], sizeof(WAVEHDR)); - if (MMSYSERR_NOERROR != phres) { - LOGE() << "failed wave out prepare header, err: " << res; - winMMCleanup(); - return false; - } - } - - if (activeSpec) { - *activeSpec = spec; - activeSpec->format = Format::AudioS16; - } - - s_winMMData->threadHandle = CreateThread(nullptr, 0, winMMThread, s_winMMData, 0, nullptr); - if (!s_winMMData->threadHandle) { - LOGE() << "failed create thread"; - winMMCleanup(); - return false; - } - - return true; -} - -void WinmmDriver::close() -{ - winMMCleanup(); -} - -bool WinmmDriver::isOpened() const -{ - return s_winMMData != nullptr; -} - -std::string WinmmDriver::outputDevice() const -{ - NOT_IMPLEMENTED; - return "default"; -} - -bool WinmmDriver::selectOutputDevice(const std::string& /*name*/) -{ - NOT_IMPLEMENTED; - return false; -} - -std::vector WinmmDriver::availableOutputDevices() const -{ - NOT_IMPLEMENTED; - return { "default" }; -} - -async::Notification WinmmDriver::availableOutputDevicesChanged() const -{ - NOT_IMPLEMENTED; - return async::Notification(); -} - -void WinmmDriver::resume() -{ -} - -void WinmmDriver::suspend() -{ -} diff --git a/src/framework/audio/driver/platform/win/winmmdriver.h b/src/framework/audio/driver/platform/win/winmmdriver.h deleted file mode 100644 index 41db99db68c0e..0000000000000 --- a/src/framework/audio/driver/platform/win/winmmdriver.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore Limited and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include "iaudiodriver.h" - -namespace muse::audio { -class WinmmDriver : public IAudioDriver -{ -public: - WinmmDriver() = default; - - std::string name() const override; - bool open(const Spec& spec, Spec* activeSpec) override; - void close() override; - bool isOpened() const override; - - std::string outputDevice() const override; - bool selectOutputDevice(const std::string& name) override; - std::vector availableOutputDevices() const override; - async::Notification availableOutputDevicesChanged() const override; - void resume() override; - void suspend() override; -}; -} diff --git a/src/framework/audio/main/internal/audiodrivercontroller.cpp b/src/framework/audio/main/internal/audiodrivercontroller.cpp index 2aa596d0ea2c7..82014f33595bf 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.cpp +++ b/src/framework/audio/main/internal/audiodrivercontroller.cpp @@ -39,10 +39,7 @@ #endif #ifdef Q_OS_WIN -//#include "audio/driver/platform/win/winmmdriver.h" -//#include "audio/driver/platform/win/wincoreaudiodriver.h" -//#include "audio/driver/platform/win/wasapiaudiodriver.h" -#include "audio/driver/platform/win/wasapiaudiodriver2.h" +#include "audio/driver/platform/win/wasapiaudiodriver.h" #ifdef MUSE_MODULE_AUDIO_ASIO #include "audio/driver/platform/win/asio/asioaudiodriver.h" #endif @@ -79,7 +76,7 @@ IAudioDriverPtr AudioDriverController::createDriver(const std::string& name) con } // required WASAPI or fallback - return std::shared_ptr(new WasapiAudioDriver2()); + return std::shared_ptr(new WasapiAudioDriver()); #endif #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) diff --git a/src/framework/stubs/audio/audioconfigurationstub.cpp b/src/framework/stubs/audio/audioconfigurationstub.cpp index cef7adbc3145d..6395ac68379f6 100644 --- a/src/framework/stubs/audio/audioconfigurationstub.cpp +++ b/src/framework/stubs/audio/audioconfigurationstub.cpp @@ -29,9 +29,14 @@ AudioEngineConfig AudioConfigurationStub::engineConfig() const return {}; } +std::string AudioConfigurationStub::defaultAudioApi() const +{ + return {}; +} + std::string AudioConfigurationStub::currentAudioApi() const { - return std::string(); + return {}; } void AudioConfigurationStub::setCurrentAudioApi(const std::string&) diff --git a/src/framework/stubs/audio/audioconfigurationstub.h b/src/framework/stubs/audio/audioconfigurationstub.h index de2d55e955ef0..480b768c50ebf 100644 --- a/src/framework/stubs/audio/audioconfigurationstub.h +++ b/src/framework/stubs/audio/audioconfigurationstub.h @@ -29,6 +29,7 @@ class AudioConfigurationStub : public IAudioConfiguration public: AudioEngineConfig engineConfig() const override; + std::string defaultAudioApi() const override; std::string currentAudioApi() const override; void setCurrentAudioApi(const std::string& name) override; async::Notification currentAudioApiChanged() const override; diff --git a/src/framework/stubs/audio/audiodrivercontrollerstub.cpp b/src/framework/stubs/audio/audiodrivercontrollerstub.cpp index b8487688c9291..72b26115a88f7 100644 --- a/src/framework/stubs/audio/audiodrivercontrollerstub.cpp +++ b/src/framework/stubs/audio/audiodrivercontrollerstub.cpp @@ -22,46 +22,104 @@ #include "audiodrivercontrollerstub.h" -#include "audiodriverstub.h" - +using namespace muse; using namespace muse::audio; -std::string AudioDriverControllerStub::currentAudioApi() const +// Api +std::vector AudioDriverControllerStub::availableAudioApiList() const { return {}; } -IAudioDriverPtr AudioDriverControllerStub::audioDriver() const +std::string AudioDriverControllerStub::currentAudioApi() const { - if (!m_audioDriver) { - m_audioDriver = std::make_shared(); - } + return {}; +} - return m_audioDriver; +void AudioDriverControllerStub::changeCurrentAudioApi(const std::string&) +{ } -void AudioDriverControllerStub::changeAudioDriver(const std::string&) +async::Notification AudioDriverControllerStub::currentAudioApiChanged() const { + return async::Notification(); } -muse::async::Notification AudioDriverControllerStub::audioDriverChanged() const +// Current driver operation +AudioDeviceList AudioDriverControllerStub::availableOutputDevices() const { return {}; } -std::vector AudioDriverControllerStub::availableAudioApiList() const +async::Notification AudioDriverControllerStub::availableOutputDevicesChanged() const +{ + return async::Notification(); +} + +bool AudioDriverControllerStub::open(const IAudioDriver::Spec&, IAudioDriver::Spec*) +{ + return false; +} + +void AudioDriverControllerStub::close() +{ +} + +bool AudioDriverControllerStub::isOpened() const +{ + return false; +} + +const IAudioDriver::Spec& AudioDriverControllerStub::activeSpec() const +{ + static IAudioDriver::Spec spec; + return spec; +} + +async::Channel AudioDriverControllerStub::activeSpecChanged() const +{ + return async::Channel(); +} + +AudioDeviceID AudioDriverControllerStub::outputDevice() const { return {}; } -void AudioDriverControllerStub::selectOutputDevice(const std::string&) +bool AudioDriverControllerStub::selectOutputDevice(const AudioDeviceID&) { + return false; +} + +async::Notification AudioDriverControllerStub::outputDeviceChanged() const +{ + return async::Notification(); +} + +std::vector AudioDriverControllerStub::availableOutputDeviceBufferSizes() const +{ + return {}; } void AudioDriverControllerStub::changeBufferSize(samples_t) { } +async::Notification AudioDriverControllerStub::outputDeviceBufferSizeChanged() const +{ + return async::Notification(); +} + +std::vector AudioDriverControllerStub::availableOutputDeviceSampleRates() const +{ + return {}; +} + void AudioDriverControllerStub::changeSampleRate(sample_rate_t) { } + +async::Notification AudioDriverControllerStub::outputDeviceSampleRateChanged() const +{ + return async::Notification(); +} diff --git a/src/framework/stubs/audio/audiodrivercontrollerstub.h b/src/framework/stubs/audio/audiodrivercontrollerstub.h index 9fbcbbc7219e9..a057e66e6ecb9 100644 --- a/src/framework/stubs/audio/audiodrivercontrollerstub.h +++ b/src/framework/stubs/audio/audiodrivercontrollerstub.h @@ -29,16 +29,35 @@ class AudioDriverControllerStub : public IAudioDriverController { public: + // Api + std::vector availableAudioApiList() const override; + std::string currentAudioApi() const override; - IAudioDriverPtr audioDriver() const override; - void changeAudioDriver(const std::string& name) override; - async::Notification audioDriverChanged() const override; + void changeCurrentAudioApi(const std::string& name) override; + async::Notification currentAudioApiChanged() const override; - std::vector availableAudioApiList() const override; + // Current driver operation + AudioDeviceList availableOutputDevices() const override; + async::Notification availableOutputDevicesChanged() const override; + + bool open(const IAudioDriver::Spec& spec, IAudioDriver::Spec* activeSpec) override; + void close() override; + bool isOpened() const override; + + const IAudioDriver::Spec& activeSpec() const override; + async::Channel activeSpecChanged() const override; + + AudioDeviceID outputDevice() const override; + bool selectOutputDevice(const AudioDeviceID& deviceId) override; + async::Notification outputDeviceChanged() const override; + + std::vector availableOutputDeviceBufferSizes() const override; + void changeBufferSize(samples_t samples) override; + async::Notification outputDeviceBufferSizeChanged() const override; - void selectOutputDevice(const std::string& deviceId) override; - void changeBufferSize(samples_t samples) override; - void changeSampleRate(sample_rate_t sampleRate) override; + std::vector availableOutputDeviceSampleRates() const override; + void changeSampleRate(sample_rate_t sampleRate) override; + async::Notification outputDeviceSampleRateChanged() const override; private: mutable IAudioDriverPtr m_audioDriver; diff --git a/src/framework/stubs/audio/audiodriverstub.cpp b/src/framework/stubs/audio/audiodriverstub.cpp index 83e9f84cb85de..e4ffe890103f6 100644 --- a/src/framework/stubs/audio/audiodriverstub.cpp +++ b/src/framework/stubs/audio/audiodriverstub.cpp @@ -30,7 +30,12 @@ void AudioDriverStub::init() std::string AudioDriverStub::name() const { - return std::string(); + return {}; +} + +AudioDeviceID AudioDriverStub::defaultDevice() const +{ + return {}; } bool AudioDriverStub::open(const IAudioDriver::Spec&, IAudioDriver::Spec*) @@ -59,26 +64,6 @@ async::Channel AudioDriverStub::activeSpecChanged() const return activeSpecChanged; } -std::string AudioDriverStub::outputDevice() const -{ - return std::string(); -} - -bool AudioDriverStub::selectOutputDevice(const std::string&) -{ - return false; -} - -bool AudioDriverStub::resetToDefaultOutputDevice() -{ - return false; -} - -async::Notification AudioDriverStub::outputDeviceChanged() const -{ - return async::Notification(); -} - AudioDeviceList AudioDriverStub::availableOutputDevices() const { return {}; @@ -89,40 +74,12 @@ async::Notification AudioDriverStub::availableOutputDevicesChanged() const return async::Notification(); } -bool AudioDriverStub::setOutputDeviceBufferSize(unsigned int) -{ - return false; -} - -async::Notification AudioDriverStub::outputDeviceBufferSizeChanged() const -{ - return async::Notification(); -} - -std::vector AudioDriverStub::availableOutputDeviceBufferSizes() const +std::vector AudioDriverStub::availableOutputDeviceBufferSizes() const { return {}; } -bool AudioDriverStub::setOutputDeviceSampleRate(unsigned int) -{ - return false; -} - -async::Notification AudioDriverStub::outputDeviceSampleRateChanged() const -{ - return async::Notification(); -} - -std::vector AudioDriverStub::availableOutputDeviceSampleRates() const +std::vector AudioDriverStub::availableOutputDeviceSampleRates() const { return {}; } - -void AudioDriverStub::resume() -{ -} - -void AudioDriverStub::suspend() -{ -} diff --git a/src/framework/stubs/audio/audiodriverstub.h b/src/framework/stubs/audio/audiodriverstub.h index 11b64b20ee65a..2a953f8c9046a 100644 --- a/src/framework/stubs/audio/audiodriverstub.h +++ b/src/framework/stubs/audio/audiodriverstub.h @@ -30,6 +30,9 @@ class AudioDriverStub : public IAudioDriver void init() override; std::string name() const override; + + AudioDeviceID defaultDevice() const override; + bool open(const Spec& spec, Spec* activeSpec) override; void close() override; bool isOpened() const override; @@ -37,25 +40,10 @@ class AudioDriverStub : public IAudioDriver const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; - AudioDeviceID outputDevice() const override; - bool selectOutputDevice(const AudioDeviceID& id) override; - bool resetToDefaultOutputDevice() override; - async::Notification outputDeviceChanged() const override; - AudioDeviceList availableOutputDevices() const override; async::Notification availableOutputDevicesChanged() const override; - bool setOutputDeviceBufferSize(unsigned int bufferSize) override; - async::Notification outputDeviceBufferSizeChanged() const override; - - std::vector availableOutputDeviceBufferSizes() const override; - - bool setOutputDeviceSampleRate(unsigned int bufferSize) override; - async::Notification outputDeviceSampleRateChanged() const override; - - std::vector availableOutputDeviceSampleRates() const override; - - void resume() override; - void suspend() override; + std::vector availableOutputDeviceBufferSizes() const override; + std::vector availableOutputDeviceSampleRates() const override; }; } From 1b967e7c82c966f5d379817f11eee805120dd3cd Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Mon, 24 Nov 2025 08:22:41 +0300 Subject: [PATCH 7/7] added check of available asio drivers --- .../platform/win/asio/asioaudiodriver.cpp | 46 +++++++++++++++++-- .../platform/win/asio/asioaudiodriver.h | 1 + .../main/internal/audiodrivercontroller.cpp | 12 +++-- .../main/internal/audiodrivercontroller.h | 1 + 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp index 088377ad4e5d4..b312551baeb18 100644 --- a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp +++ b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.cpp @@ -26,6 +26,7 @@ #undef UNICODE #include "ASIOSDK/common/asiosys.h" #include "ASIOSDK/common/asio.h" +#include "ASIOSDK/common/iasiodrv.h" #include "ASIOSDK/host/asiodrivers.h" #include "log.h" @@ -36,6 +37,7 @@ using namespace muse::audio; struct AsioData { // Drivers list AsioDrivers drivers; + std::set baddrivers; // ASIOInit() ASIODriverInfo driverInfo; @@ -612,6 +614,40 @@ std::vector AsioAudioDriver::availableOutputDeviceSampleRates() c }; } +static bool isDriverAvailable(long index, const char* name) +{ + //! NOTE We remember drivers that are not loaded or initialized + //! until the end of the application's operation. + //! Because there are drivers that cannot be reloaded after closing + //! (to implement checking every time) + //! For example: Audient USB Audio ASIO Driver + //! When we reopen it, it crashes. + if (s_adata.baddrivers.find(std::string(name)) != s_adata.baddrivers.end()) { + return false; + } + + IASIO* driver = 0; + LONG ret = s_adata.drivers.asioOpenDriver(index, (void**)&driver); + if (ret == DRVERR_DEVICE_ALREADY_OPEN) { + return true; + } + + if (ret != 0) { + s_adata.baddrivers.insert(std::string(name)); + return false; + } + + void* sysRef = nullptr; + bool ok = driver->init(sysRef) == ASIOTrue; + if (!ok) { + s_adata.baddrivers.insert(std::string(name)); + } + + s_adata.drivers.asioCloseDriver(index); + + return ok; +} + AudioDeviceList AsioAudioDriver::availableOutputDevices() const { char names[16][32]; @@ -626,10 +662,12 @@ AudioDeviceList AsioAudioDriver::availableOutputDevices() const AudioDeviceList devices; devices.reserve(count); for (long i = 0; i < count; i++) { - AudioDevice d; - d.id = names[i]; - d.name = d.id; - devices.push_back(std::move(d)); + if (isDriverAvailable(i, names[i])) { + AudioDevice d; + d.id = names[i]; + d.name = d.id; + devices.push_back(std::move(d)); + } } return devices; diff --git a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h index 0176bad391014..01d0bab2af70b 100644 --- a/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h +++ b/src/framework/audio/driver/platform/win/asio/asioaudiodriver.h @@ -59,6 +59,7 @@ class AsioAudioDriver : public IAudioDriver, public async::Asyncable std::thread m_thread; std::atomic m_running = false; + AudioDeviceID m_audioDeviceId; async::Channel m_activeSpecChanged; async::Notification m_availableOutputDevicesChanged; }; diff --git a/src/framework/audio/main/internal/audiodrivercontroller.cpp b/src/framework/audio/main/internal/audiodrivercontroller.cpp index 82014f33595bf..cf7819e9b5cc1 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.cpp +++ b/src/framework/audio/main/internal/audiodrivercontroller.cpp @@ -211,7 +211,6 @@ void AudioDriverController::changeCurrentAudioApi(const std::string& name) return; } - IAudioDriver::Spec oldSpec = m_audioDriver->activeSpec(); if (m_audioDriver->isOpened()) { m_audioDriver->close(); } @@ -223,8 +222,13 @@ void AudioDriverController::changeCurrentAudioApi(const std::string& name) // reset to default IAudioDriver::Spec spec = defaultSpec(); - spec.callback = oldSpec.callback; - m_audioDriver->open(spec, nullptr); + spec.callback = m_callback; + + if (!spec.deviceId.empty()) { + m_audioDriver->open(spec, nullptr); + } else { + LOGW() << "no devices for " << name; + } configuration()->setCurrentAudioApi(name); m_currentAudioApiChanged.notify(); @@ -251,6 +255,8 @@ async::Notification AudioDriverController::availableOutputDevicesChanged() const bool AudioDriverController::open(const IAudioDriver::Spec& spec, IAudioDriver::Spec* activeSpec) { + m_callback = spec.callback; + const std::string currentAudioApi = configuration()->currentAudioApi(); IAudioDriverPtr driver = createDriver(currentAudioApi); driver->init(); diff --git a/src/framework/audio/main/internal/audiodrivercontroller.h b/src/framework/audio/main/internal/audiodrivercontroller.h index b20853d5184b8..b1cfd22b17dbd 100644 --- a/src/framework/audio/main/internal/audiodrivercontroller.h +++ b/src/framework/audio/main/internal/audiodrivercontroller.h @@ -79,6 +79,7 @@ class AudioDriverController : public IAudioDriverController, public Injectable, void checkOutputDevice(); void updateOutputSpec(); + IAudioDriver::Callback m_callback; IAudioDriverPtr m_audioDriver; async::Notification m_currentAudioApiChanged; async::Notification m_availableOutputDevicesChanged;