2020-09-16 20:14:35 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-06-23 18:07:00 +00:00
|
|
|
//
|
2020-09-16 20:14:35 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-23 18:07:00 +00:00
|
|
|
// VolumeControl.cpp
|
|
|
|
//
|
|
|
|
// Controls system audio volume.
|
|
|
|
//
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "VolumeControl.h"
|
|
|
|
|
|
|
|
#include "Log.h"
|
2021-08-17 20:11:16 +00:00
|
|
|
#include "utils/MathUtil.h"
|
2020-07-09 17:24:20 +00:00
|
|
|
|
2020-12-28 22:23:01 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
#include <cmath>
|
|
|
|
#endif
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
#if defined(__linux__)
|
2020-12-16 23:09:26 +00:00
|
|
|
std::string VolumeControl::mixerName = "Master";
|
|
|
|
std::string VolumeControl::mixerCard = "default";
|
2014-06-25 16:29:58 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
VolumeControl::VolumeControl()
|
2022-01-16 17:18:28 +00:00
|
|
|
// clang-format off
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(__linux__)
|
2022-01-16 17:18:28 +00:00
|
|
|
: mixerIndex {0}
|
|
|
|
, mixerHandle {nullptr}
|
|
|
|
, mixerElem {nullptr}
|
|
|
|
, mixerSelemId {nullptr}
|
2021-07-07 18:03:42 +00:00
|
|
|
#elif defined(_WIN64)
|
2022-01-16 17:18:28 +00:00
|
|
|
: mixerHandle {nullptr}
|
|
|
|
, endpointVolume {nullptr}
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2022-01-16 17:18:28 +00:00
|
|
|
// clang-format on
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-23 18:07:00 +00:00
|
|
|
init();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 20:55:56 +00:00
|
|
|
VolumeControl::~VolumeControl()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2021-03-18 20:55:56 +00:00
|
|
|
deinit();
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(__linux__)
|
2021-03-18 20:55:56 +00:00
|
|
|
snd_config_update_free_global();
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VolumeControl::init()
|
|
|
|
{
|
2020-06-23 18:07:00 +00:00
|
|
|
// Initialize audio mixer interface.
|
2021-07-07 18:03:42 +00:00
|
|
|
|
|
|
|
#if defined(__linux__)
|
2020-06-23 18:07:00 +00:00
|
|
|
// 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);
|
2020-12-16 22:59:00 +00:00
|
|
|
snd_mixer_selem_id_set_name(mixerSelemId, mixerName.c_str());
|
2020-06-23 18:07:00 +00:00
|
|
|
if (snd_mixer_open(&mixerHandle, 0) >= 0) {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogDebug) << "VolumeControl::init(): Opened ALSA mixer";
|
2020-12-16 22:59:00 +00:00
|
|
|
if (snd_mixer_attach(mixerHandle, mixerCard.c_str()) >= 0) {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogDebug) << "VolumeControl::init(): Attached to default card";
|
2020-06-23 18:07:00 +00:00
|
|
|
if (snd_mixer_selem_register(mixerHandle, nullptr, nullptr) >= 0) {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogDebug) << "VolumeControl::init(): Registered simple element class";
|
2020-06-23 18:07:00 +00:00
|
|
|
if (snd_mixer_load(mixerHandle) >= 0) {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogDebug) << "VolumeControl::init(): Loaded mixer elements";
|
2021-03-18 20:55:56 +00:00
|
|
|
// Find elements.
|
2020-06-23 18:07:00 +00:00
|
|
|
mixerElem = snd_mixer_find_selem(mixerHandle, mixerSelemId);
|
|
|
|
if (mixerElem != nullptr) {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogDebug) << "VolumeControl::init(): Mixer initialized";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-07-07 18:03:42 +00:00
|
|
|
LOG(LogError)
|
|
|
|
<< "VolumeControl::init(): Failed to find mixer elements!";
|
2020-06-23 18:07:00 +00:00
|
|
|
snd_mixer_close(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogError) << "VolumeControl::init(): Failed to load mixer elements!";
|
2020-06-23 18:07:00 +00:00
|
|
|
snd_mixer_close(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-07-07 18:03:42 +00:00
|
|
|
LOG(LogError)
|
|
|
|
<< "VolumeControl::init(): Failed to register simple element class!";
|
2020-06-23 18:07:00 +00:00
|
|
|
snd_mixer_close(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogError) << "VolumeControl::init(): Failed to attach to default card!";
|
2020-06-23 18:07:00 +00:00
|
|
|
snd_mixer_close(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogError) << "VolumeControl::init(): Failed to open ALSA mixer!";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#elif defined(_WIN64)
|
2021-03-15 16:41:28 +00:00
|
|
|
// Windows Vista or above.
|
|
|
|
if (endpointVolume == nullptr) {
|
|
|
|
CoInitialize(nullptr);
|
|
|
|
IMMDeviceEnumerator* deviceEnumerator = nullptr;
|
|
|
|
CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
|
2021-07-07 18:03:42 +00:00
|
|
|
__uuidof(IMMDeviceEnumerator),
|
|
|
|
reinterpret_cast<LPVOID*>(&deviceEnumerator));
|
2021-03-15 16:41:28 +00:00
|
|
|
if (deviceEnumerator != nullptr) {
|
|
|
|
// Get default endpoint.
|
2021-07-07 18:03:42 +00:00
|
|
|
IMMDevice* defaultDevice = nullptr;
|
2021-03-15 16:41:28 +00:00
|
|
|
deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
|
|
|
|
if (defaultDevice != nullptr) {
|
|
|
|
// Retrieve endpoint volume.
|
2021-07-07 18:03:42 +00:00
|
|
|
defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER,
|
|
|
|
nullptr, reinterpret_cast<LPVOID*>(&endpointVolume));
|
2021-09-19 13:27:32 +00:00
|
|
|
if (endpointVolume == nullptr) {
|
2021-03-15 16:41:28 +00:00
|
|
|
LOG(LogError) << "VolumeControl::init(): "
|
2021-07-07 18:03:42 +00:00
|
|
|
"Failed to get default audio endpoint volume!";
|
2021-09-19 13:27:32 +00:00
|
|
|
}
|
2021-03-15 16:41:28 +00:00
|
|
|
// Release default device. we don't need it anymore.
|
|
|
|
defaultDevice->Release();
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-07-07 18:03:42 +00:00
|
|
|
LOG(LogError) << "VolumeControl::init(): Failed to get default audio endpoint!";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
2021-03-15 16:41:28 +00:00
|
|
|
// Release device enumerator. we don't need it anymore.
|
|
|
|
deviceEnumerator->Release();
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
2021-03-15 16:41:28 +00:00
|
|
|
else {
|
|
|
|
LOG(LogError) << "VolumeControl::init(): Failed to get audio endpoint enumerator!";
|
|
|
|
CoUninitialize();
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VolumeControl::deinit()
|
|
|
|
{
|
2020-06-23 18:07:00 +00:00
|
|
|
// Deinitialize audio mixer interface.
|
2021-07-07 18:03:42 +00:00
|
|
|
|
|
|
|
#if defined(__linux__)
|
2020-06-23 18:07:00 +00:00
|
|
|
if (mixerHandle != nullptr) {
|
2020-12-16 22:59:00 +00:00
|
|
|
snd_mixer_detach(mixerHandle, mixerCard.c_str());
|
2020-06-23 18:07:00 +00:00
|
|
|
snd_mixer_free(mixerHandle);
|
|
|
|
snd_mixer_close(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
mixerElem = nullptr;
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#elif defined(_WIN64)
|
2021-03-15 16:41:28 +00:00
|
|
|
if (endpointVolume != nullptr) {
|
2020-06-23 18:07:00 +00:00
|
|
|
endpointVolume->Release();
|
|
|
|
endpointVolume = nullptr;
|
|
|
|
CoUninitialize();
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int VolumeControl::getVolume() const
|
|
|
|
{
|
2020-06-23 18:07:00 +00:00
|
|
|
int volume = 0;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(__linux__)
|
2020-06-23 18:07:00 +00:00
|
|
|
if (mixerElem != nullptr) {
|
|
|
|
// Get volume range.
|
|
|
|
long minVolume;
|
|
|
|
long maxVolume;
|
|
|
|
if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) {
|
|
|
|
long rawVolume;
|
2021-07-07 18:03:42 +00:00
|
|
|
if (snd_mixer_selem_get_playback_volume(mixerElem, SND_MIXER_SCHN_MONO, &rawVolume) ==
|
|
|
|
0) {
|
2021-03-18 20:55:56 +00:00
|
|
|
// Bring into range 0-100.
|
2020-06-23 18:07:00 +00:00
|
|
|
rawVolume -= minVolume;
|
|
|
|
if (rawVolume > 0)
|
|
|
|
volume = (rawVolume * 100.0) / (maxVolume - minVolume) + 0.5;
|
|
|
|
}
|
|
|
|
else {
|
2021-03-18 20:55:56 +00:00
|
|
|
LOG(LogError) << "VolumeControl::getVolume(): Failed to get mixer volume";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-03-18 20:55:56 +00:00
|
|
|
LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#elif defined(_WIN64)
|
2021-03-15 16:41:28 +00:00
|
|
|
if (endpointVolume != nullptr) {
|
2021-03-18 20:55:56 +00:00
|
|
|
// Windows Vista or above, uses EndpointVolume API.
|
2020-06-23 18:07:00 +00:00
|
|
|
float floatVolume = 0.0f; // 0-1
|
|
|
|
if (endpointVolume->GetMasterVolumeLevelScalar(&floatVolume) == S_OK) {
|
2020-12-28 22:23:01 +00:00
|
|
|
volume = static_cast<int>(std::round(floatVolume * 100.0f));
|
2020-07-09 17:24:20 +00:00
|
|
|
LOG(LogInfo) << "System audio volume is " << volume;
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-01-12 22:43:46 +00:00
|
|
|
LOG(LogError) << "VolumeControl::getVolume(): Failed to get master volume!";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2021-08-17 18:55:29 +00:00
|
|
|
volume = glm::clamp(volume, 0, 100);
|
2020-06-23 18:07:00 +00:00
|
|
|
return volume;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VolumeControl::setVolume(int volume)
|
|
|
|
{
|
2021-08-17 18:55:29 +00:00
|
|
|
volume = glm::clamp(volume, 0, 100);
|
2020-06-23 18:07:00 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(__linux__)
|
2020-06-23 18:07:00 +00:00
|
|
|
if (mixerElem != nullptr) {
|
|
|
|
// Get volume range.
|
|
|
|
long minVolume;
|
|
|
|
long maxVolume;
|
|
|
|
if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) {
|
2021-03-18 20:55:56 +00:00
|
|
|
// Bring into minVolume-maxVolume range and set.
|
2020-06-23 18:07:00 +00:00
|
|
|
long rawVolume = (volume * (maxVolume - minVolume) / 100) + minVolume;
|
2021-07-07 18:03:42 +00:00
|
|
|
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) {
|
2021-03-18 20:55:56 +00:00
|
|
|
LOG(LogError) << "VolumeControl::getVolume(): Failed to set mixer volume";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-03-18 20:55:56 +00:00
|
|
|
LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range";
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#elif defined(_WIN64)
|
2021-03-15 16:41:28 +00:00
|
|
|
if (endpointVolume != nullptr) {
|
|
|
|
// Windows Vista or above, uses EndpointVolume API.
|
2020-06-23 18:07:00 +00:00
|
|
|
float floatVolume = 0.0f; // 0-1
|
|
|
|
if (volume > 0)
|
2020-09-16 20:14:35 +00:00
|
|
|
floatVolume = static_cast<float>(volume) / 100.0f;
|
2021-09-19 13:27:32 +00:00
|
|
|
if (endpointVolume->SetMasterVolumeLevelScalar(floatVolume, nullptr) != S_OK) {
|
2021-03-18 20:55:56 +00:00
|
|
|
LOG(LogError) << "VolumeControl::setVolume(): Failed to set master volume";
|
2021-09-19 13:27:32 +00:00
|
|
|
}
|
2020-06-23 18:07:00 +00:00
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2014-11-23 17:10:38 +00:00
|
|
|
}
|