// 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 // // // WASAPICaptureSharedEventDriven.cpp : Scaffolding associated with the WASAPI Capture Shared Event Driven sample application. // // This application captures data from the specified input device and writes it to a uniquely named .WAV file in the current directory. // #include "stdafx.h" #include #include "WASAPICapture.h" #include "CmdLine.h" int TargetLatency = 20; int TargetDurationInSec = 10; bool ShowHelp; bool UseConsoleDevice; bool UseCommunicationsDevice; bool UseMultimediaDevice; bool DisableMMCSS; wchar_t *OutputEndpoint; CommandLineSwitch CmdLineArgs[] = { { L"?", L"Print this help", CommandLineSwitch::SwitchTypeNone, reinterpret_cast(&ShowHelp)}, { L"h", L"Print this help", CommandLineSwitch::SwitchTypeNone, reinterpret_cast(&ShowHelp)}, { L"l", L"Audio Capture Latency (ms)", CommandLineSwitch::SwitchTypeInteger, reinterpret_cast(&TargetLatency), false}, { L"d", L"Audio Capture Duration (s)", CommandLineSwitch::SwitchTypeInteger, reinterpret_cast(&TargetDurationInSec), false}, { L"m", L"Disable the use of MMCSS", CommandLineSwitch::SwitchTypeNone, reinterpret_cast(&DisableMMCSS)}, { L"console", L"Use the default console device", CommandLineSwitch::SwitchTypeNone, reinterpret_cast(&UseConsoleDevice)}, { L"communications", L"Use the default communications device", CommandLineSwitch::SwitchTypeNone, reinterpret_cast(&UseCommunicationsDevice)}, { L"multimedia", L"Use the default multimedia device", CommandLineSwitch::SwitchTypeNone, reinterpret_cast(&UseMultimediaDevice)}, { L"endpoint", L"Use the specified endpoint ID", CommandLineSwitch::SwitchTypeString, reinterpret_cast(&OutputEndpoint), true}, }; size_t CmdLineArgLength = ARRAYSIZE(CmdLineArgs); // // Print help for the sample // void Help(LPCWSTR ProgramName) { printf("Usage: %S [-/][Switch][:][Value]\n\n", ProgramName); printf("Where Switch is one of the following: \n"); for (size_t i = 0 ; i < CmdLineArgLength ; i += 1) { printf(" -%S: %S\n", CmdLineArgs[i].SwitchName, CmdLineArgs[i].SwitchHelp); } } // // Retrieves the device friendly name for a particular device in a device collection. // // The returned string was allocated using malloc() so it should be freed using free(); // LPWSTR GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex) { IMMDevice *device; LPWSTR deviceId; HRESULT hr; hr = DeviceCollection->Item(DeviceIndex, &device); if (FAILED(hr)) { printf("Unable to get device %d: %x\n", DeviceIndex, hr); return NULL; } hr = device->GetId(&deviceId); if (FAILED(hr)) { printf("Unable to get device %d id: %x\n", DeviceIndex, hr); return NULL; } IPropertyStore *propertyStore; hr = device->OpenPropertyStore(STGM_READ, &propertyStore); SafeRelease(&device); if (FAILED(hr)) { printf("Unable to open device %d property store: %x\n", DeviceIndex, hr); return NULL; } PROPVARIANT friendlyName; PropVariantInit(&friendlyName); hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName); SafeRelease(&propertyStore); if (FAILED(hr)) { printf("Unable to retrieve friendly name for device %d : %x\n", DeviceIndex, hr); return NULL; } wchar_t deviceName[128]; hr = StringCbPrintf(deviceName, sizeof(deviceName), L"%s (%s)", friendlyName.vt != VT_LPWSTR ? L"Unknown" : friendlyName.pwszVal, deviceId); if (FAILED(hr)) { printf("Unable to format friendly name for device %d : %x\n", DeviceIndex, hr); return NULL; } PropVariantClear(&friendlyName); CoTaskMemFree(deviceId); wchar_t *returnValue = _wcsdup(deviceName); if (returnValue == NULL) { printf("Unable to allocate buffer for return\n"); return NULL; } return returnValue; } // // Based on the input switches, pick the specified device to use. // bool PickDevice(IMMDevice **DeviceToUse, bool *IsDefaultDevice, ERole *DefaultDeviceRole) { HRESULT hr; bool retValue = true; IMMDeviceEnumerator *deviceEnumerator = NULL; IMMDeviceCollection *deviceCollection = NULL; *IsDefaultDevice = false; // Assume we're not using the default device. hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)); if (FAILED(hr)) { printf("Unable to instantiate device enumerator: %x\n", hr); retValue = false; goto Exit; } IMMDevice *device = NULL; // // First off, if none of the console switches was specified, use the console device. // if (!UseConsoleDevice && !UseCommunicationsDevice && !UseMultimediaDevice && OutputEndpoint == NULL) { // // The user didn't specify an output device, prompt the user for a device and use that. // hr = deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection); if (FAILED(hr)) { printf("Unable to retrieve device collection: %x\n", hr); retValue = false; goto Exit; } printf("Select an output device:\n"); printf(" 0: Default Console Device\n"); printf(" 1: Default Communications Device\n"); printf(" 2: Default Multimedia Device\n"); UINT deviceCount; hr = deviceCollection->GetCount(&deviceCount); if (FAILED(hr)) { printf("Unable to get device collection length: %x\n", hr); retValue = false; goto Exit; } for (UINT i = 0 ; i < deviceCount ; i += 1) { LPWSTR deviceName; deviceName = GetDeviceName(deviceCollection, i); if (deviceName == NULL) { retValue = false; goto Exit; } printf(" %d: %S\n", i + 3, deviceName); free(deviceName); } wchar_t choice[10]; _getws_s(choice); // Note: Using the safe CRT version of _getws. long deviceIndex; wchar_t *endPointer; deviceIndex = wcstoul(choice, &endPointer, 0); if (deviceIndex == 0 && endPointer == choice) { printf("unrecognized device index: %S\n", choice); retValue = false; goto Exit; } switch (deviceIndex) { case 0: UseConsoleDevice = 1; break; case 1: UseCommunicationsDevice = 1; break; case 2: UseMultimediaDevice = 1; break; default: hr = deviceCollection->Item(deviceIndex - 3, &device); if (FAILED(hr)) { printf("Unable to retrieve device %d: %x\n", deviceIndex - 3, hr); retValue = false; goto Exit; } break; } } else if (OutputEndpoint != NULL) { hr = deviceEnumerator->GetDevice(OutputEndpoint, &device); if (FAILED(hr)) { printf("Unable to get endpoint for endpoint %S: %x\n", OutputEndpoint, hr); retValue = false; goto Exit; } } if (device == NULL) { ERole deviceRole = eConsole; // Assume we're using the console role. if (UseConsoleDevice) { deviceRole = eConsole; } else if (UseCommunicationsDevice) { deviceRole = eCommunications; } else if (UseMultimediaDevice) { deviceRole = eMultimedia; } hr = deviceEnumerator->GetDefaultAudioEndpoint(eCapture, deviceRole, &device); if (FAILED(hr)) { printf("Unable to get default device for role %d: %x\n", deviceRole, hr); retValue = false; goto Exit; } *IsDefaultDevice = true; *DefaultDeviceRole = deviceRole; } *DeviceToUse = device; retValue = true; Exit: SafeRelease(&deviceCollection); SafeRelease(&deviceEnumerator); return retValue; } // // WAV file writer. // // This is a VERY simple .WAV file writer. // // // A wave file consists of: // // RIFF header: 8 bytes consisting of the signature "RIFF" followed by a 4 byte file length. // WAVE header: 4 bytes consisting of the signature "WAVE". // fmt header: 4 bytes consisting of the signature "fmt " followed by a WAVEFORMATEX // WAVEFORMAT: bytes containing a waveformat structure. // DATA header: 8 bytes consisting of the signature "data" followed by a 4 byte file length. // wave data: bytes containing wave data. // // // Header for a WAV file - we define a structure describing the first few fields in the header for convenience. // struct WAVEHEADER { DWORD dwRiff; // "RIFF" DWORD dwSize; // Size DWORD dwWave; // "WAVE" DWORD dwFmt; // "fmt " DWORD dwFmtSize; // Wave Format Size }; // Static RIFF header, we'll append the format to it. const BYTE WaveHeader[] = { 'R', 'I', 'F', 'F', 0x00, 0x00, 0x00, 0x00, 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ', 0x00, 0x00, 0x00, 0x00 }; // Static wave DATA tag. const BYTE WaveData[] = { 'd', 'a', 't', 'a'}; // // Write the contents of a WAV file. We take as input the data to write and the format of that data. // bool WriteWaveFile(HANDLE FileHandle, const BYTE *Buffer, const size_t BufferSize, const WAVEFORMATEX *WaveFormat) { DWORD waveFileSize = sizeof(WAVEHEADER) + sizeof(WAVEFORMATEX) + WaveFormat->cbSize + sizeof(WaveData) + sizeof(DWORD) + static_cast(BufferSize); BYTE *waveFileData = new (std::nothrow) BYTE[waveFileSize]; BYTE *waveFilePointer = waveFileData; WAVEHEADER *waveHeader = reinterpret_cast(waveFileData); if (waveFileData == NULL) { printf("Unable to allocate %d bytes to hold output wave data\n", waveFileSize); return false; } // // Copy in the wave header - we'll fix up the lengths later. // CopyMemory(waveFilePointer, WaveHeader, sizeof(WaveHeader)); waveFilePointer += sizeof(WaveHeader); // // Update the sizes in the header. // waveHeader->dwSize = waveFileSize - (2 * sizeof(DWORD)); waveHeader->dwFmtSize = sizeof(WAVEFORMATEX) + WaveFormat->cbSize; // // Next copy in the WaveFormatex structure. // CopyMemory(waveFilePointer, WaveFormat, sizeof(WAVEFORMATEX) + WaveFormat->cbSize); waveFilePointer += sizeof(WAVEFORMATEX) + WaveFormat->cbSize; // // Then the data header. // CopyMemory(waveFilePointer, WaveData, sizeof(WaveData)); waveFilePointer += sizeof(WaveData); *(reinterpret_cast(waveFilePointer)) = static_cast(BufferSize); waveFilePointer += sizeof(DWORD); // // And finally copy in the audio data. // CopyMemory(waveFilePointer, Buffer, BufferSize); // // Last but not least, write the data to the file. // DWORD bytesWritten; if (!WriteFile(FileHandle, waveFileData, waveFileSize, &bytesWritten, NULL)) { printf("Unable to write wave file: %d\n", GetLastError()); delete []waveFileData; return false; } if (bytesWritten != waveFileSize) { printf("Failed to write entire wave file\n"); delete []waveFileData; return false; } delete []waveFileData; return true; } // // Write the captured wave data to an output file so that it can be examined later. // void SaveWaveData(BYTE *CaptureBuffer, size_t BufferSize, const WAVEFORMATEX *WaveFormat) { wchar_t waveFileName[MAX_PATH]; HRESULT hr = StringCbCopy(waveFileName, sizeof(waveFileName), L"WASAPICaptureEventDriven-"); if (SUCCEEDED(hr)) { GUID testGuid; if (SUCCEEDED(CoCreateGuid(&testGuid))) { wchar_t *guidString; if (SUCCEEDED(StringFromCLSID(testGuid, &guidString))) { hr = StringCbCat(waveFileName, sizeof(waveFileName), guidString); if (SUCCEEDED(hr)) { hr = StringCbCat(waveFileName, sizeof(waveFileName), L".WAV"); if (SUCCEEDED(hr)) { HANDLE waveHandle = CreateFile(waveFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (waveHandle != INVALID_HANDLE_VALUE) { if (WriteWaveFile(waveHandle, CaptureBuffer, BufferSize, WaveFormat)) { printf("Successfully wrote WAVE data to %S\n", waveFileName); } else { printf("Unable to write wave file\n"); } CloseHandle(waveHandle); } else { printf("Unable to open output WAV file %S: %d\n", waveFileName, GetLastError()); } } } CoTaskMemFree(guidString); } } } } // // The core of the sample. // // Parse the command line, interpret the input parameters, pick an audio device then capture data from that device. // When done, write the data to a file. // int wmain(int argc, wchar_t* argv[]) { int result = 0; IMMDevice *device = NULL; bool isDefaultDevice; ERole role; printf("WASAPI Capture Shared Event Driven Sample\n"); printf("Copyright (c) Microsoft. All Rights Reserved\n"); printf("\n"); if (!ParseCommandLine(argc, argv, CmdLineArgs, CmdLineArgLength)) { result = -1; goto Exit; } // // Now that we've parsed our command line, do some semantic checks. // // // First off, show the help for the app if the user asked for it. // if (ShowHelp) { Help(argv[0]); goto Exit; } // // The user can only specify one of -console, -communications or -multimedia or a specific endpoint. // if (((UseConsoleDevice != 0) + (UseCommunicationsDevice != 0) + (UseMultimediaDevice != 0) + (OutputEndpoint != NULL)) > 1) { printf("Can only specify one of -Console, -Communications or -Multimedia\n"); result = -1; goto Exit; } // // A GUI application should use COINIT_APARTMENTTHREADED instead of COINIT_MULTITHREADED. // HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { printf("Unable to initialize COM: %x\n", hr); result = hr; goto Exit; } // // Now that we've parsed our command line, pick the device to capture. // if (!PickDevice(&device, &isDefaultDevice, &role)) { result = -1; goto Exit; } printf("Capture audio data for %d seconds\n", TargetDurationInSec); // // Instantiate a capturer and capture sounds for TargetDuration seconds // // Configure the capturer to enable stream switching on the specified role if the user specified one of the default devices. // { CWASAPICapture *capturer = new (std::nothrow) CWASAPICapture(device, isDefaultDevice, role); if (capturer == NULL) { printf("Unable to allocate capturer\n"); return -1; } if (capturer->Initialize(TargetLatency)) { // // We've initialized the capturer. Once we've done that, we know some information about the // mix format and we can allocate the buffer that we're going to capture. // // // The buffer is going to contain "TargetDuration" seconds worth of PCM data. That means // we're going to have TargetDuration*samples/second frames multiplied by the frame size. // size_t captureBufferSize = capturer->SamplesPerSecond() * TargetDurationInSec * capturer->FrameSize(); BYTE *captureBuffer = new (std::nothrow) BYTE[captureBufferSize]; if (captureBuffer == NULL) { printf("Unable to allocate capture buffer\n"); return -1; } if (capturer->Start(captureBuffer, captureBufferSize)) { do { printf("."); Sleep(1000); } while (--TargetDurationInSec); printf("\n"); capturer->Stop(); // // We've now captured our wave data. Now write it out in a wave file. // SaveWaveData(captureBuffer, capturer->BytesCaptured(), capturer->MixFormat()); // // Now shut down the capturer and release it we're done. // capturer->Shutdown(); SafeRelease(&capturer); } delete []captureBuffer; } } Exit: SafeRelease(&device); CoUninitialize(); return 0; }