// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved // // // A very simple "Chat" client - reads samples from the default console device and discards the output. // #include "StdAfx.h" #include "WasapiChat.h" CWasapiChat::CWasapiChat(HWND AppWindow) : CChatTransport(AppWindow), _ChatEndpoint(NULL), _AudioClient(NULL), _RenderClient(NULL), _CaptureClient(NULL), _Flow(eRender), _ChatThread(NULL), _ShutdownEvent(NULL), _AudioSamplesReadyEvent(NULL) { } CWasapiChat::~CWasapiChat(void) { SafeRelease(&_ChatEndpoint); SafeRelease(&_AudioClient); SafeRelease(&_RenderClient); SafeRelease(&_CaptureClient); if (_ChatThread) { CloseHandle(_ChatThread); } if (_ShutdownEvent) { CloseHandle(_ShutdownEvent); } if (_AudioSamplesReadyEvent) { CloseHandle(_AudioSamplesReadyEvent); } } // // We can "Chat" if there's more than one capture device. // bool CWasapiChat::Initialize(bool UseInputDevice) { IMMDeviceEnumerator *deviceEnumerator; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)); if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to instantiate device enumerator", L"WASAPI Transport Initialize Failure", MB_OK); return false; } if (UseInputDevice) { _Flow = eCapture; } else { _Flow = eRender; } hr = deviceEnumerator->GetDefaultAudioEndpoint(_Flow, eCommunications, &_ChatEndpoint); deviceEnumerator->Release(); if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to retrieve default endpoint", L"WASAPI Transport Initialize Failure", MB_OK); return false; } // // Create our shutdown event - we want an auto reset event that starts in the not-signaled state. // _ShutdownEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); if (_ShutdownEvent == NULL) { MessageBox(_AppWindow, L"Unable to create shutdown event.", L"WASAPI Transport Initialize Failure", MB_OK); return false; } _AudioSamplesReadyEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); if (_ShutdownEvent == NULL) { MessageBox(_AppWindow, L"Unable to create samples ready event.", L"WASAPI Transport Initialize Failure", MB_OK); return false; } return true; } // // Shut down the chat code and free all the resources. // void CWasapiChat::Shutdown() { if (_ChatThread) { SetEvent(_ShutdownEvent); WaitForSingleObject(_ChatThread, INFINITE); CloseHandle(_ChatThread); _ChatThread = NULL; } if (_ShutdownEvent) { CloseHandle(_ShutdownEvent); _ShutdownEvent = NULL; } if (_AudioSamplesReadyEvent) { CloseHandle(_AudioSamplesReadyEvent); _AudioSamplesReadyEvent = NULL; } SafeRelease(&_ChatEndpoint); SafeRelease(&_AudioClient); SafeRelease(&_RenderClient); SafeRelease(&_CaptureClient); } // // Start the "Chat" - open the capture device, start capturing. // bool CWasapiChat::StartChat(bool HideFromVolumeMixer) { WAVEFORMATEX *mixFormat = NULL; HRESULT hr = _ChatEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast(&_AudioClient)); if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to activate audio client.", L"WASAPI Transport Start Failure", MB_OK); return false; } hr = _AudioClient->GetMixFormat(&mixFormat); if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to get mix format on audio client.", L"WASAPI Transport Start Failure", MB_OK); return false; } // // Initialize the chat transport - Initialize WASAPI in event driven mode, associate the audio client with // our samples ready event handle, retrieve a capture/render client for the transport, create the chat thread // and start the audio engine. // GUID chatGuid; hr = CoCreateGuid(&chatGuid); if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to create GUID.", L"WASAPI Transport Start Failure", MB_OK); return false; } hr = _AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, (HideFromVolumeMixer ? AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE : 0) | AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, 500000, 0, mixFormat, &chatGuid); CoTaskMemFree(mixFormat); mixFormat = NULL; if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to initialize audio client.", L"WASAPI Transport Start Failure", MB_OK); return false; } hr = _AudioClient->SetEventHandle(_AudioSamplesReadyEvent); if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to set ready event.", L"WASAPI Transport Start Failure", MB_OK); return false; } if (_Flow == eRender) { hr = _AudioClient->GetService(IID_PPV_ARGS(&_RenderClient)); } else { hr = _AudioClient->GetService(IID_PPV_ARGS(&_CaptureClient)); } if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to get Capture/Render client.", L"WASAPI Transport Start Failure", MB_OK); return false; } // // Now create the thread which is going to drive the "Chat". // _ChatThread = CreateThread(NULL, 0, WasapiChatThread, this, 0, NULL); if (_ChatThread == NULL) { MessageBox(_AppWindow, L"Unable to create transport thread.", L"WASAPI Transport Start Failure", MB_OK); return false; } // // For render, we want to pre-roll a frames worth of silence into the pipeline. That way the audio engine won't glitch on startup. // if (_Flow == eRender) { BYTE *pData; UINT32 framesAvailable; hr = _AudioClient->GetBufferSize(&framesAvailable); if (FAILED(hr)) { MessageBox(_AppWindow, L"Failed to get client buffer size.", L"WASAPI Transport Start Failure", MB_OK); return false; } hr = _RenderClient->GetBuffer(framesAvailable, &pData); if (FAILED(hr)) { MessageBox(_AppWindow, L"Failed to get buffer.", L"WASAPI Transport Start Failure", MB_OK); return false; } hr = _RenderClient->ReleaseBuffer(framesAvailable, AUDCLNT_BUFFERFLAGS_SILENT); if (FAILED(hr)) { MessageBox(_AppWindow, L"Failed to release buffer.", L"WASAPI Transport Start Failure", MB_OK); return false; } } // // We're ready to go, start the chat! // hr = _AudioClient->Start(); if (FAILED(hr)) { MessageBox(_AppWindow, L"Unable to start chat client.", L"WASAPI Transport Start Failure", MB_OK); return false; } return true; } // // Stop the "Chat" - Stop the capture thread and release the buffers. // void CWasapiChat::StopChat() { // // Tell the chat thread to shut down, wait for the thread to complete then clean up all the stuff we // allocated in StartChat(). // if (_ShutdownEvent) { SetEvent(_ShutdownEvent); } if (_ChatThread) { WaitForSingleObject(_ChatThread, INFINITE); CloseHandle(_ChatThread); _ChatThread = NULL; } SafeRelease(&_RenderClient); SafeRelease(&_CaptureClient); SafeRelease(&_AudioClient); } DWORD CWasapiChat::WasapiChatThread(LPVOID Context) { bool stillPlaying = true; CWasapiChat *chat = static_cast(Context); HANDLE waitArray[2] = {chat->_ShutdownEvent, chat->_AudioSamplesReadyEvent}; while (stillPlaying) { HRESULT hr; DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, INFINITE); switch (waitResult) { case WAIT_OBJECT_0 + 0: stillPlaying = false; // We're done, exit the loop. break; case WAIT_OBJECT_0 + 1: // // Either stream silence to the audio client or ignore the audio samples. // // Note that we don't check for errors here. This is because // (a) there's no way of reporting the failure // (b) once the streaming engine has started there's really no way for it to fail. // if (chat->_Flow == eRender) { BYTE *pData; UINT32 framesAvailable; hr = chat->_AudioClient->GetCurrentPadding(&framesAvailable); hr = chat->_RenderClient->GetBuffer(framesAvailable, &pData); hr = chat->_RenderClient->ReleaseBuffer(framesAvailable, AUDCLNT_BUFFERFLAGS_SILENT); } else { BYTE *pData; UINT32 framesAvailable; DWORD flags; hr = chat->_AudioClient->GetCurrentPadding(&framesAvailable); hr = chat->_CaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, NULL); hr = chat->_CaptureClient->ReleaseBuffer(framesAvailable); } } } return 0; } // // Returns "true" for the window messages we handle in our transport. // bool CWasapiChat::HandlesMessage(HWND /*hWnd*/, UINT /*message*/) { return false; } // // Don't process Wave messages. // INT_PTR CWasapiChat::MessageHandler(HWND /*hWnd*/, UINT /*message*/, WPARAM /*wParam*/, LPARAM /*lParam*/) { return FALSE; }