Last active
April 10, 2019 13:25
-
-
Save Raincode/d248acc5069bfabc7382f6c1ef238519 to your computer and use it in GitHub Desktop.
Windows WASAPI Managing active audio sessions example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WASAPI Example to set application volumes | |
// 10.04.2019 | |
#include <iostream> | |
#include <limits> | |
#include <string> | |
#include <vector> | |
#define WIN32_LEAN_AND_MEAN | |
#define NOMINMAX | |
#include <Windows.h> | |
#include <Objbase.h> | |
#include <audiopolicy.h> | |
#include <mmdeviceapi.h> | |
// Definition taken from https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/dataaccess/oledb/prsample/prsample.h | |
#define CHECK_HR(hr) \ | |
if(FAILED(hr)) \ | |
goto CLEANUP | |
// instead of SAFE_RELEASE Makro | |
// https://docs.microsoft.com/en-us/windows/desktop/medfound/saferelease | |
// release and zero out a possible NULL pointer. note this will | |
// do the release on a temp copy to avoid reentrancy issues that can result from | |
// callbacks durring the release | |
template <class T> void SafeRelease( __deref_inout_opt T **ppT ) | |
{ | |
T *pTTemp = *ppT; // temp copy | |
*ppT = nullptr; // zero the input | |
if (pTTemp) | |
{ | |
pTTemp->Release(); | |
} | |
} | |
// simple compile time string hash (without std::string_view) | |
constexpr std::uint32_t fnv32(const char* str) { | |
std::uint32_t hash{2166136261u}; | |
for (; *str; ++str) { | |
hash = 16777619u * (hash ^ *str); | |
} | |
return hash; | |
} | |
std::uint32_t fnv32(const std::string& str) { | |
std::uint32_t hash{2166136261u}; | |
for (char ch : str) { | |
hash = 16777619u * (hash ^ ch); | |
} | |
return hash; | |
} | |
// https://docs.microsoft.com/en-us/windows/desktop/api/audiopolicy/nn-audiopolicy-iaudiosessionmanager2 | |
HRESULT CreateSessionManager(IAudioSessionManager2** ppSessionManager) { | |
HRESULT hr = S_OK; | |
// IMMDevice is used to represent an audio endpoint device. | |
IMMDevice* pDevice{}; | |
IMMDeviceEnumerator* pEnumerator{}; | |
IAudioSessionManager2* pSessionManager{}; | |
// Create the device enumerator. | |
CHECK_HR( hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator)); | |
// Get the default audio device. | |
CHECK_HR(hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice)); | |
// Get the session manager. | |
CHECK_HR( hr = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&pSessionManager)); | |
// Return the pointer to the caller. | |
*(ppSessionManager) = pSessionManager; | |
(*ppSessionManager)->AddRef(); | |
CLEANUP: | |
SafeRelease(&pSessionManager); | |
SafeRelease(&pEnumerator); | |
SafeRelease(&pDevice); | |
return hr; | |
} | |
int error(const char* msg, int code = 1) { | |
std::cerr << msg << '\n'; | |
return code; | |
} | |
int main() { | |
/* https://docs.microsoft.com/en-us/windows/desktop/api/audiopolicy/nn-audiopolicy-iaudiosessionmanager2 | |
The application thread that uses this interface must be initialized for COM. | |
For more information about COM initialization, see the description of the CoInitializeEx function in the Windows SDK documentation. | |
*/ | |
CoInitializeEx(NULL, COINIT_MULTITHREADED); | |
std::cout << "\n:: Retrieving interface to default audio endpoint...\n"; | |
IAudioSessionManager2* sessionManager{}; | |
if (CreateSessionManager(&sessionManager) != S_OK) | |
return error("Failed to create session manager"); | |
std::cout << "\n:: Retrieving audio session enumerator from session manager...\n"; | |
IAudioSessionEnumerator* sessionEnumerator{}; | |
if (sessionManager->GetSessionEnumerator(&sessionEnumerator) != S_OK) | |
return error("Failed to get session enumerator"); | |
// Get the number of active sessions | |
// I think the first slider in sndvol (speaker) represents the global volume | |
// After that, audio sessions with the same display name are merged (e. g. Firefox might give multiple | |
// audio sessions, but sndvol only shows one. I'm guessing adjusting the single slider changes all active sessions | |
// of Firefox). | |
// "@%SystemRoot%\System32\AudioSrv.Dll,-202" is probably "Systemsounds" in sndvol | |
std::cout << "\n:: Querying the number of active sessions...\n"; | |
int sessionCount{}; | |
if (sessionEnumerator->GetCount(&sessionCount) != S_OK) | |
return error("Failed to get session count"); | |
std::cout << "Number of active audio sessions: " << sessionCount << '\n'; | |
std::cout << "\n:: Querying the inidividual sessions...\n"; | |
// alternatively use array with a maxSize or create a list | |
std::vector<IAudioSessionControl*> sessionControls; | |
for (int i = 0; i < sessionCount; ++i) { | |
IAudioSessionControl* session{}; | |
if (sessionEnumerator->GetSession(i, &session) == S_OK) { | |
sessionControls.push_back(session); | |
LPWSTR displayName{}; | |
if (session->GetDisplayName(&displayName) == S_OK) { | |
std::wcout << L" " << displayName << '\n'; | |
CoTaskMemFree(displayName); | |
} | |
} | |
else { | |
std::cerr << "Failed to get session with index: " << i << '\n'; | |
} | |
} | |
std::cout << "Total number of session controls retrieved: " << sessionControls.size() << '\n'; | |
// From https://stackoverflow.com/questions/6077526/how-is-sndvol-able-to-change-the-volume-level-of-a-given-audio-session?rq=1 | |
// and https://stackoverflow.com/questions/31821754/what-is-the-purpose-of-using-the-queryinterface-method-direct3d | |
std::cout << "\n:: Retrieving ISimpleAudioVolumes from the active sessions...\n"; | |
std::vector<ISimpleAudioVolume*> sessionVolumes; | |
for (auto control : sessionControls) { | |
ISimpleAudioVolume* volume{}; | |
if (control->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&volume) == S_OK) | |
sessionVolumes.push_back(volume); | |
else | |
std::cerr << "Failed to get session volume\n"; | |
} | |
std::cout << "Total number of session volumes retrieved: " << sessionVolumes.size() << '\n'; | |
std::cout << "\nCommand Loop ('exit', 'quit', 'mute', 'unmute', 'showvol', 'setvol <0..1>'):\n"; | |
std::cout << " (verify by checking sndvol in windows)\n"; | |
for (std::string command; std::cout << "> " && std::cin >> command; ) { | |
switch (fnv32(command)) { | |
case fnv32("quit"): | |
case fnv32("exit"): | |
return 0; | |
case fnv32("mute"): | |
for (auto vol : sessionVolumes) { | |
vol->SetMute(TRUE, NULL); | |
} | |
break; | |
case fnv32("unmute"): | |
for (auto vol : sessionVolumes) { | |
vol->SetMute(FALSE, NULL); | |
} | |
break; | |
case fnv32("setvol"): { | |
float volume{}; | |
if (std::cin >> volume && volume >= 0.f && volume <= 1.f) { | |
for (auto vol : sessionVolumes) { | |
vol->SetMasterVolume(volume, NULL); | |
} | |
} | |
break; | |
} | |
case fnv32("showvol"): | |
for (auto control : sessionControls) { | |
ISimpleAudioVolume* vol{}; | |
if (control->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&vol) == S_OK) { | |
float volume{}; | |
vol->GetMasterVolume(&volume); | |
LPWSTR displayName{}; | |
control->GetDisplayName(&displayName); | |
std::wcout << displayName << ": " << 100 * volume << L"%\n"; | |
CoTaskMemFree(displayName); | |
} | |
} | |
break; | |
default: | |
std::cerr << "Unknown command '" << command << "'\n"; | |
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); | |
break; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment