Complete overhaul of VolumeControl with fixes for some related bugs.

This commit is contained in:
Leon Styhre 2021-03-18 21:55:56 +01:00
parent 3e9e592c3b
commit 87e6837980
5 changed files with 56 additions and 117 deletions

View file

@ -24,7 +24,6 @@
#include "Platform.h"
#include "Scripting.h"
#include "SystemData.h"
#include "VolumeControl.h"
#include "Window.h"
#include <assert.h>

View file

@ -21,9 +21,7 @@
// The ALSA Audio Card and Audio Device selection code is disabled at the moment.
// As PulseAudio controls the sound devices for the desktop environment, it doesn't
// make much sense to be able to select ALSA devices directly. Normally (always?)
// the selection doesn't make any difference at all. But maybe some PulseAudio
// settings could be added later on, if needed.
// make much sense to be able to select ALSA devices directly.
// The code is still active for Raspberry Pi though as I'm not sure if this is
// useful for that device.
// Keeping mixerName and mixerCard at their default values should make sure that
@ -37,82 +35,51 @@ std::string VolumeControl::mixerName = "Master";
std::string VolumeControl::mixerCard = "default";
#endif
std::weak_ptr<VolumeControl> VolumeControl::sInstance;
VolumeControl* VolumeControl::sInstance = nullptr;
VolumeControl::VolumeControl()
: originalVolume(0),
internalVolume(0)
#if defined(__APPLE__)
// #error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
, mixerIndex(0),
#if defined(__linux__)
: mixerIndex(0),
mixerHandle(nullptr),
mixerElem(nullptr),
mixerSelemId(nullptr)
#elif defined(_WIN64)
, mixerHandle(nullptr),
: mixerHandle(nullptr),
endpointVolume(nullptr)
#endif
{
init();
// Get original volume levels for system.
originalVolume = getVolume();
}
VolumeControl::VolumeControl(
const VolumeControl& right):
originalVolume(0),
internalVolume(0)
#if defined(__APPLE__)
// #error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
, mixerIndex(0),
mixerHandle(nullptr),
mixerElem(nullptr),
mixerSelemId(nullptr)
#elif defined(_WIN64)
, mixerHandle(nullptr),
endpointVolume(nullptr)
#endif
{
static_cast<void>(right);
sInstance = right.sInstance;
}
VolumeControl& VolumeControl::operator=(const VolumeControl& right)
{
if (this != &right)
sInstance = right.sInstance;
return *this;
}
VolumeControl::~VolumeControl()
{
// Set original volume levels for system.
//setVolume(originalVolume);
deinit();
#if defined(__linux__)
snd_config_update_free_global();
#endif
}
std::shared_ptr<VolumeControl>& VolumeControl::getInstance()
VolumeControl* VolumeControl::getInstance()
{
// Check if an VolumeControl instance is already created, if not then create it.
static std::shared_ptr<VolumeControl> sharedInstance = sInstance.lock();
if (sharedInstance == nullptr) {
sharedInstance.reset(new VolumeControl);
sInstance = sharedInstance;
// 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;
}
return sharedInstance;
}
void VolumeControl::init()
{
// Initialize audio mixer interface.
#if defined(__APPLE__)
// #error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
#if defined(__linux__)
// Try to open mixer device.
if (mixerHandle == nullptr) {
// Allow user to override the AudioCard and AudioDevice in es_settings.cfg.
@ -125,22 +92,17 @@ void VolumeControl::init()
// Sets simple-mixer index and name.
snd_mixer_selem_id_set_index(mixerSelemId, mixerIndex);
snd_mixer_selem_id_set_name(mixerSelemId, mixerName.c_str());
// Open mixer.
if (snd_mixer_open(&mixerHandle, 0) >= 0) {
LOG(LogDebug) << "VolumeControl::init(): Opened ALSA mixer";
// Ok, attach to defualt card.
if (snd_mixer_attach(mixerHandle, mixerCard.c_str()) >= 0) {
LOG(LogDebug) << "VolumeControl::init(): Attached to default card";
// Ok, register simple element class.
if (snd_mixer_selem_register(mixerHandle, nullptr, nullptr) >= 0) {
LOG(LogDebug) << "VolumeControl::init(): Registered simple element class";
// Ok, load registered elements.
if (snd_mixer_load(mixerHandle) >= 0) {
LOG(LogDebug) << "VolumeControl::init(): Loaded mixer elements";
// Ok, find elements now.
// Find elements.
mixerElem = snd_mixer_find_selem(mixerHandle, mixerSelemId);
if (mixerElem != nullptr) {
// Wohoo. good to go...
LOG(LogDebug) << "VolumeControl::init(): Mixer initialized";
}
else {
@ -213,14 +175,11 @@ void VolumeControl::init()
void VolumeControl::deinit()
{
// Deinitialize audio mixer interface.
#if defined(__APPLE__)
// #error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
#if defined(__linux__)
if (mixerHandle != nullptr) {
snd_mixer_detach(mixerHandle, mixerCard.c_str());
snd_mixer_free(mixerHandle);
snd_mixer_close(mixerHandle);
snd_config_update_free_global();
mixerHandle = nullptr;
mixerElem = nullptr;
}
@ -237,36 +196,31 @@ int VolumeControl::getVolume() const
{
int volume = 0;
#if defined(__APPLE__)
// #error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
#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) {
// Ok, now get volume.
long rawVolume;
if (snd_mixer_selem_get_playback_volume(mixerElem,
SND_MIXER_SCHN_MONO, &rawVolume) == 0) {
// Worked. bring into range 0-100.
// Bring into range 0-100.
rawVolume -= minVolume;
if (rawVolume > 0)
volume = (rawVolume * 100.0) / (maxVolume - minVolume) + 0.5;
//else
// volume = 0;
}
else {
LOG(LogError) << "VolumeControl::getVolume(): Failed to get mixer volume!";
LOG(LogError) << "VolumeControl::getVolume(): Failed to get mixer volume";
}
}
else {
LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range!";
LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range";
}
}
#elif defined(_WIN64)
if (endpointVolume != nullptr) {
// Windows Vista or above. uses EndpointVolume API.
// Windows Vista or above, uses EndpointVolume API.
float floatVolume = 0.0f; // 0-1
if (endpointVolume->GetMasterVolumeLevelScalar(&floatVolume) == S_OK) {
volume = static_cast<int>(std::round(floatVolume * 100.0f));
@ -278,38 +232,31 @@ int VolumeControl::getVolume() const
}
#endif
// Clamp to 0-100 range.
volume = Math::clamp(volume, 0, 100);
return volume;
}
void VolumeControl::setVolume(int volume)
{
// Clamp to 0-100 range.
volume = Math::clamp(volume, 0, 100);
// Store values in internal variables.
internalVolume = volume;
#if defined(__APPLE__)
// #error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
#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) {
// Ok, bring into minVolume-maxVolume range and set.
// 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!";
LOG(LogError) << "VolumeControl::getVolume(): Failed to set mixer volume";
}
}
else {
LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range!";
LOG(LogError) << "VolumeControl::getVolume(): Failed to get volume range";
}
}
#elif defined(_WIN64)
@ -319,7 +266,7 @@ void VolumeControl::setVolume(int volume)
if (volume > 0)
floatVolume = static_cast<float>(volume) / 100.0f;
if (endpointVolume->SetMasterVolumeLevelScalar(floatVolume, nullptr) != S_OK)
LOG(LogError) << "VolumeControl::setVolume(): Failed to set master volume!";
LOG(LogError) << "VolumeControl::setVolume(): Failed to set master volume";
}
#endif
}

View file

@ -11,9 +11,7 @@
#include <memory>
#if defined(__APPLE__)
//#error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
#if defined(__linux__)
#include <alsa/asoundlib.h>
#include <fcntl.h>
#include <unistd.h>
@ -21,15 +19,26 @@
#include <Windows.h>
#include <endpointvolume.h>
#include <mmdeviceapi.h>
#include <mmsystem.h>
#endif
// Singleton pattern. Call getInstance() to get an object.
class VolumeControl
{
#if defined(__APPLE__)
// #error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
public:
VolumeControl();
~VolumeControl();
static VolumeControl* getInstance();
void deleteInstance();
void init();
void deinit();
int getVolume() const;
void setVolume(int volume);
static VolumeControl* sInstance;
#if defined(__linux__)
static std::string mixerName;
static std::string mixerCard;
int mixerIndex;
@ -41,26 +50,6 @@ class VolumeControl
MIXERCONTROL mixerControl;
IAudioEndpointVolume * endpointVolume;
#endif
int originalVolume;
int internalVolume;
static std::weak_ptr<VolumeControl> sInstance;
VolumeControl();
VolumeControl(const VolumeControl & right);
VolumeControl & operator=(const VolumeControl & right);
public:
static std::shared_ptr<VolumeControl> & getInstance();
void init();
void deinit();
int getVolume() const;
void setVolume(int volume);
~VolumeControl();
};
#endif // ES_APP_VOLUME_CONTROL_H

View file

@ -513,6 +513,10 @@ void GuiMenu::openSoundSettings()
s->addSaveFunc([system_volume] {
VolumeControl::getInstance()->
setVolume(static_cast<int>(std::round(system_volume->getValue())));
// Explicitly delete the VolumeControl instance so that it will reinitialize the
// next time the menu is entered. This is the easiest way to detect new default
// audio devices or changes in audio volume done by the operating system.
VolumeControl::getInstance()->deleteInstance();
});
#endif

View file

@ -33,7 +33,7 @@ AudioManager::~AudioManager()
std::shared_ptr<AudioManager>& AudioManager::getInstance()
{
// Check if an AudioManager instance is already created, if not, create it.
// Check if an AudioManager instance is already created, and if not then create it.
if (sInstance == nullptr)
sInstance = std::shared_ptr<AudioManager>(new AudioManager);