2014-06-25 16:29:58 +00:00
|
|
|
#include "VolumeControl.h"
|
2017-06-02 15:58:44 +00:00
|
|
|
#include "Settings.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
#include "Log.h"
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(__linux__)
|
|
|
|
#ifdef _RPI_
|
|
|
|
const char * VolumeControl::mixerName = "PCM";
|
|
|
|
#else
|
|
|
|
const char * VolumeControl::mixerName = "Master";
|
|
|
|
#endif
|
|
|
|
const char * VolumeControl::mixerCard = "default";
|
|
|
|
#endif
|
|
|
|
|
|
|
|
std::weak_ptr<VolumeControl> VolumeControl::sInstance;
|
|
|
|
|
|
|
|
|
|
|
|
VolumeControl::VolumeControl()
|
|
|
|
: 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(WIN32) || defined(_WIN32)
|
|
|
|
, mixerHandle(nullptr), endpointVolume(nullptr)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
init();
|
|
|
|
|
|
|
|
//get original volume levels for system
|
|
|
|
originalVolume = getVolume();
|
|
|
|
}
|
|
|
|
|
|
|
|
VolumeControl::VolumeControl(const VolumeControl & 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
|
2014-11-23 17:10:38 +00:00
|
|
|
//setVolume(originalVolume);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
deinit();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<VolumeControl> & VolumeControl::getInstance()
|
|
|
|
{
|
|
|
|
//check if an VolumeControl instance is already created, if not create one
|
|
|
|
static std::shared_ptr<VolumeControl> sharedInstance = sInstance.lock();
|
|
|
|
if (sharedInstance == nullptr) {
|
|
|
|
sharedInstance.reset(new VolumeControl);
|
|
|
|
sInstance = sharedInstance;
|
|
|
|
}
|
|
|
|
return sharedInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VolumeControl::init()
|
|
|
|
{
|
|
|
|
//initialize audio mixer interface
|
|
|
|
#if defined (__APPLE__)
|
|
|
|
#error TODO: Not implemented for MacOS yet!!!
|
|
|
|
#elif defined(__linux__)
|
|
|
|
//try to open mixer device
|
|
|
|
if (mixerHandle == nullptr)
|
|
|
|
{
|
2017-06-02 15:58:44 +00:00
|
|
|
#ifdef _RPI_
|
|
|
|
mixerName = Settings::getInstance()->getString("AudioDevice").c_str();
|
|
|
|
#endif
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
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);
|
|
|
|
//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) >= 0)
|
|
|
|
{
|
|
|
|
LOG(LogDebug) << "VolumeControl::init() - Attached to default card";
|
|
|
|
//ok. register simple element class
|
|
|
|
if (snd_mixer_selem_register(mixerHandle, NULL, NULL) >= 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
|
|
|
|
mixerElem = snd_mixer_find_selem(mixerHandle, mixerSelemId);
|
|
|
|
if (mixerElem != nullptr)
|
|
|
|
{
|
|
|
|
//wohoo. good to go...
|
|
|
|
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(WIN32) || defined(_WIN32)
|
|
|
|
//get windows version information
|
|
|
|
OSVERSIONINFOEXA osVer = {sizeof(OSVERSIONINFO)};
|
|
|
|
::GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&osVer));
|
|
|
|
//check windows version
|
|
|
|
if(osVer.dwMajorVersion < 6)
|
|
|
|
{
|
|
|
|
//Windows older than Vista. use mixer API. open default mixer
|
|
|
|
if (mixerHandle == nullptr)
|
|
|
|
{
|
|
|
|
if (mixerOpen(&mixerHandle, 0, NULL, 0, 0) == MMSYSERR_NOERROR)
|
|
|
|
{
|
|
|
|
//retrieve info on the volume slider control for the "Speaker Out" line
|
|
|
|
MIXERLINECONTROLS mixerLineControls;
|
|
|
|
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
|
|
|
|
mixerLineControls.dwLineID = 0xFFFF0000; //Id of "Speaker Out" line
|
|
|
|
mixerLineControls.cControls = 1;
|
|
|
|
//mixerLineControls.dwControlID = 0x00000000; //Id of "Speaker Out" line's volume slider
|
|
|
|
mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; //Get volume control
|
|
|
|
mixerLineControls.pamxctrl = &mixerControl;
|
|
|
|
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
|
|
|
|
if (mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::getVolume() - Failed to get mixer volume control!";
|
|
|
|
mixerClose(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::init() - Failed to open mixer!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Windows Vista or above. use EndpointVolume API. get device enumerator
|
|
|
|
if (endpointVolume == nullptr)
|
|
|
|
{
|
|
|
|
CoInitialize(nullptr);
|
|
|
|
IMMDeviceEnumerator * deviceEnumerator = nullptr;
|
|
|
|
CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&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, (LPVOID *)&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 (__APPLE__)
|
|
|
|
#error TODO: Not implemented for MacOS yet!!!
|
|
|
|
#elif defined(__linux__)
|
|
|
|
if (mixerHandle != nullptr) {
|
|
|
|
snd_mixer_detach(mixerHandle, mixerCard);
|
|
|
|
snd_mixer_free(mixerHandle);
|
|
|
|
snd_mixer_close(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
mixerElem = nullptr;
|
|
|
|
}
|
|
|
|
#elif defined(WIN32) || defined(_WIN32)
|
|
|
|
if (mixerHandle != nullptr) {
|
|
|
|
mixerClose(mixerHandle);
|
|
|
|
mixerHandle = nullptr;
|
|
|
|
}
|
|
|
|
else if (endpointVolume != nullptr) {
|
|
|
|
endpointVolume->Release();
|
|
|
|
endpointVolume = nullptr;
|
|
|
|
CoUninitialize();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
int VolumeControl::getVolume() const
|
|
|
|
{
|
|
|
|
int volume = 0;
|
|
|
|
|
|
|
|
#if defined (__APPLE__)
|
|
|
|
#error TODO: Not implemented for MacOS yet!!!
|
|
|
|
#elif 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
|
|
|
|
rawVolume -= minVolume;
|
|
|
|
if (rawVolume > 0)
|
|
|
|
{
|
|
|
|
volume = (rawVolume * 100) / (maxVolume - minVolume);
|
|
|
|
}
|
|
|
|
//else volume = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::getVolume() - Failed to get mixer volume!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::getVolume() - Failed to get volume range!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#elif defined(WIN32) || defined(_WIN32)
|
|
|
|
if (mixerHandle != nullptr)
|
|
|
|
{
|
|
|
|
//Windows older than Vista. use mixer API. get volume from line control
|
|
|
|
MIXERCONTROLDETAILS_UNSIGNED value;
|
|
|
|
MIXERCONTROLDETAILS mixerControlDetails;
|
|
|
|
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
|
|
|
|
mixerControlDetails.dwControlID = mixerControl.dwControlID;
|
|
|
|
mixerControlDetails.cChannels = 1; //always 1 for a MIXERCONTROL_CONTROLF_UNIFORM control
|
|
|
|
mixerControlDetails.cMultipleItems = 0; //always 0 except for a MIXERCONTROL_CONTROLF_MULTIPLE control
|
|
|
|
mixerControlDetails.paDetails = &value;
|
|
|
|
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
|
|
|
|
if (mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_GETCONTROLDETAILSF_VALUE) == MMSYSERR_NOERROR)
|
|
|
|
{
|
2014-07-09 21:17:59 +00:00
|
|
|
volume = (uint8_t)round((value.dwValue * 100) / 65535);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::getVolume() - Failed to get mixer volume!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (endpointVolume != nullptr)
|
|
|
|
{
|
|
|
|
//Windows Vista or above. use EndpointVolume API
|
|
|
|
float floatVolume = 0.0f; //0-1
|
|
|
|
if (endpointVolume->GetMasterVolumeLevelScalar(&floatVolume) == S_OK)
|
|
|
|
{
|
2014-07-09 21:17:59 +00:00
|
|
|
volume = (uint8_t)round(floatVolume * 100.0f);
|
|
|
|
LOG(LogInfo) << " getting volume as " << volume << " ( from float " << floatVolume << ")";
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::getVolume() - Failed to get master volume!";
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
//clamp to 0-100 range
|
|
|
|
if (volume < 0)
|
|
|
|
{
|
|
|
|
volume = 0;
|
|
|
|
}
|
|
|
|
if (volume > 100)
|
|
|
|
{
|
|
|
|
volume = 100;
|
|
|
|
}
|
|
|
|
return volume;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VolumeControl::setVolume(int volume)
|
|
|
|
{
|
|
|
|
//clamp to 0-100 range
|
|
|
|
if (volume < 0)
|
|
|
|
{
|
|
|
|
volume = 0;
|
|
|
|
}
|
|
|
|
if (volume > 100)
|
|
|
|
{
|
|
|
|
volume = 100;
|
|
|
|
}
|
|
|
|
//store values in internal variables
|
|
|
|
internalVolume = volume;
|
|
|
|
#if defined (__APPLE__)
|
|
|
|
#error TODO: Not implemented for MacOS yet!!!
|
|
|
|
#elif 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
|
|
|
|
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(WIN32) || defined(_WIN32)
|
|
|
|
if (mixerHandle != nullptr)
|
|
|
|
{
|
|
|
|
//Windows older than Vista. use mixer API. get volume from line control
|
|
|
|
MIXERCONTROLDETAILS_UNSIGNED value;
|
|
|
|
value.dwValue = (volume * 65535) / 100;
|
|
|
|
MIXERCONTROLDETAILS mixerControlDetails;
|
|
|
|
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
|
|
|
|
mixerControlDetails.dwControlID = mixerControl.dwControlID;
|
|
|
|
mixerControlDetails.cChannels = 1; //always 1 for a MIXERCONTROL_CONTROLF_UNIFORM control
|
|
|
|
mixerControlDetails.cMultipleItems = 0; //always 0 except for a MIXERCONTROL_CONTROLF_MULTIPLE control
|
|
|
|
mixerControlDetails.paDetails = &value;
|
|
|
|
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
|
|
|
|
if (mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::setVolume() - Failed to set mixer volume!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (endpointVolume != nullptr)
|
|
|
|
{
|
|
|
|
//Windows Vista or above. use EndpointVolume API
|
|
|
|
float floatVolume = 0.0f; //0-1
|
|
|
|
if (volume > 0) {
|
|
|
|
floatVolume = (float)volume / 100.0f;
|
|
|
|
}
|
|
|
|
if (endpointVolume->SetMasterVolumeLevelScalar(floatVolume, nullptr) != S_OK)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "VolumeControl::setVolume() - Failed to set master volume!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2014-11-23 17:10:38 +00:00
|
|
|
}
|