326 lines
9.6 KiB
C++
326 lines
9.6 KiB
C++
// 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<void **>(&_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<CWasapiChat *>(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;
|
|
} |