From edc26aa4e1b70ba4d4273a9727c56128725c24d6 Mon Sep 17 00:00:00 2001 From: Bim Overbohm Date: Wed, 22 May 2013 19:11:10 +0200 Subject: [PATCH] Add Volume control in Windows and Linux Add volume control int Windows through the mixer API (until XP) and the EndpointVolume API (Vista and above). Add volume control in Linux through ALSA. Convert AudioManager to use shared_ptrs. --- CMakeLists.txt | 26 +++ src/AudioManager.cpp | 39 ++-- src/AudioManager.h | 7 +- src/Sound.cpp | 2 +- src/SystemData.cpp | 3 + src/VolumeControl.cpp | 357 ++++++++++++++++++++++++++++++++++++ src/VolumeControl.h | 56 ++++++ src/components/GuiTheme.cpp | 8 +- 8 files changed, 478 insertions(+), 20 deletions(-) create mode 100644 src/VolumeControl.cpp create mode 100644 src/VolumeControl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f67e4a728..1fc96327c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,11 @@ find_package(FreeImage REQUIRED) find_package(SDL REQUIRED) find_package(Boost REQUIRED COMPONENTS system filesystem) +#add ALSA for Linux +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + find_package(ALSA REQUIRED) +endif() + #------------------------------------------------------------------------------- #set up compiler flags and excutable names if(DEFINED BCMHOST) @@ -71,6 +76,13 @@ set(ES_INCLUDE_DIRS ${Boost_INCLUDE_DIRS} ) +#add ALSA for Linux +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + LIST(APPEND ES_INCLUDE_DIRS + ${ALSA_INCLUDE_DIRS} + ) +endif() + if(DEFINED BCMHOST) LIST(APPEND ES_INCLUDE_DIRS "/opt/vc/include" @@ -109,6 +121,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Window.h ${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiAnimation.h @@ -142,6 +155,7 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer_init.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiAnimation.cpp @@ -199,12 +213,24 @@ set(ES_LIBRARIES ${SDLMAIN_LIBRARY} ) +#add ALSA for Linux +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + LIST(APPEND ES_LIBRARIES + ${ALSA_LIBRARY} + ) +endif() + if(DEFINED BCMHOST) LIST(APPEND ES_LIBRARIES bcm_host EGL ) else() + if(MSVC) + LIST(APPEND ES_LIBRARIES + winmm + ) + endif() if(${GLSystem} MATCHES "Desktop OpenGL") LIST(APPEND ES_LIBRARIES ${OPENGL_LIBRARIES} diff --git a/src/AudioManager.cpp b/src/AudioManager.cpp index 2d68e5165..081670e6c 100644 --- a/src/AudioManager.cpp +++ b/src/AudioManager.cpp @@ -1,11 +1,12 @@ #include "AudioManager.h" #include "Log.h" +#include "VolumeControl.h" std::vector> AudioManager::sSoundVector; -std::shared_ptr AudioManager::sInstance; SDL_AudioSpec AudioManager::sAudioFormat; +std::shared_ptr AudioManager::sInstance; void AudioManager::mixAudio(void *unused, Uint8 *stream, int len) @@ -52,6 +53,9 @@ void AudioManager::mixAudio(void *unused, Uint8 *stream, int len) AudioManager::AudioManager() { init(); + + //set internal volume + //VolumeControl::getInstance()->setVolume(50); } AudioManager::~AudioManager() @@ -95,16 +99,9 @@ void AudioManager::init() void AudioManager::deinit() { - //stop playing all Sounds - for(unsigned int i = 0; i < sSoundVector.size(); i++) - { - if(sSoundVector.at(i)->isPlaying()) - { - sSoundVector[i]->stop(); - } - } - //pause audio and close SDL connection - SDL_PauseAudio(1); + //stop all playback + stop(); + SDL_CloseAudio(); } @@ -132,6 +129,24 @@ void AudioManager::unregisterSound(std::shared_ptr & sound) void AudioManager::play() { getInstance(); + + //set internal audio volume. important after launching a game and returning here + //VolumeControl::getInstance()->setVolume(50); + //unpause audio, the mixer will figure out if samples need to be played... SDL_PauseAudio(0); -} \ No newline at end of file +} + +void AudioManager::stop() +{ + //stop playing all Sounds + for(unsigned int i = 0; i < sSoundVector.size(); i++) + { + if(sSoundVector.at(i)->isPlaying()) + { + sSoundVector[i]->stop(); + } + } + //pause audio + SDL_PauseAudio(1); +} diff --git a/src/AudioManager.h b/src/AudioManager.h index 21a441cff..37b6d9f7b 100644 --- a/src/AudioManager.h +++ b/src/AudioManager.h @@ -25,10 +25,11 @@ public: void init(); void deinit(); - static void registerSound(std::shared_ptr & sound); - static void unregisterSound(std::shared_ptr & sound); + void registerSound(std::shared_ptr & sound); + void unregisterSound(std::shared_ptr & sound); - static void play(); + void play(); + void stop(); virtual ~AudioManager(); }; diff --git a/src/Sound.cpp b/src/Sound.cpp index ec1166b1b..e908a7716 100644 --- a/src/Sound.cpp +++ b/src/Sound.cpp @@ -95,7 +95,7 @@ void Sound::play() playing = true; } //tell the AudioManager to start playing samples - AudioManager::play(); + AudioManager::getInstance()->play(); } bool Sound::isPlaying() const diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 1707a59e1..9382cb73b 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -7,6 +7,7 @@ #include #include "Renderer.h" #include "AudioManager.h" +#include "VolumeControl.h" #include "Log.h" #include "InputManager.h" #include @@ -69,6 +70,7 @@ void SystemData::launchGame(Window* window, GameData* game) LOG(LogInfo) << "Attempting to launch game..."; AudioManager::getInstance()->deinit(); + VolumeControl::getInstance()->deinit(); window->deinit(); std::string command = mLaunchCommand; @@ -87,6 +89,7 @@ void SystemData::launchGame(Window* window, GameData* game) } window->init(); + VolumeControl::getInstance()->init(); AudioManager::getInstance()->init(); } diff --git a/src/VolumeControl.cpp b/src/VolumeControl.cpp new file mode 100644 index 000000000..711a97d3b --- /dev/null +++ b/src/VolumeControl.cpp @@ -0,0 +1,357 @@ +#include "VolumeControl.h" + +#include "Log.h" + + +#if defined(__linux__) + const char * VolumeControl::mixerName = "Master"; + const char * VolumeControl::mixerCard = "default"; +#endif + +std::shared_ptr 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 + getVolume(originalVolume); +} + +VolumeControl::~VolumeControl() +{ + //set original volume levels for system + setVolume(originalVolume); + + deinit(); +} + +std::shared_ptr & VolumeControl::getInstance() +{ + //check if an VolumeControl instance is already created, if not create one + if (sInstance == nullptr) { + sInstance = std::shared_ptr(new VolumeControl); + } + return sInstance; +} + +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) + { + 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) << "AudioManager::init() - Opened ALSA mixer"; + //ok. attach to defualt card + if (snd_mixer_attach(mixerHandle, mixerCard) >= 0) + { + LOG(LogDebug) << "AudioManager::init() - Attached to default card"; + //ok. register simple element class + if (snd_mixer_selem_register(mixerHandle, NULL, NULL) >= 0) + { + LOG(LogDebug) << "AudioManager::init() - Registered simple element class"; + //ok. load registered elements + if (snd_mixer_load(mixerHandle) >= 0) + { + LOG(LogDebug) << "AudioManager::init() - Loaded mixer elements"; + //ok. find elements now + mixerElem = snd_mixer_find_selem(mixerHandle, mixerSelemId); + if (mixerElem != nullptr) + { + //wohoo. good to go... + LOG(LogDebug) << "AudioManager::init() - Mixer initialized"; + } + else + { + LOG(LogError) << "AudioManager::init() - Failed to find mixer elements!"; + snd_mixer_close(mixerHandle); + mixerHandle = nullptr; + } + } + else + { + LOG(LogError) << "AudioManager::init() - Failed to load mixer elements!"; + snd_mixer_close(mixerHandle); + mixerHandle = nullptr; + } + } + else + { + LOG(LogError) << "AudioManager::init() - Failed to register simple element class!"; + snd_mixer_close(mixerHandle); + mixerHandle = nullptr; + } + } + else + { + LOG(LogError) << "AudioManager::init() - Failed to attach to default card!"; + snd_mixer_close(mixerHandle); + mixerHandle = nullptr; + } + } + else + { + LOG(LogError) << "AudioManager::init() - Failed to open ALSA mixer!"; + } + } +#elif defined(WIN32) || defined(_WIN32) + //get windows version information + OSVERSIONINFOEXA osVer = {sizeof(OSVERSIONINFO)}; + ::GetVersionExA(reinterpret_cast(&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) << "AudioManager::getVolume() - Failed to get mixer volume control!"; + mixerClose(mixerHandle); + mixerHandle = nullptr; + } + } + else + { + LOG(LogError) << "AudioManager::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) << "AudioManager::init() - Failed to get default audio endpoint volume!"; + } + //release default device. we don't need it anymore + defaultDevice->Release(); + } + else + { + LOG(LogError) << "AudioManager::init() - Failed to get default audio endpoint!"; + } + //release device enumerator. we don't need it anymore + deviceEnumerator->Release(); + } + else + { + LOG(LogError) << "AudioManager::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_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 +} + +void VolumeControl::getVolume(uint8_t & volume) +{ + 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); + //clamp to 0-100 range + if (volume > 100) + { + volume = 100; + } + } + //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) + { + volume = (uint8_t)((value.dwValue * 100) / 65535); + } + else + { + LOG(LogError) << "AudioManager::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) + { + volume = (uint8_t)(floatVolume * 100.0f); + } + else + { + LOG(LogError) << "AudioManager::setVolume() - Failed to get master volume!"; + } + + } +#endif +} + +void VolumeControl::setVolume(uint8_t volume) +{ + //clamp to 0-100 range + 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) << "AudioManager::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) << "AudioManager::setVolume() - Failed to set master volume!"; + } + } +#endif +} \ No newline at end of file diff --git a/src/VolumeControl.h b/src/VolumeControl.h new file mode 100644 index 000000000..08b9ac7d0 --- /dev/null +++ b/src/VolumeControl.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#if defined (__APPLE__) + #error TODO: Not implemented for MacOS yet!!! +#elif defined(__linux__) + #include + #include + #include +#elif defined(WIN32) || defined(_WIN32) + #include + #include + #include + #include +#endif + +/*! +Singleton pattern. Call getInstance() to get an object. +*/ +class VolumeControl +{ +#if defined (__APPLE__) + #error TODO: Not implemented for MacOS yet!!! +#elif defined(__linux__) + static const char * mixerName; + static const char * mixerCard; + int mixerIndex; + snd_mixer_t* mixerHandle; + snd_mixer_elem_t* mixerElem; + snd_mixer_selem_id_t* mixerSelemId; +#elif defined(WIN32) || defined(_WIN32) + HMIXER mixerHandle; + MIXERCONTROL mixerControl; + IAudioEndpointVolume * endpointVolume; +#endif + + uint8_t originalVolume; + uint8_t internalVolume; + + static std::shared_ptr sInstance; + + VolumeControl(); + +public: + static std::shared_ptr & getInstance(); + + void init(); + void deinit(); + + void getVolume(uint8_t & volume); + void setVolume(uint8_t volume); + + virtual ~VolumeControl(); +}; diff --git a/src/components/GuiTheme.cpp b/src/components/GuiTheme.cpp index 6bc27a07a..71f9765b5 100644 --- a/src/components/GuiTheme.cpp +++ b/src/components/GuiTheme.cpp @@ -69,10 +69,10 @@ GuiTheme::GuiTheme(Window* window, bool detailed, std::string path) : Gui(window mSoundMap["menuOpen"] = std::shared_ptr(new Sound); //register all sound with the audiomanager - AudioManager::registerSound(mSoundMap["menuScroll"]); - AudioManager::registerSound(mSoundMap["menuSelect"]); - AudioManager::registerSound(mSoundMap["menuBack"]); - AudioManager::registerSound(mSoundMap["menuOpen"]); + AudioManager::getInstance()->registerSound(mSoundMap["menuScroll"]); + AudioManager::getInstance()->registerSound(mSoundMap["menuSelect"]); + AudioManager::getInstance()->registerSound(mSoundMap["menuBack"]); + AudioManager::getInstance()->registerSound(mSoundMap["menuOpen"]); mListFont = NULL; mDescFont = NULL;