### WinMain: Register IAudioEndpointVolumeCallback and open volume dialog Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/endpoint-volume-controls WinMain initializes COM, creates a GUID for the volume context, gets the default rendering endpoint, activates IAudioEndpointVolume, and registers an IAudioEndpointVolumeCallback to monitor volume changes. It then shows a dialog box and cleans up by unregistering the callback and releasing interfaces. Requires Windows Vista or later and the CAudioEndpointVolumeCallback class. ```cpp //----------------------------------------------------------- // WinMain -- Registers an IAudioEndpointVolumeCallback // interface to monitor endpoint volume level, and opens // a dialog box that displays a volume control that will // mirror the endpoint volume control in SndVol. //----------------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HRESULT hr = S_OK; IMMDeviceEnumerator *pEnumerator = NULL; IMMDevice *pDevice = NULL; CAudioEndpointVolumeCallback EPVolEvents; if (hPrevInstance) { return 0; } CoInitialize(NULL); hr = CoCreateGuid(&g_guidMyContext); EXIT_ON_ERROR(hr) // Get enumerator for audio endpoint devices. hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator); EXIT_ON_ERROR(hr) // Get default audio-rendering device. hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice); EXIT_ON_ERROR(hr) hr = pDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&g_pEndptVol); EXIT_ON_ERROR(hr) hr = g_pEndptVol->RegisterControlChangeNotify( (IAudioEndpointVolumeCallback*)&EPVolEvents); EXIT_ON_ERROR(hr) InitCommonControls(); DialogBox(hInstance, L"VOLUMECONTROL", NULL, (DLGPROC)DlgProc); Exit: if (FAILED(hr)) { MessageBox(NULL, TEXT("This program requires Windows Vista."), TEXT("Error termination"), MB_OK); } if (g_pEndptVol != NULL) { g_pEndptVol->UnregisterControlChangeNotify( (IAudioEndpointVolumeCallback*)&EPVolEvents); } SAFE_RELEASE(pEnumerator) SAFE_RELEASE(pDevice) SAFE_RELEASE(g_pEndptVol) CoUninitialize(); return 0; } ``` ```cpp //----------------------------------------------------------- // DlgProc -- Dialog box procedure //----------------------------------------------------------- BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { HRESULT hr; BOOL bMute; float fVolume; int nVolume; int nChecked; switch (message) { case WM_INITDIALOG: g_hDlg = hDlg; SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_SETRANGEMIN, FALSE, 0); SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_SETRANGEMAX, FALSE, MAX_VOL); hr = g_pEndptVol->GetMute(&bMute); ERROR_CANCEL(hr) SendDlgItemMessage(hDlg, IDC_CHECK_MUTE, BM_SETCHECK, bMute ? BST_CHECKED : BST_UNCHECKED, 0); hr = g_pEndptVol->GetMasterVolumeLevelScalar(&fVolume); ERROR_CANCEL(hr) nVolume = (int)(MAX_VOL*fVolume + 0.5); SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_SETPOS, TRUE, nVolume); return TRUE; case WM_HSCROLL: switch (LOWORD(wParam)) { case SB_THUMBPOSITION: case SB_THUMBTRACK: case SB_LINERIGHT: case SB_LINELEFT: case SB_PAGERIGHT: case SB_PAGELEFT: case SB_RIGHT: case SB_LEFT: // The user moved the volume slider in the dialog box. SendDlgItemMessage(hDlg, IDC_CHECK_MUTE, BM_SETCHECK, BST_UNCHECKED, 0); hr = g_pEndptVol->SetMute(FALSE, &g_guidMyContext); ERROR_CANCEL(hr) nVolume = SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_GETPOS, 0, 0); fVolume = (float)nVolume/MAX_VOL; hr = g_pEndptVol->SetMasterVolumeLevelScalar(fVolume, &g_guidMyContext); ERROR_CANCEL(hr) return TRUE; } break; case WM_COMMAND: switch ((int)LOWORD(wParam)) { case IDC_CHECK_MUTE: // The user selected the Mute check box in the dialog box. nChecked = SendDlgItemMessage(hDlg, IDC_CHECK_MUTE, BM_GETCHECK, 0, 0); bMute = (BST_CHECKED == nChecked); hr = g_pEndptVol->SetMute(bMute, &g_guidMyContext); ERROR_CANCEL(hr) return TRUE; case IDCANCEL: EndDialog(hDlg, TRUE); return TRUE; } break; } return FALSE; } ``` -------------------------------- ### Start and render dynamic spatial audio objects Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/render-spatial-sound-using-spatial-audio-objects Starts a spatial audio stream and repeatedly updates dynamic audio objects. It activates a new dynamic object every 200 iterations, writes audio data to the buffers, updates position and volume, and removes objects once they reach their lifetime. ```cpp ``` // Start streaming / rendering hr = spatialAudioStream->Start(); do { // Wait for a signal from the audio-engine to start the next processing pass if (WaitForSingleObject(bufferCompletionEvent, 100) != WAIT_OBJECT_0) { break; } UINT32 availableDynamicObjectCount; UINT32 frameCount; // Begin the process of sending object data and metadata // Get the number of active objects that can be used to send object-data // Get the frame count that each buffer will be filled with hr = spatialAudioStream->BeginUpdatingAudioObjects(&availableDynamicObjectCount, &frameCount); BYTE* buffer; UINT32 bufferLength; // Spawn a new dynamic audio object every 200 iterations if (spawnCounter % 200 == 0 && spawnCounter < 1000) { // Activate a new dynamic audio object Microsoft::WRL::ComPtr audioObject; hr = spatialAudioStream->ActivateSpatialAudioObject(AudioObjectType::AudioObjectType_Dynamic, &audioObject); // If SPTLAUDCLNT_E_NO_MORE_OBJECTS is returned, there are no more available objects if (SUCCEEDED(hr)) { // Init new struct with the new audio object. My3dObject obj = { audioObject, Windows::Foundation::Numerics::float3(static_cast(pos_dist(gen)), static_cast(pos_dist(gen)), static_cast(pos_dist(gen))), Windows::Foundation::Numerics::float3(static_cast(vel_dist(gen)), static_cast(vel_dist(gen)), static_cast(vel_dist(gen))), static_cast(static_cast(vol_dist(gen))), static_cast(static_cast(pitch_dist(gen))), format.nSamplesPerSec * 5 // 5 seconds of audio samples }; objectVector.insert(objectVector.begin(), obj); } } spawnCounter++; // Loop through all dynamic audio objects std::vector::iterator it = objectVector.begin(); while (it != objectVector.end()) { it->audioObject->GetBuffer(&buffer, &bufferLength); if (it->totalFrameCount >= frameCount) { // Write audio data to the buffer WriteToAudioObjectBuffer(reinterpret_cast(buffer), frameCount, it->frequency, format.nSamplesPerSec); // Update the position and volume of the audio object it->audioObject->SetPosition(it->position.x, it->position.y, it->position.z); it->position += it->velocity; it->audioObject->SetVolume(it->volume); it->totalFrameCount -= frameCount; ++it; } else { // If the audio object reaches its lifetime, set EndOfStream and release the object // Write audio data to the buffer WriteToAudioObjectBuffer(reinterpret_cast(buffer), it->totalFrameCount, it->frequency, format.nSamplesPerSec); // Set end of stream for the last buffer hr = it->audioObject->SetEndOfStream(it->totalFrameCount); it->audioObject = nullptr; // Release the object it->totalFrameCount = 0; it = objectVector.erase(it); } } // Let the audio-engine know that the object data are available for processing now hr = spatialAudioStream->EndUpdatingAudioObjects(); } while (objectVector.size() > 0); ``` ``` ```cpp ``` // Stop the stream hr = spatialAudioStream->Stop(); // We don't want to start again, so reset the stream to free it's resources. hr = spatialAudioStream->Reset(); CloseHandle(bufferCompletionEvent); ``` ``` -------------------------------- ### wmain: Set up DirectSound and apply OTA policy Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/protected-user-mode-audio--puma- Main function that initializes COM, gets a digital audio endpoint, activates IDirectSound8, creates a sound buffer with a WAVEFORMATEXTENSIBLE format (PCM or AC3), plays it, sets an output trust authority policy using MAKE_MFPROTECTIONDATA_TRUSTEDAUDIODRIVERS2, and then stops and restarts the buffer after clearing the policy. Includes error handling and cleanup. ```cpp //------------------------------------------------------------------- int __cdecl wmain(int argc, char* argv[]) { IMMDevice *pEndpoint=NULL; HRESULT hr = S_OK; // DSound related variables IDirectSound8* DSSound8 = NULL; IDirectSoundBuffer* DSBuffer = NULL; DSBUFFERDESC DSBufferDesc; WAVEFORMATEXTENSIBLE wfext; WORD nChannels = 2; DWORD nSamplesPerSec = 48000; WORD wBitsPerSample = 16; // OTA related variables IMFTrustedOutput *pMFTrustedOutput=NULL; IMFOutputPolicy *pMFOutputPolicy=NULL; IMFOutputTrustAuthority *pMFOutputTrustAuthority=NULL; DWORD dwConfigData=0; // Initialize COM hr = CoInitialize(NULL); IF_FAILED_JUMP(hr, Exit); printf("OTA test app for DSound\n"); hr = GetDigitalAudioEndpoint(&pEndpoint); IF_FAILED_JUMP(hr, Exit); if (pEndpoint) { printf("Found digital audio endpoint.\n"); } // // Active DSound interface // hr = pEndpoint->Activate(IID_IDirectSound8, CLSCTX_INPROC_SERVER, NULL, reinterpret_cast(&DSSound8)); IF_FAILED_JUMP(hr, Exit); nChannels = 2; nSamplesPerSec = 48000; wBitsPerSample = 16; ZeroMemory(&wfext, sizeof(wfext)); wfext.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfext.Format.nChannels = nChannels; wfext.Format.nSamplesPerSec = nSamplesPerSec; wfext.Format.wBitsPerSample = wBitsPerSample; wfext.Format.nBlockAlign = (nChannels * wBitsPerSample) / 8; wfext.Format.nAvgBytesPerSec = nSamplesPerSec * ((nChannels * wBitsPerSample) / 8); wfext.Format.cbSize = 22; wfext.Samples.wValidBitsPerSample = wBitsPerSample; wfext.dwChannelMask = 0x3; wfext.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; #if 1 wfext.SubFormat = KSDATAFORMAT_SUBTYPE_AC3; #endif ZeroMemory(&DSBufferDesc, sizeof(DSBufferDesc)); DSBufferDesc.dwSize = sizeof(DSBufferDesc); DSBufferDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE | DSBCAPS_GETCURRENTPOSITION2; DSBufferDesc.lpwfxFormat = (WAVEFORMATEX *)&wfext; DSBufferDesc.dwBufferBytes = wfext.Format.nAvgBytesPerSec / 100; HWND hwnd = GetForegroundWindow(); hr = DSSound8->SetCooperativeLevel(hwnd, DSSCL_PRIORITY); IF_FAILED_JUMP(hr, Exit); hr = DSSound8->CreateSoundBuffer(&DSBufferDesc, &DSBuffer, NULL); IF_FAILED_JUMP(hr, Exit); hr = DSBuffer->Play(0, 0, DSBPLAY_LOOPING); IF_FAILED_JUMP(hr, Exit); printf("Will set the following audio policy:\n"); printf("Test Certificate Enable: %s\n", TRUE ? "True" : "False"); printf("Copy OK: %s\n", FALSE ? "True" : "False"); printf("Digital Output Disable: %s\n", FALSE ? "True" : "False"); printf("DRM Level: %u\n", 1300); // Set policy when the stream is in RUN state dwConfigData = MAKE_MFPROTECTIONDATA_TRUSTEDAUDIODRIVERS2(TRUE, /*_bTestCertificateEnable*/ FALSE, /*_bDigitalOutputDisable*/ FALSE, /*_bCopyOK*/ 1300 /*_dwDrmLevel*/); hr = SetOTAPolicy(pEndpoint,dwConfigData, &pMFTrustedOutput, &pMFOutputTrustAuthority,&pMFOutputPolicy); IF_FAILED_JUMP(hr, Exit); // // Perform all the necessary streaming operations here. // // stop audio streaming DSBuffer->Stop(); // In order for the stream to restart successfully // Need to release the following OutputTrust* interface to release audio endpoint hr = ClearOTAPolicy(pMFTrustedOutput,pMFOutputTrustAuthority,pMFOutputPolicy); IF_FAILED_JUMP(hr, Exit); // After above release operations, the following Play() will succeed without device-in-use error message 0x8889000A DSBuffer->SetCurrentPosition(0); hr = DSBuffer->Play(0, 0, DSBPLAY_LOOPING); IF_FAILED_JUMP(hr, Exit); } ``` -------------------------------- ### Sample policy and schema implementation (OTADsoundSample.cpp) Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/protected-user-mode-audio--puma- Sample implementation of the policy and schema objects for protected user-mode audio. Includes the OTADsoundSample.cpp header, GUID definitions for AC3, function declarations for SetOTAPolicy and ClearOTAPolicy, and the IsDigitalEndpoint and GetDigitalAudioEndpoint functions. The code uses IF_FAILED_JUMP and SAFE_RELEASE macros that are not defined in this snippet. ```C++ //OTADsoundSample.cpp #include #include #include #include #include #include #include #include #include #include "OTADSoundSample.h" #define STATIC_KSDATAFORMAT_SUBTYPE_AC3\ DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_DOLBY_AC3_SPDIF) DEFINE_GUIDSTRUCT("00000092-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_AC3); #define KSDATAFORMAT_SUBTYPE_AC3 DEFINE_GUIDNAMED(KSDATAFORMAT_SUBTYPE_AC3) HRESULT SetOTAPolicy(IMMDevice *_pMMDevice, DWORD _dwConfigData, IMFTrustedOutput **_ppMFTrustedOutput, IMFOutputTrustAuthority **ppMFOutputTrustAuthority, IMFOutputPolicy **_ppMFOutputPolicy); HRESULT ClearOTAPolicy(IMFTrustedOutput *_pMFTrustedOutput, IMFOutputTrustAuthority *_pMFOutputTrustAuthority, IMFOutputPolicy *_pMFOutputPolicy); const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); BOOL IsDigitalEndpoint(IMMDevice *pDevice) { PROPVARIANT var; IPropertyStore *pProperties = NULL; EndpointFormFactor formfactor; BOOL bResult = FALSE; HRESULT hr = S_OK; PropVariantInit(&var); // Open endpoint properties hr = pDevice->OpenPropertyStore(STGM_READ, &pProperties); IF_FAILED_JUMP(hr, Exit); // get form factor hr = pProperties->GetValue(PKEY_AudioEndpoint_FormFactor, &var); IF_FAILED_JUMP(hr, Exit); formfactor = (EndpointFormFactor)var.uiVal; if ((SPDIF == formfactor) || (DigitalAudioDisplayDevice == formfactor)) { bResult = TRUE; } Exit: PropVariantClear(&var); SAFE_RELEASE(pProperties); return bResult; } HRESULT GetDigitalAudioEndpoint(IMMDevice** ppDevice) { IMMDeviceEnumerator *pEnumerator = NULL; IMMDevice *pDevice = NULL; IMMDeviceCollection *pEndpoints = NULL; UINT cEndpoints = 0; HRESULT hr = S_OK; *ppDevice = NULL; // Get enumerator for audio endpoint devices. hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator); IF_FAILED_JUMP(hr, Exit); ``` -------------------------------- ### Resource script for endpoint volume controls (Epvolume.rc) Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/endpoint-volume-controls Defines the dialog box with a volume slider and mute check box for the endpoint volume control example. Includes the necessary Windows and common controls headers. ```C++ // Epvolume.rc -- Resource script #include "resource.h" #include "windows.h" #include "commctrl.h" // // Dialog box // VOLUMECONTROL DIALOGEX 0, 0, 160, 60 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT CAPTION "Audio Endpoint Volume" FONT 8, "Arial Rounded MT Bold", 400, 0, 0x0 BEGIN LTEXT "Min",IDC_STATIC_MINVOL,10,10,20,12 RTEXT "Max",IDC_STATIC_MAXVOL,130,10,20,12 CONTROL "",IDC_SLIDER_VOLUME,"msctls_trackbar32", TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,10,20,140,12 CONTROL "Mute",IDC_CHECK_MUTE,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,20,40,70,12 END ``` -------------------------------- ### Render loop for static spatial audio objects Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/render-spatial-sound-using-spatial-audio-objects Starts the spatial audio stream and enters a render loop that waits for buffer completion events, activates a static audio object, writes audio data to its buffer, and ends the updating pass. Uses a counter to stop rendering after 5 seconds. ```C++ // Start streaming / rendering hr = spatialAudioStream->Start(); // This example will render 5 seconds of audio samples UINT totalFrameCount = format.nSamplesPerSec * 5; bool isRendering = true; while (isRendering) { // Wait for a signal from the audio-engine to start the next processing pass if (WaitForSingleObject(bufferCompletionEvent, 100) != WAIT_OBJECT_0) { hr = spatialAudioStream->Reset(); if (hr != S_OK) { // handle the error break; } } UINT32 availableDynamicObjectCount; UINT32 frameCount; // Begin the process of sending object data and metadata // Get the number of dynamic objects that can be used to send object-data // Get the frame count that each buffer will be filled with hr = spatialAudioStream->BeginUpdatingAudioObjects(&availableDynamicObjectCount, &frameCount); BYTE* buffer; UINT32 bufferLength; if (audioObjectFrontLeft == nullptr) { hr = spatialAudioStream->ActivateSpatialAudioObject(AudioObjectType::AudioObjectType_FrontLeft, &audioObjectFrontLeft); if (hr != S_OK) break; } // Get the buffer to write audio data hr = audioObjectFrontLeft->GetBuffer(&buffer, &bufferLength); if (totalFrameCount >= frameCount) { // Write audio data to the buffer WriteToAudioObjectBuffer(reinterpret_cast(buffer), frameCount, 200.0f, format.nSamplesPerSec); totalFrameCount -= frameCount; } else { // Write audio data to the buffer WriteToAudioObjectBuffer(reinterpret_cast(buffer), totalFrameCount, 750.0f, format.nSamplesPerSec); // Set end of stream for the last buffer hr = audioObjectFrontLeft->SetEndOfStream(totalFrameCount); audioObjectFrontLeft = nullptr; // Release the object isRendering = false; } // Let the audio engine know that the object data are available for processing now hr = spatialAudioStream->EndUpdatingAudioObjects(); }; ``` -------------------------------- ### Exclusive-mode stream: render loop with MMCSS boost and cleanup Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/exclusive-mode-streams Shows the core exclusive-mode rendering loop: get the buffer size, obtain the IAudioRenderClient service, preload the first buffer, boost thread priority with AvSetMmThreadCharacteristics("Pro Audio"), then start and run a while loop that waits on an event (2-second timeout) and fills each buffer until AUDCLNT_BUFFERFLAGS_SILENT. After the loop it sleeps for the requested duration, stops the stream, and cleans up handles, the task, the format, and COM objects. Requires an initialized pAudioClient, pMySource, hEvent, and hnsRequestedDuration; the EXIT_ON_ERROR macro and SAFE_RELEASE macro are assumed. ```cpp // Get the actual size of the two allocated buffers. hr = pAudioClient->GetBufferSize(&bufferFrameCount); EXIT_ON_ERROR(hr) hr = pAudioClient->GetService( IID_IAudioRenderClient, (void**)&pRenderClient); EXIT_ON_ERROR(hr) // To reduce latency, load the first buffer with data // from the audio source before starting the stream. hr = pRenderClient->GetBuffer(bufferFrameCount, &pData); EXIT_ON_ERROR(hr) hr = pMySource->LoadData(bufferFrameCount, pData, &flags); EXIT_ON_ERROR(hr) hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags); EXIT_ON_ERROR(hr) // Ask MMCSS to temporarily boost the thread priority // to reduce glitches while the low-latency stream plays. hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex); if (hTask == NULL) { hr = E_FAIL; EXIT_ON_ERROR(hr) } hr = pAudioClient->Start(); // Start playing. EXIT_ON_ERROR(hr) // Each loop fills one of the two buffers. while (flags != AUDCLNT_BUFFERFLAGS_SILENT) { // Wait for next buffer event to be signaled. DWORD retval = WaitForSingleObject(hEvent, 2000); if (retval != WAIT_OBJECT_0) { // Event handle timed out after a 2-second wait. pAudioClient->Stop(); hr = ERROR_TIMEOUT; goto Exit; } // Grab the next empty buffer from the audio device. hr = pRenderClient->GetBuffer(bufferFrameCount, &pData); EXIT_ON_ERROR(hr) // Load the buffer with data from the audio source. hr = pMySource->LoadData(bufferFrameCount, pData, &flags); EXIT_ON_ERROR(hr) hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags); EXIT_ON_ERROR(hr) } // Wait for the last buffer to play before stopping. Sleep((DWORD)(hnsRequestedDuration/REFTIMES_PER_MILLISEC)); hr = pAudioClient->Stop(); // Stop playing. EXIT_ON_ERROR(hr) Exit: if (hEvent != NULL) { CloseHandle(hEvent); } if (hTask != NULL) { AvRevertMmThreadCharacteristics(hTask); } CoTaskMemFree(pwfx); SAFE_RELEASE(pEnumerator) SAFE_RELEASE(pDevice) SAFE_RELEASE(pAudioClient) SAFE_RELEASE(pRenderClient) return hr; } ``` -------------------------------- ### Peakmeter.rc - Resource script for peak meter dialog Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/peak-meters Defines the dialog box for the peak meter example. Includes the dialog layout with a peak meter control and min/max labels. Must be compiled with the resource compiler. ```C++ // Peakmeter.rc -- Resource script #include "resource.h" #include "windows.h" // // Dialog // PEAKMETER DIALOGEX 0, 0, 150, 34 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT CAPTION "Peak Meter" FONT 8, "Arial Rounded MT Bold", 400, 0, 0x0 BEGIN CTEXT "",IDC_PEAK_METER,34,14,82,5 LTEXT "Min",IDC_STATIC_MINVOL,10,12,20,12 RTEXT "Max",IDC_STATIC_MAXVOL,120,12,20,12 END ``` -------------------------------- ### CTrustedAudioDriversOutputPolicy constructor and destructor Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/protected-user-mode-audio--puma- Constructor initializes members, creates a GUID, and calls CreateTrustedAudioDriversOutputSchema. On failure, prints an error message and returns an HRESULT. Destructor releases the output schema if non-null. ```cpp // constructor CTrustedAudioDriversOutputPolicy::CTrustedAudioDriversOutputPolicy(DWORD dwConfigData, HRESULT &hr) : m_cRefCount(1), m_dwConfigData(dwConfigData), m_pOutputSchema(NULL) { hr = CoCreateGuid(&m_guidOriginator); IF_FAILED_JUMP(hr, Exit); hr = CreateTrustedAudioDriversOutputSchema(dwConfigData, m_guidOriginator, &m_pOutputSchema); IF_FAILED_JUMP(hr, Exit); Exit: if (FAILED(hr)) { printf("CreateTrustedAudioDriversOutputSchema failed: hr = 0x%08x", hr); } return; } // destructor CTrustedAudioDriversOutputPolicy::~CTrustedAudioDriversOutputPolicy() { if (NULL != m_pOutputSchema) { m_pOutputSchema->Release(); } } ``` -------------------------------- ### WASAPI Automatic Stream Routing Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/automatic-stream-routing Code examples showing how to update a WASAPI implementation to support automatic stream routing. ```cpp PWSTR audioRenderGuidString; StringFromIID(DEVINTERFACE_AUDIO_RENDER, &audioRenderGuidString); ``` ```cpp //Activate the default audio interface ActivateAudioInterfaceAsync(audioRenderGuidString __uuidof(IAudioClient), NULL, completionHandler.Get(), operation.GetAddressOf())); ``` ```cpp CoTaskMemFree(audioRenderGuidString); //free the string memory ``` -------------------------------- ### Register for audio session events with IAudioSessionEvents Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/audio-events-for-legacy-audio-applications This C++ code example shows how an application can register to receive notifications of volume changes and other audio events using the IAudioSessionEvents interface. It is intended for legacy audio applications that need to update volume sliders in response to changes made through Sndvol. ```cpp C++ Copy ``` -------------------------------- ### Enumerate active render endpoints and select digital endpoint Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/protected-user-mode-audio--puma- Enumerates all active render endpoints using IMMDeviceEnumerator::EnumAudioEndpoints, gets the count, and iterates to find a digital endpoint (e.g., SPDIF or HDMI) using a custom IsDigitalEndpoint function. Includes error handling and cleanup with SAFE_RELEASE. ```cpp // Enumerate all active render endpoints, hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pEndpoints); IF_FAILED_JUMP(hr, Exit); hr = pEndpoints->GetCount(&cEndpoints); IF_FAILED_JUMP(hr, Exit); for (UINT i = 0; i < cEndpoints; i++) { hr = pEndpoints->Item(i, &pDevice); IF_FAILED_JUMP(hr, Exit); // Select the endpoint that meets the requirements. // For example, SPDIF analog output or HDMI // Not Shown. if (IsDigitalEndpoint(pDevice)) { *ppDevice = pDevice; (*ppDevice)->AddRef(); break; } SAFE_RELEASE(pDevice); } Exit: if (FAILED(hr)) { // Notify error. // Not Shown. } SAFE_RELEASE(pEndpoints); SAFE_RELEASE(pEnumerator); return hr; } ``` -------------------------------- ### Get DirectSound device GUID for a device role (GetDirectSoundGuid) Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/device-roles-for-directsound-applications Use this function to obtain the DirectSound or DirectSoundCapture device GUID for an audio endpoint device assigned to a specific device role. It takes the dataflow direction (eRender or eCapture) and the device role, then uses the MMDevice API to get the endpoint device and its property store, retrieving the PKEY_AudioEndpoint_GUID value and converting it to a GUID structure. The function returns S_OK on success or an error code, and cleans up resources via the Exit label. ```C++ //----------------------------------------------------------- // Get the DirectSound or DirectSoundCapture device GUID for // an audio endpoint device. If flow = eRender, the function // gets the DirectSound device GUID for the rendering device // with the specified device role. If flow = eCapture, the // function gets the DirectSoundCapture device GUID for the // capture device with the specified device role. //----------------------------------------------------------- #define EXIT_ON_ERROR(hr) \ if (FAILED(hr)) { goto Exit; } #define SAFE_RELEASE(punk) \ if ((punk) != NULL) \ { (punk)->Release(); (punk) = NULL; } HRESULT GetDirectSoundGuid(EDataFlow flow, ERole role, GUID* pDevGuid) { HRESULT hr = S_OK; IMMDeviceEnumerator *pEnumerator = NULL; IMMDevice *pDevice = NULL; IPropertyStore *pProps = NULL; PROPVARIANT var; PropVariantInit(&var); if (pDevGuid == NULL) { return E_POINTER; } // Get a device enumerator for the audio endpoint // devices in the system. hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator); EXIT_ON_ERROR(hr) // Get the endpoint device with the specified dataflow // direction (eRender or eCapture) and device role. hr = pEnumerator->GetDefaultAudioEndpoint(flow, role, &pDevice); EXIT_ON_ERROR(hr) hr = pDevice->OpenPropertyStore(STGM_READ, &pProps); EXIT_ON_ERROR(hr) // Get the DirectSound or DirectSoundCapture device GUID // (in WCHAR string format) for the endpoint device. hr = pProps->GetValue(PKEY_AudioEndpoint_GUID, &var); EXIT_ON_ERROR(hr) // Convert the WCHAR string to a GUID structure. hr = CLSIDFromString(var.pwszVal, pDevGuid); EXIT_ON_ERROR(hr) Exit: PropVariantClear(&var); SAFE_RELEASE(pEnumerator); SAFE_RELEASE(pDevice); SAFE_RELEASE(pProps); return hr; } ``` -------------------------------- ### CMMNotificationClient class implementing IMMNotificationClient Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/device-events Example implementation of the IMMNotificationClient interface. The class includes a SAFE_RELEASE macro for releasing COM pointers and declares a reference count and an IMMDeviceEnumerator pointer. This is a starting point for a client that registers for device-event notifications. ```cpp //----------------------------------------------------------- // Example implementation of IMMNotificationClient interface. // When the status of audio endpoint devices change, the // MMDevice module calls these methods to notify the client. //----------------------------------------------------------- #define SAFE_RELEASE(punk) \ if ((punk) != NULL) \ { (punk)->Release(); (punk) = NULL; } class CMMNotificationClient : public IMMNotificationClient { LONG _cRef; IMMDeviceEnumerator *_pEnumerator; ``` -------------------------------- ### Get OTA count with IMFTrustedOutput::GetOutputTrustAuthorityCount Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/protected-user-mode-audio--puma- Gets the count of available OTA objects by calling IMFTrustedOutput::GetOutputTrustAuthorityCount. The count is stored in m_dwCountOTA. ```C++ hr = pTrustedOutput->GetOutputTrustAuthorityCount(&m_dwCountOTA); ``` -------------------------------- ### Get IMFTrustedOutput via IAudioClient::GetService Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/protected-user-mode-audio--puma- Gets a reference to the IMFTrustedOutput interface of the output trust authority (OTA) object for the stream by calling IAudioClient::GetService with IID_IMFTrustedOutput. This is required for WASAPI clients to set the protection policy. ```C++ IMFTrustedOutput* pTrustedOutput = NULL; hr = pIAudioClient>GetService( __uuidof(IMFTrustedOutput), (void**)& pTrustedOutput); ``` -------------------------------- ### Open audio stream with IAudioClient::Initialize Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/protected-user-mode-audio--puma- Opens the audio stream by calling IAudioClient::Initialize on the IAudioClient object created for WASAPI. The ellipsis represents the required initialization parameters. ```C++ hr = pIAudioClient->Initialize(...); ``` -------------------------------- ### Activate ISpatialAudioClient and configure HRTF stream parameters Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/render-spatial-sound-using-spatial-audio-objects Activates ISpatialAudioClient on the default device, checks stream and format support, creates a buffer completion event, retrieves the maximum dynamic object count, and configures SpatialAudioHrtfActivationParams with optional distance decay, directivity, environment, and orientation. Finally activates the spatial audio stream for HRTF. ```C++ // Activate ISpatialAudioClient on the desired audio-device Microsoft::WRL::ComPtr spatialAudioClient; hr = defaultDevice->Activate(__uuidof(ISpatialAudioClient), CLSCTX_INPROC_SERVER, nullptr, (void**)&spatialAudioClient); Microsoft::WRL::ComPtr spatialAudioStreamForHrtf; hr = spatialAudioClient->IsSpatialAudioStreamAvailable(__uuidof(spatialAudioStreamForHrtf), NULL); hr = spatialAudioClient->IsAudioObjectFormatSupported(&format); // Create the event that will be used to signal the client for more data HANDLE bufferCompletionEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); UINT32 maxDynamicObjectCount; hr = spatialAudioClient->GetMaxDynamicObjectCount(&maxDynamicObjectCount); SpatialAudioHrtfActivationParams streamParams; streamParams.ObjectFormat = &format; streamParams.StaticObjectTypeMask = AudioObjectType_None; streamParams.MinDynamicObjectCount = 0; streamParams.MaxDynamicObjectCount = min(maxDynamicObjectCount, 4); streamParams.Category = AudioCategory_GameEffects; streamParams.EventHandle = bufferCompletionEvent; streamParams.NotifyObject = NULL; SpatialAudioHrtfDistanceDecay decayModel; decayModel.CutoffDistance = 100; decayModel.MaxGain = 3.98f; decayModel.MinGain = float(1.58439 * pow(10, -5)); decayModel.Type = SpatialAudioHrtfDistanceDecayType::SpatialAudioHrtfDistanceDecay_NaturalDecay; decayModel.UnityGainDistance = 1; streamParams.DistanceDecay = &decayModel; SpatialAudioHrtfDirectivity directivity; directivity.Type = SpatialAudioHrtfDirectivityType::SpatialAudioHrtfDirectivity_Cone; directivity.Scaling = 1.0f; SpatialAudioHrtfDirectivityCone cone; cone.directivity = directivity; cone.InnerAngle = 0.1f; cone.OuterAngle = 0.2f; SpatialAudioHrtfDirectivityUnion directivityUnion; directivityUnion.Cone = cone; streamParams.Directivity = &directivityUnion; SpatialAudioHrtfEnvironmentType environment = SpatialAudioHrtfEnvironmentType::SpatialAudioHrtfEnvironment_Large; streamParams.Environment = &environment; SpatialAudioHrtfOrientation orientation = { 1,0,0,0,1,0,0,0,1 }; // identity matrix streamParams.Orientation = &orientation; PROPVARIANT pv; PropVariantInit(&pv); pv.vt = VT_BLOB; pv.blob.cbSize = sizeof(streamParams); pv.blob.pBlobData = (BYTE *)&streamParams; hr = spatialAudioClient->ActivateSpatialAudioStream(&pv, __uuidof(spatialAudioStreamForHrtf), (void**)&spatialAudioStreamForHrtf); ``` -------------------------------- ### Get default audio render device with IMMDeviceEnumerator Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/render-spatial-sound-using-spatial-audio-objects Get the default audio render device by creating an MMDeviceEnumerator and calling GetDefaultAudioEndpoint with eRender and eMultimedia. The resulting IMMDevice pointer is used as the target for spatial audio rendering. ```C++ HRESULT hr; Microsoft::WRL::ComPtr deviceEnum; Microsoft::WRL::ComPtr defaultDevice; hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&deviceEnum); hr = deviceEnum->GetDefaultAudioEndpoint(EDataFlow::eRender, eMultimedia, &defaultDevice); ``` -------------------------------- ### Enumerate Audio Endpoint Devices with IMMDeviceEnumerator Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/device-properties Enumerates active audio rendering endpoint devices and prints their friendly names and endpoint ID strings. Requires COM initialization (CoInitialize or CoInitializeEx) before calling CoCreateInstance. The FAILED macro is defined in Winerror.h. ```cpp //----------------------------------------------------------- // This function enumerates all active (plugged in) audio // rendering endpoint devices. It prints the friendly name // and endpoint ID string of each endpoint device. //----------------------------------------------------------- #include #include #include #include #define EXIT_ON_ERROR(hres) \ if (FAILED(hres)) { goto Exit; } #define SAFE_RELEASE(punk) \ if ((punk) != NULL) \ { (punk)->Release(); (punk) = NULL; } const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); void PrintEndpointNames() { HRESULT hr = S_OK; IMMDeviceEnumerator *pEnumerator = NULL; IMMDeviceCollection *pCollection = NULL; IMMDevice *pEndpoint = NULL; IPropertyStore *pProps = NULL; LPWSTR pwszID = NULL; hr = CoCreateInstance( CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator); EXIT_ON_ERROR(hr) hr = pEnumerator->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &pCollection); EXIT_ON_ERROR(hr) UINT count; hr = pCollection->GetCount(&count); EXIT_ON_ERROR(hr) if (count == 0) { printf("No endpoints found.\n"); } // Each loop prints the name of an endpoint device. for (ULONG i = 0; i < count; i++) { // Get pointer to endpoint number i. hr = pCollection->Item(i, &pEndpoint); EXIT_ON_ERROR(hr) // Get the endpoint ID string. hr = pEndpoint->GetId(&pwszID); EXIT_ON_ERROR(hr) hr = pEndpoint->OpenPropertyStore( STGM_READ, &pProps); EXIT_ON_ERROR(hr) PROPVARIANT varName; // Initialize container for property value. PropVariantInit(&varName); // Get the endpoint's friendly-name property. hr = pProps->GetValue( PKEY_Device_FriendlyName, &varName); EXIT_ON_ERROR(hr) // GetValue succeeds and returns S_OK if PKEY_Device_FriendlyName is not found. // In this case vartName.vt is set to VT_EMPTY. if (varName.vt != VT_EMPTY) { // Print endpoint friendly name and endpoint ID. printf("Endpoint %d: \"%S\" (%S)\n", i, varName.pwszVal, pwszID); } CoTaskMemFree(pwszID); pwszID = NULL; PropVariantClear(&varName); SAFE_RELEASE(pProps) SAFE_RELEASE(pEndpoint) } SAFE_RELEASE(pEnumerator) SAFE_RELEASE(pCollection) return; Exit: printf("Error!\n"); CoTaskMemFree(pwszID); SAFE_RELEASE(pEnumerator) SAFE_RELEASE(pCollection) SAFE_RELEASE(pEndpoint) SAFE_RELEASE(pProps) } ``` -------------------------------- ### Get default communication device endpoint with IMMDeviceEnumerator::GetDefaultAudioEndpoint Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/using-the-communication-device Use this code to get a reference to the default communication device endpoint for rendering an audio stream. It creates the device enumerator and calls GetDefaultAudioEndpoint with eCommunications in the Role parameter to restrict enumeration to communication devices. After obtaining the IMMDevice reference, you can activate services such as IAudioClient, IAudioEndpointVolume, or IAudioSessionManager. ```cpp IMMDevice *defaultDevice = NULL; hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator); hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eCommunications, &defaultDevice); ``` -------------------------------- ### Implement IAudioEndpointVolumeCallback (Epvolume.h) Source: https://learn.microsoft.com/en-us/windows/win32/coreaudio/endpoint-volume-controls Defines the CAudioEndpointVolumeCallback class implementing IAudioEndpointVolumeCallback. It includes IUnknown methods (AddRef, Release, QueryInterface) and an OnNotify method that updates the volume control UI when the endpoint volume changes. The OnNotify method posts messages to the dialog controls IDC_CHECK_MUTE and IDC_SLIDER_VOLUME, using the MAX_VOL constant to scale the volume. ```cpp // Epvolume.h -- Implementation of IAudioEndpointVolumeCallback interface #include #include #include #include #include "resource.h" // Dialog handle from dialog box procedure extern HWND g_hDlg; // Client's proprietary event-context GUID extern GUID g_guidMyContext; // Maximum volume level on trackbar #define MAX_VOL 100 #define SAFE_RELEASE(punk) \ if ((punk) != NULL) \ { (punk)->Release(); (punk) = NULL; } //----------------------------------------------------------- // Client implementation of IAudioEndpointVolumeCallback // interface. When a method in the IAudioEndpointVolume // interface changes the volume level or muting state of the // endpoint device, the change initiates a call to the // client's IAudioEndpointVolumeCallback::OnNotify method. //----------------------------------------------------------- class CAudioEndpointVolumeCallback : public IAudioEndpointVolumeCallback { LONG _cRef; public: CAudioEndpointVolumeCallback() : _cRef(1) { } ~CAudioEndpointVolumeCallback() { } // IUnknown methods -- AddRef, Release, and QueryInterface ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&_cRef); } ULONG STDMETHODCALLTYPE Release() { ULONG ulRef = InterlockedDecrement(&_cRef); if (0 == ulRef) { delete this; } return ulRef; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) { if (IID_IUnknown == riid) { AddRef(); *ppvInterface = (IUnknown*)this; } else if (__uuidof(IAudioEndpointVolumeCallback) == riid) { AddRef(); *ppvInterface = (IAudioEndpointVolumeCallback*)this; } else { *ppvInterface = NULL; return E_NOINTERFACE; } return S_OK; } // Callback method for endpoint-volume-change notifications. HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) { return E_INVALIDARG; } if (g_hDlg != NULL && pNotify->guidEventContext != g_guidMyContext) { PostMessage(GetDlgItem(g_hDlg, IDC_CHECK_MUTE), BM_SETCHECK, (pNotify->bMuted) ? BST_CHECKED : BST_UNCHECKED, 0); PostMessage(GetDlgItem(g_hDlg, IDC_SLIDER_VOLUME), TBM_SETPOS, TRUE, LPARAM((UINT32)(MAX_VOL*pNotify->fMasterVolume + 0.5))); } return S_OK; } }; ```