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;