From 6df27b89921e822311759b837529ea0a91f74cc0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 23 Oct 2025 22:54:05 +0300 Subject: [PATCH] add audio.get_output_devices_names, set_input_device, set_output_device --- src/audio/AL/ALAudio.cpp | 114 +++++++++++++++------- src/audio/AL/ALAudio.hpp | 10 +- src/audio/NoAudio.hpp | 8 +- src/audio/audio.cpp | 34 ++++++- src/audio/audio.hpp | 19 +++- src/logic/scripting/lua/libs/libaudio.cpp | 26 +++++ 6 files changed, 169 insertions(+), 42 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index ce73823c..9e555133 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -30,13 +30,14 @@ const char* alc_error_to_string(ALCenum error) { } } -static void check_alc_errors(ALCdevice* device, const char* context) { +static bool check_alc_errors(ALCdevice* device, const char* context) { ALCenum error = alcGetError(device); if (error == ALC_NO_ERROR) { - return; + return false; } logger.error() << context << ": " << alc_error_to_string(error) << "(" << error << ")"; + return true; } ALSound::ALSound( @@ -419,6 +420,8 @@ int ALSpeaker::getPriority() const { return priority; } +static bool alc_enumeration_ext = false; + ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) : device(device), context(context) { ALCint size; @@ -431,9 +434,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) maxSources = attrs[i + 1]; } } - auto devices = getAvailableDevices(); - logger.info() << "devices:"; - for (auto& name : devices) { + auto outputDevices = getOutputDeviceNames(); + logger.info() << "output devices:"; + for (auto& name : outputDevices) { + logger.info() << " " << name; + } + + auto inputDevices = getInputDeviceNames(); + logger.info() << "input devices:"; + for (auto& name : inputDevices) { logger.info() << " " << name; } } @@ -482,35 +491,59 @@ std::unique_ptr ALAudio::openStream( std::vector ALAudio::getInputDeviceNames() { std::vector devices; - if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { - auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); - if (deviceList == nullptr) { - logger.warning() << "no input devices found"; - return devices; - } - while (*deviceList) { - std::string deviceName(deviceList); - devices.push_back(deviceName); - deviceList += deviceName.length() + 1; - } - } else { + if (!alc_enumeration_ext) { logger.warning() << "enumeration extension is not available"; + return devices; + } + + auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + if (deviceList == nullptr) { + logger.warning() << "no input devices found"; + return devices; + } + while (*deviceList) { + std::string deviceName(deviceList); + devices.push_back(deviceName); + deviceList += deviceName.length() + 1; + } + + return devices; +} + +std::vector ALAudio::getOutputDeviceNames() { + std::vector devices; + + if (!alc_enumeration_ext) { + logger.warning() << "enumeration extension is not available"; + return devices; + } + + auto deviceList = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + if (deviceList == nullptr) { + logger.warning() << "no input devices found"; + return devices; + } + while (*deviceList) { + std::string deviceName(deviceList); + devices.push_back(deviceName); + deviceList += deviceName.length() + 1; } return devices; } std::unique_ptr ALAudio::openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample ) { uint bps = bitsPerSample >> 3; ALCdevice* device = alcCaptureOpenDevice( - nullptr, + deviceName, sampleRate, AL::to_al_format(channels, bitsPerSample), sampleRate * channels * bps / 8 ); - check_alc_errors(device, "alcCaptureOpenDevice"); + if (check_alc_errors(device, "alcCaptureOpenDevice")) + return nullptr; return std::make_unique( this, device, channels, bitsPerSample @@ -518,6 +551,8 @@ std::unique_ptr ALAudio::openInputDevice( } std::unique_ptr ALAudio::create() { + alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); + ALCdevice* device = alcOpenDevice(nullptr); if (device == nullptr) return nullptr; ALCcontext* context = alcCreateContext(device, nullptr); @@ -574,22 +609,35 @@ void ALAudio::freeBuffer(uint buffer) { freebuffers.push_back(buffer); } -std::vector ALAudio::getAvailableDevices() const { - std::vector devicesVec; - - const ALCchar* devices; - devices = alcGetString(device, ALC_DEVICE_SPECIFIER); - if (!AL_GET_ERROR()) { - return devicesVec; +void ALAudio::setOutputDevice(const std::string& deviceName) { + ALCdevice* newDevice = alcOpenDevice(deviceName.c_str()); + if (newDevice == nullptr) { + logger.error() << "failed to open output device: " << deviceName; + return; } - const char* ptr = devices; - do { - devicesVec.emplace_back(ptr); - ptr += devicesVec.back().size() + 1; - } while (ptr[0]); + ALCcontext* newContext = alcCreateContext(newDevice, nullptr); + if (!alcMakeContextCurrent(newContext)) { + logger.error() << "failed to make context current for device: " + << deviceName; + alcCloseDevice(newDevice); + return; + } - return devicesVec; + // Clean up old device and context + alcMakeContextCurrent(nullptr); + check_alc_errors(device, "alcMakeContextCurrent"); + alcDestroyContext(context); + check_alc_errors(device, "alcDestroyContext"); + if (!alcCloseDevice(device)) { + logger.error() << "old device not closed"; + } + + // Update to new device and context + device = newDevice; + context = newContext; + + logger.info() << "switched output device to: " << deviceName; } void ALAudio::setListener( diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 8e4ad02c..d8fe2000 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -172,8 +172,6 @@ namespace audio { void freeSource(uint source); void freeBuffer(uint buffer); - std::vector getAvailableDevices() const; - std::unique_ptr createSound( std::shared_ptr pcm, bool keepPCM ) override; @@ -183,11 +181,17 @@ namespace audio { ) override; std::unique_ptr openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, + uint sampleRate, + uint channels, + uint bitsPerSample ) override; + std::vector getOutputDeviceNames() override; std::vector getInputDeviceNames() override; + void setOutputDevice(const std::string& deviceName) override; + void setListener( glm::vec3 position, glm::vec3 velocity, diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index c5dbae41..a7b4232c 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -77,7 +77,7 @@ namespace audio { ) override; std::unique_ptr openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample ) override { return nullptr; } @@ -85,6 +85,12 @@ namespace audio { std::vector getInputDeviceNames() override { return {}; } + std::vector getOutputDeviceNames() override { + return {}; + } + + void setOutputDevice(const std::string& deviceName) override { + } void setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 72b22df5..06a36c06 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -183,7 +183,7 @@ void audio::initialize(bool enabled, AudioSettings& settings) { }, true)); } - input_device = backend->openInputDevice(44100, 1, 16); + input_device = backend->openInputDevice(nullptr, 44100, 1, 16); if (input_device) { input_device->startCapture(); } @@ -254,15 +254,43 @@ std::unique_ptr audio::open_stream( } std::unique_ptr audio::open_input_device( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample ) { - return backend->openInputDevice(sampleRate, channels, bitsPerSample); + return backend->openInputDevice( + deviceName, sampleRate, channels, bitsPerSample + ); } std::vector audio::get_input_devices_names() { return backend->getInputDeviceNames(); } +std::vector audio::get_output_devices_names() { + return backend->getOutputDeviceNames(); +} + +void audio::set_input_device(const std::string& deviceName) { + auto newDevice = backend->openInputDevice( + deviceName.empty() ? nullptr : deviceName.c_str(), 44100, 1, 16 + ); + if (newDevice == nullptr) { + logger.error() << "could not open input device: " << deviceName; + return; + } + + if (input_device) { + input_device->stopCapture(); + } + input_device = std::move(newDevice); + if (input_device) { + input_device->startCapture(); + } +} + +void audio::set_output_device(const std::string& deviceName) { + backend->setOutputDevice(deviceName); +} + void audio::set_listener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up ) { diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 3364d89e..47fab4f5 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -362,7 +362,10 @@ namespace audio { std::shared_ptr stream, bool keepSource ) = 0; virtual std::unique_ptr openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, + uint sampleRate, + uint channels, + uint bitsPerSample ) = 0; virtual void setListener( glm::vec3 position, @@ -371,6 +374,8 @@ namespace audio { glm::vec3 up ) = 0; virtual std::vector getInputDeviceNames() = 0; + virtual std::vector getOutputDeviceNames() = 0; + virtual void setOutputDevice(const std::string& deviceName) = 0; virtual void update(double delta) = 0; /// @brief Check if backend is an abstraction that does not internally @@ -432,13 +437,23 @@ namespace audio { /// @param bitsPerSample number of bits per sample (8 or 16) /// @return new InputDevice instance or nullptr std::unique_ptr open_input_device( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, + uint sampleRate, + uint channels, + uint bitsPerSample ); /// @brief Retrieve names of available audio input devices /// @return list of device names std::vector get_input_devices_names(); + /// @brief Retrieve names of available audio output devices + /// @return list of device names + std::vector get_output_devices_names(); + + void set_input_device(const std::string& deviceName); + void set_output_device(const std::string& deviceName); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index 951522d9..d240ec2e 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -419,6 +419,29 @@ static int l_audio_get_input_devices_names(lua::State* L) { return 1; } +static int l_audio_get_output_devices_names(lua::State* L) { + auto device_names = audio::get_output_devices_names(); + lua::createtable(L, device_names.size(), 0); + int index = 1; + for (const auto& name : device_names) { + lua::pushstring(L, name.c_str()); + lua::rawseti(L, index++); + } + return 1; +} + +static int l_audio_set_input_device(lua::State* L) { + auto device_name = lua::tostring(L, 1); + audio::set_input_device(device_name); + return 0; +} + +static int l_audio_set_output_device(lua::State* L) { + auto device_name = lua::tostring(L, 1); + audio::set_output_device(device_name); + return 0; +} + const luaL_Reg audiolib[] = { {"play_sound", lua::wrap}, {"play_sound_2d", lua::wrap}, @@ -446,5 +469,8 @@ const luaL_Reg audiolib[] = { {"count_streams", lua::wrap}, {"fetch_input", lua::wrap}, {"get_input_devices_names", lua::wrap}, + {"get_output_devices_names", lua::wrap}, + {"set_input_device", lua::wrap}, + {"set_output_device", lua::wrap}, {nullptr, nullptr} };