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.
This commit is contained in:
Bim Overbohm 2013-05-22 19:11:10 +02:00
parent a1cb5bdda1
commit edc26aa4e1
8 changed files with 478 additions and 20 deletions

View file

@ -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}

View file

@ -1,11 +1,12 @@
#include "AudioManager.h"
#include "Log.h"
#include "VolumeControl.h"
std::vector<std::shared_ptr<Sound>> AudioManager::sSoundVector;
std::shared_ptr<AudioManager> AudioManager::sInstance;
SDL_AudioSpec AudioManager::sAudioFormat;
std::shared_ptr<AudioManager> 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> & 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);
}
}
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);
}

View file

@ -25,10 +25,11 @@ public:
void init();
void deinit();
static void registerSound(std::shared_ptr<Sound> & sound);
static void unregisterSound(std::shared_ptr<Sound> & sound);
void registerSound(std::shared_ptr<Sound> & sound);
void unregisterSound(std::shared_ptr<Sound> & sound);
static void play();
void play();
void stop();
virtual ~AudioManager();
};

View file

@ -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

View file

@ -7,6 +7,7 @@
#include <SDL_joystick.h>
#include "Renderer.h"
#include "AudioManager.h"
#include "VolumeControl.h"
#include "Log.h"
#include "InputManager.h"
#include <iostream>
@ -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();
}

357
src/VolumeControl.cpp Normal file
View file

@ -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> 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> & VolumeControl::getInstance()
{
//check if an VolumeControl instance is already created, if not create one
if (sInstance == nullptr) {
sInstance = std::shared_ptr<VolumeControl>(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<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) << "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
}

56
src/VolumeControl.h Normal file
View file

@ -0,0 +1,56 @@
#pragma once
#include <memory>
#include <stdint.h>
#if defined (__APPLE__)
#error TODO: Not implemented for MacOS yet!!!
#elif defined(__linux__)
#include <unistd.h>
#include <fcntl.h>
#include <alsa/asoundlib.h>
#elif defined(WIN32) || defined(_WIN32)
#include <Windows.h>
#include <MMSystem.h>
#include <mmdeviceapi.h>
#include <endpointvolume.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__)
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<VolumeControl> sInstance;
VolumeControl();
public:
static std::shared_ptr<VolumeControl> & getInstance();
void init();
void deinit();
void getVolume(uint8_t & volume);
void setVolume(uint8_t volume);
virtual ~VolumeControl();
};

View file

@ -69,10 +69,10 @@ GuiTheme::GuiTheme(Window* window, bool detailed, std::string path) : Gui(window
mSoundMap["menuOpen"] = std::shared_ptr<Sound>(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;