// SPDX-License-Identifier: MIT // // EmulationStation Desktop Edition // VolumeControl.cpp // // Controls system audio volume. // #include "VolumeControl.h" #include "Log.h" #include "utils/MathUtil.h" #if defined(_WIN64) #include #endif #if defined(__linux__) std::string VolumeControl::mixerName = "Master"; std::string VolumeControl::mixerCard = "default"; #endif VolumeControl* VolumeControl::sInstance = nullptr; VolumeControl::VolumeControl() #if defined(__linux__) : mixerIndex(0) , mixerHandle(nullptr) , mixerElem(nullptr) , mixerSelemId(nullptr) #elif defined(_WIN64) : mixerHandle(nullptr) , endpointVolume(nullptr) #endif { init(); } VolumeControl::~VolumeControl() { deinit(); #if defined(__linux__) snd_config_update_free_global(); #endif } VolumeControl* VolumeControl::getInstance() { // Check if a VolumeControl instance is already created, and if not then create it. if (!sInstance) sInstance = new VolumeControl(); return sInstance; } void VolumeControl::deleteInstance() { if (sInstance) { delete sInstance; sInstance = nullptr; } } void VolumeControl::init() { // Initialize audio mixer interface. #if defined(__linux__) // Try to open mixer device. if (mixerHandle == nullptr) { snd_mixer_selem_id_alloca(&mixerSelemId); // Sets simple-mixer index and name. snd_mixer_selem_id_set_index(mixerSelemId, mixerIndex); snd_mixer_selem_id_set_name(mixerSelemId, mixerName.c_str()); if (snd_mixer_open(&mixerHandle, 0) >= 0) { LOG(LogDebug) << "VolumeControl::init(): Opened ALSA mixer"; if (snd_mixer_attach(mixerHandle, mixerCard.c_str()) >= 0) { LOG(LogDebug) << "VolumeControl::init(): Attached to default card"; if (snd_mixer_selem_register(mixerHandle, nullptr, nullptr) >= 0) { LOG(LogDebug) << "VolumeControl::init(): Registered simple element class"; if (snd_mixer_load(mixerHandle) >= 0) { LOG(LogDebug) << "VolumeControl::init(): Loaded mixer elements"; // Find elements. mixerElem = snd_mixer_find_selem(mixerHandle, mixerSelemId); if (mixerElem != nullptr) { LOG(LogDebug) << "VolumeControl::init(): Mixer initialized"; } else { LOG(LogError) << "VolumeControl::init(): Failed to find mixer elements!"; snd_mixer_close(mixerHandle); mixerHandle = nullptr; } } else { LOG(LogError) << "VolumeControl::init(): Failed to load mixer elements!"; snd_mixer_close(mixerHandle); mixerHandle = nullptr; } } else { LOG(LogError) << "VolumeControl::init(): Failed to register simple element class!"; snd_mixer_close(mixerHandle); mixerHandle = nullptr; } } else { LOG(LogError) << "VolumeControl::init(): Failed to attach to default card!"; snd_mixer_close(mixerHandle); mixerHandle = nullptr; } } else { LOG(LogError) << "VolumeControl::init(): Failed to open ALSA mixer!"; } } #elif defined(_WIN64) // Windows Vista or above. if (endpointVolume == nullptr) { CoInitialize(nullptr); IMMDeviceEnumerator* deviceEnumerator = nullptr; CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), reinterpret_cast(&deviceEnumerator)); if (deviceEnumerator != nullptr) { // Get default endpoint. IMMDevice* defaultDevice = nullptr; deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice); if (defaultDevice != nullptr) { // Retrieve endpoint volume. defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast(&endpointVolume)); if (endpointVolume == nullptr) LOG(LogError) << "VolumeControl::init(): " "Failed to get default audio endpoint volume!"; // Release default device. we don't need it anymore. defaultDevice->Release(); } else { LOG(LogError) << "VolumeControl::init(): Failed to get default audio endpoint!"; } // Release device enumerator. we don't need it anymore. deviceEnumerator->Release(); } else { LOG(LogError) << "VolumeControl::init(): Failed to get audio endpoint enumerator!"; CoUninitialize(); } } #endif } void VolumeControl::deinit() { // Deinitialize audio mixer interface. #if defined(__linux__) if (mixerHandle != nullptr) { snd_mixer_detach(mixerHandle, mixerCard.c_str()); snd_mixer_free(mixerHandle); snd_mixer_close(mixerHandle); mixerHandle = nullptr; mixerElem = nullptr; } #elif defined(_WIN64) if (endpointVolume != nullptr) { endpointVolume->Release(); endpointVolume = nullptr; CoUninitialize(); } #endif } int VolumeControl::getVolume() const { int volume = 0; #if defined(__linux__) if (mixerElem != nullptr) { // Get volume range. long minVolume; long maxVolume; if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) { long rawVolume; if (snd_mixer_selem_get_playback_volume(mixerElem, SND_MIXER_SCHN_MONO, &rawVolume) == 0) { // Bring into range 0-100. rawVolume -= minVolume; if (rawVolume > 0) volume = (rawVolume * 100.0) / (maxVolume - minVolume) + 0.5; } else { LOG(LogError) << "VolumeControl::getVolume(): Failed to get mixer volume"; } } else { LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range"; } } #elif defined(_WIN64) if (endpointVolume != nullptr) { // Windows Vista or above, uses EndpointVolume API. float floatVolume = 0.0f; // 0-1 if (endpointVolume->GetMasterVolumeLevelScalar(&floatVolume) == S_OK) { volume = static_cast(std::round(floatVolume * 100.0f)); LOG(LogInfo) << "System audio volume is " << volume; } else { LOG(LogError) << "VolumeControl::getVolume(): Failed to get master volume!"; } } #endif volume = glm::clamp(volume, 0, 100); return volume; } void VolumeControl::setVolume(int volume) { volume = glm::clamp(volume, 0, 100); #if defined(__linux__) if (mixerElem != nullptr) { // Get volume range. long minVolume; long maxVolume; if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) { // Bring into minVolume-maxVolume range and set. long rawVolume = (volume * (maxVolume - minVolume) / 100) + minVolume; if (snd_mixer_selem_set_playback_volume(mixerElem, SND_MIXER_SCHN_FRONT_LEFT, rawVolume) < 0 || snd_mixer_selem_set_playback_volume(mixerElem, SND_MIXER_SCHN_FRONT_RIGHT, rawVolume) < 0) { LOG(LogError) << "VolumeControl::getVolume(): Failed to set mixer volume"; } } else { LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range"; } } #elif defined(_WIN64) if (endpointVolume != nullptr) { // Windows Vista or above, uses EndpointVolume API. float floatVolume = 0.0f; // 0-1 if (volume > 0) floatVolume = static_cast(volume) / 100.0f; if (endpointVolume->SetMasterVolumeLevelScalar(floatVolume, nullptr) != S_OK) LOG(LogError) << "VolumeControl::setVolume(): Failed to set master volume"; } #endif }