Skip to content

Instantly share code, notes, and snippets.

@Raincode
Last active April 10, 2019 13:25
Show Gist options
  • Save Raincode/d248acc5069bfabc7382f6c1ef238519 to your computer and use it in GitHub Desktop.
Save Raincode/d248acc5069bfabc7382f6c1ef238519 to your computer and use it in GitHub Desktop.
Windows WASAPI Managing active audio sessions example
// 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