├── .gitignore ├── LICENSE.txt ├── README.md ├── guid.cpp ├── loopback-capture.cpp ├── loopback-capture.h ├── main.cpp ├── prefs.cpp ├── prefs.h ├── simpleserver.sln ├── simpleserver.vcxproj ├── simpletcpserver.cpp └── simpletcpserver.h /.gitignore: -------------------------------------------------------------------------------- 1 | Debug 2 | Release 3 | ipch 4 | *.user 5 | *sdf 6 | *suo 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Microsoft Limited Public License 2 | 3 | This license governs use of code marked as "sample" or "example" available on this web site without a license agreement, as provided under the section above titled "NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE." If you use such code (the "software"), you accept this license. If you do not accept the license, do not use the software. 4 | 5 | 1. Definitions 6 | 7 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 8 | 9 | A "contribution" is the original software, or any additions or changes to the software. 10 | 11 | A "contributor" is any person that distributes its contribution under this license. 12 | 13 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 14 | 15 | 2. Grant of Rights 16 | 17 | (A) Copyright Grant - Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 18 | 19 | (B) Patent Grant - Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 20 | 21 | 3. Conditions and Limitations 22 | 23 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 24 | 25 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 26 | 27 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 28 | 29 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 30 | 31 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 32 | 33 | (F) Platform Limitation - The licenses granted in sections 2(A) and 2(B) extend only to the software or derivative works that you create that run on a Microsoft Windows operating system product. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Protocol Server 2 | 3 | This is a server that sends all the available sound card audio from a Windows PC to the network. This is meant to be the Windows counterpart to the simple protocol module in PulseAudio. The simple protocol streams uncompressed 16 bit 2 channel PCM data. 4 | 5 | The server is meant to be used with the Simple Protocol Player client on Android. The play store link is here: https://play.google.com/store/apps/details?id=com.kaytat.simpleprotocolplayer . 6 | 7 | The client source is here: https://github.com/kaytat/SimpleProtocolPlayer 8 | 9 | ## WSAPI Loopback 10 | 11 | The server uses the Windows Audio Session API to capture the audio. And for each audio frame, it sends that data to the connected client. 12 | 13 | The code is a simple modification to an existing MSDN example: http://blogs.msdn.com/b/matthew_van_eerde/archive/2014/11/05/draining-the-wasapi-capture-buffer-fully.aspx . Instead of writing the data to a MMIO file, the data is send over a socket. 14 | 15 | ## Limitations 16 | 17 | ### One client only 18 | Although PulseAudio's module will allow multiple connections, this server does not. 19 | 20 | ### No XP support 21 | I've only tested this with one Win7 machine. And it doesn't seem to work with XP. 22 | 23 | The Win7 machine was recently upgraded to Win10 and that seems to work. 24 | 25 | ### Sampling rate / format 26 | The Android app supports 4 sampling frequencies - 48, 44.1, 24, and 22.05kHz and both stereo and mono. The app assumes 16-bit samples. 27 | 28 | My sound card doesn't support the lower sampling rates and so the server has a command line option to use an integer divider to downsample. 29 | 30 | The sampling rate may need to be manually adjusted by the user since the default format may not be a format supported by the Android app. To do this, the user will need to manually configure the properties in the "Advanced" tab of the playback device. Please also see this: http://kaytat.com/blog/?p=332 31 | 32 | ### Usage 33 | simpleserver.exe [--device "Device long name"] [--mono] [--div divisor] 34 | 35 | -? prints this message. 36 | --list-devices displays the long names of all active playback devices. 37 | --device captures from the specified device (default if omitted) 38 | --mono convert from stereo to mono 39 | --div divisor reduce sample rate by a factor of divisor 40 | -------------------------------------------------------------------------------- /guid.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://blogs.msdn.com/b/matthew_van_eerde/archive/2014/11/05/draining-the-wasapi-capture-buffer-fully.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | // guid.cpp 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | -------------------------------------------------------------------------------- /loopback-capture.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://blogs.msdn.com/b/matthew_van_eerde/archive/2014/11/05/draining-the-wasapi-capture-buffer-fully.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | // loopback-capture.cpp 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "loopback-capture.h" 21 | #include "simpletcpserver.h" 22 | 23 | HRESULT LoopbackCapture( 24 | IMMDevice *pMMDevice, 25 | bool bInt16, 26 | HANDLE hStartedEvent, 27 | HANDLE hStopEvent, 28 | PUINT32 pnFrames, 29 | bool bMono, 30 | INT32 iSampleRateDivisor 31 | ); 32 | 33 | DWORD WINAPI LoopbackCaptureThreadFunction(LPVOID pContext) { 34 | LoopbackCaptureThreadFunctionArguments *pArgs = 35 | (LoopbackCaptureThreadFunctionArguments*)pContext; 36 | 37 | pArgs->hr = CoInitialize(NULL); 38 | if (FAILED(pArgs->hr)) { 39 | printf("CoInitialize failed: hr = 0x%08x\n", pArgs->hr); 40 | return 0; 41 | } 42 | 43 | pArgs->hr = LoopbackCapture( 44 | pArgs->pMMDevice, 45 | pArgs->bInt16, 46 | pArgs->hStartedEvent, 47 | pArgs->hStopEvent, 48 | &pArgs->nFrames, 49 | pArgs->bMono, 50 | pArgs->iSampleRateDivisor 51 | ); 52 | 53 | CoUninitialize(); 54 | return 0; 55 | } 56 | 57 | HRESULT LoopbackCapture( 58 | IMMDevice *pMMDevice, 59 | bool bInt16, 60 | HANDLE hStartedEvent, 61 | HANDLE hStopEvent, 62 | PUINT32 pnFrames, 63 | bool bMono, 64 | INT32 iSampleRateDivisor 65 | ) { 66 | HRESULT hr; 67 | SimpleTcpServer server; 68 | 69 | // Wait for client connection before attempting any audio capture 70 | server.setup(); 71 | server.waitForClient(); 72 | 73 | // activate an IAudioClient 74 | IAudioClient *pAudioClient; 75 | hr = pMMDevice->Activate( 76 | __uuidof(IAudioClient), 77 | CLSCTX_ALL, NULL, 78 | (void**)&pAudioClient 79 | ); 80 | if (FAILED(hr)) { 81 | printf("IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr); 82 | return hr; 83 | } 84 | 85 | // get the default device periodicity 86 | REFERENCE_TIME hnsDefaultDevicePeriod; 87 | hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL); 88 | if (FAILED(hr)) { 89 | printf("IAudioClient::GetDevicePeriod failed: hr = 0x%08x\n", hr); 90 | pAudioClient->Release(); 91 | return hr; 92 | } 93 | 94 | // get the default device format 95 | WAVEFORMATEX *pwfx; 96 | hr = pAudioClient->GetMixFormat(&pwfx); 97 | if (FAILED(hr)) { 98 | printf("IAudioClient::GetMixFormat failed: hr = 0x%08x\n", hr); 99 | CoTaskMemFree(pwfx); 100 | pAudioClient->Release(); 101 | return hr; 102 | } 103 | 104 | if (bInt16) { 105 | // coerce int-16 wave format 106 | // can do this in-place since we're not changing the size of the format 107 | // also, the engine will auto-convert from float to int for us 108 | switch (pwfx->wFormatTag) { 109 | case WAVE_FORMAT_IEEE_FLOAT: 110 | pwfx->wFormatTag = WAVE_FORMAT_PCM; 111 | pwfx->wBitsPerSample = 16; 112 | pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; 113 | pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; 114 | break; 115 | 116 | case WAVE_FORMAT_EXTENSIBLE: 117 | { 118 | // naked scope for case-local variable 119 | PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast(pwfx); 120 | if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) { 121 | pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; 122 | pEx->Samples.wValidBitsPerSample = 16; 123 | pwfx->wBitsPerSample = 16; 124 | pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; 125 | pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; 126 | } else { 127 | printf("Don't know how to coerce mix format to int-16\n"); 128 | CoTaskMemFree(pwfx); 129 | pAudioClient->Release(); 130 | return E_UNEXPECTED; 131 | } 132 | } 133 | break; 134 | 135 | default: 136 | printf("Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16\n", pwfx->wFormatTag); 137 | CoTaskMemFree(pwfx); 138 | pAudioClient->Release(); 139 | return E_UNEXPECTED; 140 | } 141 | } 142 | 143 | // create a periodic waitable timer 144 | HANDLE hWakeUp = CreateWaitableTimer(NULL, FALSE, NULL); 145 | if (NULL == hWakeUp) { 146 | DWORD dwErr = GetLastError(); 147 | printf("CreateWaitableTimer failed: last error = %u\n", dwErr); 148 | CoTaskMemFree(pwfx); 149 | pAudioClient->Release(); 150 | return HRESULT_FROM_WIN32(dwErr); 151 | } 152 | 153 | UINT32 nBlockAlign = pwfx->nBlockAlign; 154 | UINT32 nBufferSize; 155 | *pnFrames = 0; 156 | 157 | // call IAudioClient::Initialize 158 | // note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK 159 | // do not work together... 160 | // the "data ready" event never gets set 161 | // so we're going to do a timer-driven loop 162 | hr = pAudioClient->Initialize( 163 | AUDCLNT_SHAREMODE_SHARED, 164 | AUDCLNT_STREAMFLAGS_LOOPBACK, 165 | 0, 0, pwfx, 0 166 | ); 167 | if (FAILED(hr)) { 168 | printf("IAudioClient::Initialize failed: hr = 0x%08x\n", hr); 169 | CloseHandle(hWakeUp); 170 | pAudioClient->Release(); 171 | return hr; 172 | } 173 | CoTaskMemFree(pwfx); 174 | 175 | // Get the buffer size 176 | hr = pAudioClient->GetBufferSize(&nBufferSize); 177 | if (FAILED(hr)) { 178 | printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr); 179 | CloseHandle(hWakeUp); 180 | pAudioClient->Release(); 181 | return hr; 182 | } 183 | 184 | // Configure the server. The buffer size returned is in frames 185 | // so assume stereo, 16 bits per sample to convert from frames to bytes 186 | server.configure( 187 | bMono, 188 | iSampleRateDivisor, 189 | nBufferSize * 2 * 2); 190 | 191 | // activate an IAudioCaptureClient 192 | IAudioCaptureClient *pAudioCaptureClient; 193 | hr = pAudioClient->GetService( 194 | __uuidof(IAudioCaptureClient), 195 | (void**)&pAudioCaptureClient 196 | ); 197 | if (FAILED(hr)) { 198 | printf("IAudioClient::GetService(IAudioCaptureClient) failed: hr 0x%08x\n", hr); 199 | CloseHandle(hWakeUp); 200 | pAudioClient->Release(); 201 | return hr; 202 | } 203 | 204 | // register with MMCSS 205 | DWORD nTaskIndex = 0; 206 | HANDLE hTask = AvSetMmThreadCharacteristics(L"Capture", &nTaskIndex); 207 | if (NULL == hTask) { 208 | DWORD dwErr = GetLastError(); 209 | printf("AvSetMmThreadCharacteristics failed: last error = %u\n", dwErr); 210 | pAudioCaptureClient->Release(); 211 | CloseHandle(hWakeUp); 212 | pAudioClient->Release(); 213 | return HRESULT_FROM_WIN32(dwErr); 214 | } 215 | 216 | // set the waitable timer 217 | LARGE_INTEGER liFirstFire; 218 | liFirstFire.QuadPart = -hnsDefaultDevicePeriod / 2; // negative means relative time 219 | LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 / (10 * 1000); // convert to milliseconds 220 | BOOL bOK = SetWaitableTimer( 221 | hWakeUp, 222 | &liFirstFire, 223 | lTimeBetweenFires, 224 | NULL, NULL, FALSE 225 | ); 226 | if (!bOK) { 227 | DWORD dwErr = GetLastError(); 228 | printf("SetWaitableTimer failed: last error = %u\n", dwErr); 229 | AvRevertMmThreadCharacteristics(hTask); 230 | pAudioCaptureClient->Release(); 231 | CloseHandle(hWakeUp); 232 | pAudioClient->Release(); 233 | return HRESULT_FROM_WIN32(dwErr); 234 | } 235 | 236 | // call IAudioClient::Start 237 | hr = pAudioClient->Start(); 238 | if (FAILED(hr)) { 239 | printf("IAudioClient::Start failed: hr = 0x%08x\n", hr); 240 | AvRevertMmThreadCharacteristics(hTask); 241 | pAudioCaptureClient->Release(); 242 | CloseHandle(hWakeUp); 243 | pAudioClient->Release(); 244 | return hr; 245 | } 246 | SetEvent(hStartedEvent); 247 | 248 | // loopback capture loop 249 | HANDLE waitArray[2] = { hStopEvent, hWakeUp }; 250 | DWORD dwWaitResult; 251 | 252 | bool bDone = false; 253 | for (UINT32 nPasses = 0; !bDone; nPasses++) { 254 | // drain data while it is available 255 | UINT32 nNextPacketSize; 256 | for ( 257 | hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize); 258 | SUCCEEDED(hr) && nNextPacketSize > 0; 259 | hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize) 260 | ) { 261 | // get the captured data 262 | BYTE *pData; 263 | UINT32 nNumFramesToRead; 264 | DWORD dwFlags; 265 | 266 | hr = pAudioCaptureClient->GetBuffer( 267 | &pData, 268 | &nNumFramesToRead, 269 | &dwFlags, 270 | NULL, 271 | NULL 272 | ); 273 | if (FAILED(hr)) { 274 | printf("IAudioCaptureClient::GetBuffer failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, *pnFrames, hr); 275 | pAudioClient->Stop(); 276 | CancelWaitableTimer(hWakeUp); 277 | AvRevertMmThreadCharacteristics(hTask); 278 | pAudioCaptureClient->Release(); 279 | CloseHandle(hWakeUp); 280 | pAudioClient->Release(); 281 | return hr; 282 | } 283 | 284 | #ifdef _DEBUG 285 | if (0 != dwFlags) { 286 | printf("[ignoring] IAudioCaptureClient::GetBuffer set flags to 0x%08x on pass %u after %u frames\n", dwFlags, nPasses, *pnFrames); 287 | } 288 | #endif 289 | 290 | if (0 == nNumFramesToRead) { 291 | printf("IAudioCaptureClient::GetBuffer said to read 0 frames on pass %u after %u frames\n", nPasses, *pnFrames); 292 | pAudioClient->Stop(); 293 | CancelWaitableTimer(hWakeUp); 294 | AvRevertMmThreadCharacteristics(hTask); 295 | pAudioCaptureClient->Release(); 296 | CloseHandle(hWakeUp); 297 | pAudioClient->Release(); 298 | return E_UNEXPECTED; 299 | } 300 | 301 | LONG lBytesToWrite = nNumFramesToRead * nBlockAlign; 302 | if (server.sendData(reinterpret_cast(pData), lBytesToWrite) != 0) { 303 | printf("Error sending data to peer\n"); 304 | pAudioClient->Stop(); 305 | CancelWaitableTimer(hWakeUp); 306 | AvRevertMmThreadCharacteristics(hTask); 307 | pAudioCaptureClient->Release(); 308 | CloseHandle(hWakeUp); 309 | pAudioClient->Release(); 310 | return E_UNEXPECTED; 311 | } 312 | *pnFrames += nNumFramesToRead; 313 | 314 | hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead); 315 | if (FAILED(hr)) { 316 | printf("IAudioCaptureClient::ReleaseBuffer failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, *pnFrames, hr); 317 | pAudioClient->Stop(); 318 | CancelWaitableTimer(hWakeUp); 319 | AvRevertMmThreadCharacteristics(hTask); 320 | pAudioCaptureClient->Release(); 321 | CloseHandle(hWakeUp); 322 | pAudioClient->Release(); 323 | return hr; 324 | } 325 | } 326 | 327 | if (FAILED(hr)) { 328 | printf("IAudioCaptureClient::GetNextPacketSize failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, *pnFrames, hr); 329 | pAudioClient->Stop(); 330 | CancelWaitableTimer(hWakeUp); 331 | AvRevertMmThreadCharacteristics(hTask); 332 | pAudioCaptureClient->Release(); 333 | CloseHandle(hWakeUp); 334 | pAudioClient->Release(); 335 | return hr; 336 | } 337 | 338 | dwWaitResult = WaitForMultipleObjects( 339 | ARRAYSIZE(waitArray), waitArray, 340 | FALSE, INFINITE 341 | ); 342 | 343 | if (WAIT_OBJECT_0 == dwWaitResult) { 344 | printf("Received stop event after %u passes and %u frames\n", nPasses, *pnFrames); 345 | bDone = true; 346 | continue; // exits loop 347 | } 348 | 349 | if (WAIT_OBJECT_0 + 1 != dwWaitResult) { 350 | printf("Unexpected WaitForMultipleObjects return value %u on pass %u after %u frames\n", dwWaitResult, nPasses, *pnFrames); 351 | pAudioClient->Stop(); 352 | CancelWaitableTimer(hWakeUp); 353 | AvRevertMmThreadCharacteristics(hTask); 354 | pAudioCaptureClient->Release(); 355 | CloseHandle(hWakeUp); 356 | pAudioClient->Release(); 357 | return E_UNEXPECTED; 358 | } 359 | } // capture loop 360 | 361 | pAudioClient->Stop(); 362 | CancelWaitableTimer(hWakeUp); 363 | AvRevertMmThreadCharacteristics(hTask); 364 | pAudioCaptureClient->Release(); 365 | CloseHandle(hWakeUp); 366 | pAudioClient->Release(); 367 | 368 | server.shutdown(); 369 | 370 | return hr; 371 | } 372 | -------------------------------------------------------------------------------- /loopback-capture.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://blogs.msdn.com/b/matthew_van_eerde/archive/2014/11/05/draining-the-wasapi-capture-buffer-fully.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | // loopback-capture.h 12 | 13 | // call CreateThread on this function 14 | // feed it the address of a LoopbackCaptureThreadFunctionArguments 15 | // it will capture via loopback from the IMMDevice 16 | // and dump output to the HMMIO 17 | // until the stop event is set 18 | // any failures will be propagated back via hr 19 | 20 | struct LoopbackCaptureThreadFunctionArguments { 21 | IMMDevice *pMMDevice; 22 | bool bInt16; 23 | HANDLE hStartedEvent; 24 | HANDLE hStopEvent; 25 | UINT32 nFrames; 26 | HRESULT hr; 27 | bool bMono; 28 | INT32 iSampleRateDivisor; 29 | }; 30 | 31 | DWORD WINAPI LoopbackCaptureThreadFunction(LPVOID pContext); 32 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://blogs.msdn.com/b/matthew_van_eerde/archive/2014/11/05/draining-the-wasapi-capture-buffer-fully.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | // main.cpp 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "prefs.h" 19 | #include "loopback-capture.h" 20 | 21 | int do_everything(int argc, LPCWSTR argv[]); 22 | 23 | int _cdecl wmain(int argc, LPCWSTR argv[]) { 24 | HRESULT hr = S_OK; 25 | 26 | hr = CoInitialize(NULL); 27 | if (FAILED(hr)) { 28 | printf("CoInitialize failed: hr = 0x%08x", hr); 29 | return -__LINE__; 30 | } 31 | 32 | int result = 0; 33 | do { 34 | result = do_everything(argc, argv); 35 | } while (result != 0); 36 | 37 | CoUninitialize(); 38 | return result; 39 | } 40 | 41 | int do_everything(int argc, LPCWSTR argv[]) { 42 | HRESULT hr = S_OK; 43 | bool explicitUserExit = false; 44 | 45 | // parse command line 46 | CPrefs prefs(argc, argv, hr); 47 | if (FAILED(hr)) { 48 | printf("CPrefs::CPrefs constructor failed: hr = 0x%08x\n", hr); 49 | return -__LINE__; 50 | } 51 | if (S_FALSE == hr) { 52 | // nothing to do 53 | return 0; 54 | } 55 | 56 | // create a "loopback capture has started" event 57 | HANDLE hStartedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 58 | if (NULL == hStartedEvent) { 59 | printf("CreateEvent failed: last error is %u\n", GetLastError()); 60 | return -__LINE__; 61 | } 62 | 63 | // create a "stop capturing now" event 64 | HANDLE hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 65 | if (NULL == hStopEvent) { 66 | printf("CreateEvent failed: last error is %u\n", GetLastError()); 67 | CloseHandle(hStartedEvent); 68 | return -__LINE__; 69 | } 70 | 71 | // create arguments for loopback capture thread 72 | LoopbackCaptureThreadFunctionArguments threadArgs; 73 | threadArgs.hr = E_UNEXPECTED; // thread will overwrite this 74 | threadArgs.pMMDevice = prefs.m_pMMDevice; 75 | threadArgs.bInt16 = prefs.m_bInt16; 76 | threadArgs.hStartedEvent = hStartedEvent; 77 | threadArgs.hStopEvent = hStopEvent; 78 | threadArgs.nFrames = 0; 79 | threadArgs.bMono = prefs.m_bMono; 80 | threadArgs.iSampleRateDivisor = prefs.m_iSampleRateDivisor; 81 | 82 | HANDLE hThread = CreateThread( 83 | NULL, 0, 84 | LoopbackCaptureThreadFunction, &threadArgs, 85 | 0, NULL 86 | ); 87 | if (NULL == hThread) { 88 | printf("CreateThread failed: last error is %u\n", GetLastError()); 89 | CloseHandle(hStopEvent); 90 | CloseHandle(hStartedEvent); 91 | return -__LINE__; 92 | } 93 | 94 | // wait for either capture to start or the thread to end 95 | HANDLE waitArray[2] = { hStartedEvent, hThread }; 96 | DWORD dwWaitResult; 97 | dwWaitResult = WaitForMultipleObjects( 98 | ARRAYSIZE(waitArray), waitArray, 99 | FALSE, INFINITE 100 | ); 101 | 102 | if (WAIT_OBJECT_0 + 1 == dwWaitResult) { 103 | printf("Thread aborted before starting to loopback capture: hr = 0x%08x\n", threadArgs.hr); 104 | CloseHandle(hStartedEvent); 105 | CloseHandle(hThread); 106 | CloseHandle(hStopEvent); 107 | return -__LINE__; 108 | } 109 | 110 | if (WAIT_OBJECT_0 != dwWaitResult) { 111 | printf("Unexpected WaitForMultipleObjects return value %u", dwWaitResult); 112 | CloseHandle(hStartedEvent); 113 | CloseHandle(hThread); 114 | CloseHandle(hStopEvent); 115 | return -__LINE__; 116 | } 117 | 118 | CloseHandle(hStartedEvent); 119 | 120 | HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); 121 | 122 | if (INVALID_HANDLE_VALUE == hStdIn) { 123 | printf("GetStdHandle returned INVALID_HANDLE_VALUE: last error is %u\n", GetLastError()); 124 | SetEvent(hStopEvent); 125 | WaitForSingleObject(hThread, INFINITE); 126 | CloseHandle(hStartedEvent); 127 | CloseHandle(hThread); 128 | CloseHandle(hStopEvent); 129 | return -__LINE__; 130 | } 131 | 132 | printf("Press Enter to quit...\n"); 133 | 134 | // wait for the thread to terminate early 135 | // or for the user to press (and release) Enter 136 | HANDLE rhHandles[2] = { hThread, hStdIn }; 137 | 138 | bool bKeepWaiting = true; 139 | while (bKeepWaiting) { 140 | 141 | dwWaitResult = WaitForMultipleObjects(2, rhHandles, FALSE, INFINITE); 142 | 143 | switch (dwWaitResult) { 144 | 145 | case WAIT_OBJECT_0: // hThread 146 | printf("The thread terminated early - something bad happened\n"); 147 | bKeepWaiting = false; 148 | break; 149 | 150 | case WAIT_OBJECT_0 + 1: // hStdIn 151 | // see if any of them was an Enter key-up event 152 | INPUT_RECORD rInput[128]; 153 | DWORD nEvents; 154 | if (!ReadConsoleInput(hStdIn, rInput, ARRAYSIZE(rInput), &nEvents)) { 155 | printf("ReadConsoleInput failed: last error is %u\n", GetLastError()); 156 | SetEvent(hStopEvent); 157 | WaitForSingleObject(hThread, INFINITE); 158 | bKeepWaiting = false; 159 | } else { 160 | for (DWORD i = 0; i < nEvents; i++) { 161 | if ( 162 | KEY_EVENT == rInput[i].EventType && 163 | VK_RETURN == rInput[i].Event.KeyEvent.wVirtualKeyCode && 164 | !rInput[i].Event.KeyEvent.bKeyDown 165 | ) { 166 | printf("Stopping capture...\n"); 167 | SetEvent(hStopEvent); 168 | WaitForSingleObject(hThread, INFINITE); 169 | bKeepWaiting = false; 170 | explicitUserExit = true; 171 | break; 172 | } 173 | } 174 | // if none of them were Enter key-up events, 175 | // continue waiting 176 | } 177 | break; 178 | 179 | default: 180 | printf("WaitForMultipleObjects returned unexpected value 0x%08x\n", dwWaitResult); 181 | SetEvent(hStopEvent); 182 | WaitForSingleObject(hThread, INFINITE); 183 | bKeepWaiting = false; 184 | break; 185 | } 186 | } 187 | 188 | DWORD exitCode; 189 | if (!GetExitCodeThread(hThread, &exitCode)) { 190 | printf("GetExitCodeThread failed: last error is %u\n", GetLastError()); 191 | CloseHandle(hThread); 192 | CloseHandle(hStopEvent); 193 | return -__LINE__; 194 | } 195 | 196 | if (0 != exitCode) { 197 | printf("Loopback capture thread exit code is %u; expected 0\n", exitCode); 198 | CloseHandle(hThread); 199 | CloseHandle(hStopEvent); 200 | return -__LINE__; 201 | } 202 | 203 | if (S_OK != threadArgs.hr) { 204 | printf("Thread HRESULT is 0x%08x\n", threadArgs.hr); 205 | CloseHandle(hThread); 206 | CloseHandle(hStopEvent); 207 | return -__LINE__; 208 | } 209 | 210 | CloseHandle(hThread); 211 | CloseHandle(hStopEvent); 212 | 213 | // let prefs' destructor call mmioClose 214 | return (explicitUserExit ? 0 : -__LINE__); 215 | } 216 | -------------------------------------------------------------------------------- /prefs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://blogs.msdn.com/b/matthew_van_eerde/archive/2014/11/05/draining-the-wasapi-capture-buffer-fully.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | // prefs.cpp 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "prefs.h" 20 | 21 | void usage(LPCWSTR exe); 22 | HRESULT get_default_device(IMMDevice **ppMMDevice); 23 | HRESULT list_devices(); 24 | HRESULT get_specific_device(LPCWSTR szLongName, IMMDevice **ppMMDevice); 25 | 26 | void usage(LPCWSTR exe) { 27 | printf( 28 | "%ls -?\n" 29 | "%ls --list-devices\n" 30 | "%ls [--device \"Device long name\"] [--mono] [--div divisor]\n" 31 | "\n" 32 | " -? prints this message.\n" 33 | " --list-devices displays the long names of all active playback devices.\n" 34 | " --device captures from the specified device (default if omitted)\n" 35 | " --mono convert from stereo to mono\n" 36 | " --div divisor reduce sample rate by a factor of divisor\n", 37 | exe, exe, exe 38 | ); 39 | } 40 | 41 | CPrefs::CPrefs(int argc, LPCWSTR argv[], HRESULT &hr) 42 | : m_pMMDevice(NULL) 43 | , m_bInt16(true) 44 | , m_bMono(false) 45 | , m_iSampleRateDivisor(1) 46 | , m_pwfx(NULL) 47 | { 48 | switch (argc) { 49 | case 2: 50 | if (0 == _wcsicmp(argv[1], L"-?") || 0 == _wcsicmp(argv[1], L"/?")) { 51 | // print usage but don't actually capture 52 | hr = S_FALSE; 53 | usage(argv[0]); 54 | return; 55 | } else if (0 == _wcsicmp(argv[1], L"--list-devices")) { 56 | // list the devices but don't actually capture 57 | hr = list_devices(); 58 | 59 | // don't actually play 60 | if (S_OK == hr) { 61 | hr = S_FALSE; 62 | return; 63 | } 64 | } 65 | // intentional fallthrough 66 | 67 | default: 68 | // loop through arguments and parse them 69 | for (int i = 1; i < argc; i++) { 70 | 71 | // --device 72 | if (0 == _wcsicmp(argv[i], L"--device")) { 73 | if (NULL != m_pMMDevice) { 74 | printf("Only one --device switch is allowed\n"); 75 | hr = E_INVALIDARG; 76 | return; 77 | } 78 | 79 | if (i++ == argc) { 80 | printf("--device switch requires an argument\n"); 81 | hr = E_INVALIDARG; 82 | return; 83 | } 84 | 85 | hr = get_specific_device(argv[i], &m_pMMDevice); 86 | if (FAILED(hr)) { 87 | return; 88 | } 89 | 90 | continue; 91 | } 92 | 93 | // --int-16 94 | if (0 == _wcsicmp(argv[i], L"--int-16")) { 95 | if (m_bInt16) { 96 | printf("Only one --int-16 switch is allowed\n"); 97 | hr = E_INVALIDARG; 98 | return; 99 | } 100 | 101 | m_bInt16 = true; 102 | continue; 103 | } 104 | 105 | // --mono 106 | if (0 == _wcsicmp(argv[i], L"--mono")) { 107 | m_bMono = true; 108 | continue; 109 | } 110 | 111 | // --div divisor 112 | if (0 == _wcsicmp(argv[i], L"--div")) { 113 | if (i++ == argc) { 114 | printf("--div switch requires an argument\n"); 115 | hr = E_INVALIDARG; 116 | return; 117 | } 118 | 119 | m_iSampleRateDivisor = _wtoi(argv[i]); 120 | continue; 121 | } 122 | 123 | printf("Invalid argument %ls\n", argv[i]); 124 | hr = E_INVALIDARG; 125 | return; 126 | } 127 | 128 | // open default device if not specified 129 | if (NULL == m_pMMDevice) { 130 | hr = get_default_device(&m_pMMDevice); 131 | if (FAILED(hr)) { 132 | return; 133 | } 134 | } 135 | } 136 | } 137 | 138 | CPrefs::~CPrefs() { 139 | if (NULL != m_pMMDevice) { 140 | m_pMMDevice->Release(); 141 | } 142 | 143 | if (NULL != m_pwfx) { 144 | CoTaskMemFree(m_pwfx); 145 | } 146 | } 147 | 148 | HRESULT get_default_device(IMMDevice **ppMMDevice) { 149 | HRESULT hr = S_OK; 150 | IMMDeviceEnumerator *pMMDeviceEnumerator; 151 | 152 | // activate a device enumerator 153 | hr = CoCreateInstance( 154 | __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 155 | __uuidof(IMMDeviceEnumerator), 156 | (void**)&pMMDeviceEnumerator 157 | ); 158 | if (FAILED(hr)) { 159 | printf("CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x\n", hr); 160 | return hr; 161 | } 162 | 163 | // get the default render endpoint 164 | hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, ppMMDevice); 165 | pMMDeviceEnumerator->Release(); 166 | if (FAILED(hr)) { 167 | printf("IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x\n", hr); 168 | return hr; 169 | } 170 | 171 | return S_OK; 172 | } 173 | 174 | HRESULT list_devices() { 175 | HRESULT hr = S_OK; 176 | 177 | // get an enumerator 178 | IMMDeviceEnumerator *pMMDeviceEnumerator; 179 | 180 | hr = CoCreateInstance( 181 | __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 182 | __uuidof(IMMDeviceEnumerator), 183 | (void**)&pMMDeviceEnumerator 184 | ); 185 | if (FAILED(hr)) { 186 | printf("CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x\n", hr); 187 | return hr; 188 | } 189 | 190 | IMMDeviceCollection *pMMDeviceCollection; 191 | 192 | // get all the active render endpoints 193 | hr = pMMDeviceEnumerator->EnumAudioEndpoints( 194 | eRender, DEVICE_STATE_ACTIVE, &pMMDeviceCollection 195 | ); 196 | pMMDeviceEnumerator->Release(); 197 | if (FAILED(hr)) { 198 | printf("IMMDeviceEnumerator::EnumAudioEndpoints failed: hr = 0x%08x\n", hr); 199 | return hr; 200 | } 201 | 202 | UINT count; 203 | hr = pMMDeviceCollection->GetCount(&count); 204 | if (FAILED(hr)) { 205 | pMMDeviceCollection->Release(); 206 | printf("IMMDeviceCollection::GetCount failed: hr = 0x%08x\n", hr); 207 | return hr; 208 | } 209 | printf("Active render endpoints found: %u\n", count); 210 | 211 | for (UINT i = 0; i < count; i++) { 212 | IMMDevice *pMMDevice; 213 | 214 | // get the "n"th device 215 | hr = pMMDeviceCollection->Item(i, &pMMDevice); 216 | if (FAILED(hr)) { 217 | pMMDeviceCollection->Release(); 218 | printf("IMMDeviceCollection::Item failed: hr = 0x%08x\n", hr); 219 | return hr; 220 | } 221 | 222 | // open the property store on that device 223 | IPropertyStore *pPropertyStore; 224 | hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore); 225 | pMMDevice->Release(); 226 | if (FAILED(hr)) { 227 | pMMDeviceCollection->Release(); 228 | printf("IMMDevice::OpenPropertyStore failed: hr = 0x%08x\n", hr); 229 | return hr; 230 | } 231 | 232 | // get the long name property 233 | PROPVARIANT pv; PropVariantInit(&pv); 234 | hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); 235 | pPropertyStore->Release(); 236 | if (FAILED(hr)) { 237 | pMMDeviceCollection->Release(); 238 | printf("IPropertyStore::GetValue failed: hr = 0x%08x\n", hr); 239 | return hr; 240 | } 241 | 242 | if (VT_LPWSTR != pv.vt) { 243 | printf("PKEY_Device_FriendlyName variant type is %u - expected VT_LPWSTR", pv.vt); 244 | 245 | PropVariantClear(&pv); 246 | pMMDeviceCollection->Release(); 247 | return E_UNEXPECTED; 248 | } 249 | 250 | printf(" %ls\n", pv.pwszVal); 251 | 252 | PropVariantClear(&pv); 253 | } 254 | pMMDeviceCollection->Release(); 255 | 256 | return S_OK; 257 | } 258 | 259 | HRESULT get_specific_device(LPCWSTR szLongName, IMMDevice **ppMMDevice) { 260 | HRESULT hr = S_OK; 261 | 262 | *ppMMDevice = NULL; 263 | 264 | // get an enumerator 265 | IMMDeviceEnumerator *pMMDeviceEnumerator; 266 | 267 | hr = CoCreateInstance( 268 | __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 269 | __uuidof(IMMDeviceEnumerator), 270 | (void**)&pMMDeviceEnumerator 271 | ); 272 | if (FAILED(hr)) { 273 | printf("CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x\n", hr); 274 | return hr; 275 | } 276 | 277 | IMMDeviceCollection *pMMDeviceCollection; 278 | 279 | // get all the active render endpoints 280 | hr = pMMDeviceEnumerator->EnumAudioEndpoints( 281 | eRender, DEVICE_STATE_ACTIVE, &pMMDeviceCollection 282 | ); 283 | pMMDeviceEnumerator->Release(); 284 | if (FAILED(hr)) { 285 | printf("IMMDeviceEnumerator::EnumAudioEndpoints failed: hr = 0x%08x\n", hr); 286 | return hr; 287 | } 288 | 289 | UINT count; 290 | hr = pMMDeviceCollection->GetCount(&count); 291 | if (FAILED(hr)) { 292 | pMMDeviceCollection->Release(); 293 | printf("IMMDeviceCollection::GetCount failed: hr = 0x%08x\n", hr); 294 | return hr; 295 | } 296 | 297 | for (UINT i = 0; i < count; i++) { 298 | IMMDevice *pMMDevice; 299 | 300 | // get the "n"th device 301 | hr = pMMDeviceCollection->Item(i, &pMMDevice); 302 | if (FAILED(hr)) { 303 | pMMDeviceCollection->Release(); 304 | printf("IMMDeviceCollection::Item failed: hr = 0x%08x\n", hr); 305 | return hr; 306 | } 307 | 308 | // open the property store on that device 309 | IPropertyStore *pPropertyStore; 310 | hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore); 311 | if (FAILED(hr)) { 312 | pMMDevice->Release(); 313 | pMMDeviceCollection->Release(); 314 | printf("IMMDevice::OpenPropertyStore failed: hr = 0x%08x\n", hr); 315 | return hr; 316 | } 317 | 318 | // get the long name property 319 | PROPVARIANT pv; PropVariantInit(&pv); 320 | hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); 321 | pPropertyStore->Release(); 322 | if (FAILED(hr)) { 323 | pMMDevice->Release(); 324 | pMMDeviceCollection->Release(); 325 | printf("IPropertyStore::GetValue failed: hr = 0x%08x\n", hr); 326 | return hr; 327 | } 328 | 329 | if (VT_LPWSTR != pv.vt) { 330 | printf("PKEY_Device_FriendlyName variant type is %u - expected VT_LPWSTR", pv.vt); 331 | 332 | PropVariantClear(&pv); 333 | pMMDevice->Release(); 334 | pMMDeviceCollection->Release(); 335 | return E_UNEXPECTED; 336 | } 337 | 338 | // is it a match? 339 | if (0 == _wcsicmp(pv.pwszVal, szLongName)) { 340 | // did we already find it? 341 | if (NULL == *ppMMDevice) { 342 | *ppMMDevice = pMMDevice; 343 | pMMDevice->AddRef(); 344 | } else { 345 | printf("Found (at least) two devices named %ls\n", szLongName); 346 | PropVariantClear(&pv); 347 | pMMDevice->Release(); 348 | pMMDeviceCollection->Release(); 349 | return E_UNEXPECTED; 350 | } 351 | } 352 | 353 | pMMDevice->Release(); 354 | PropVariantClear(&pv); 355 | } 356 | pMMDeviceCollection->Release(); 357 | 358 | if (NULL == *ppMMDevice) { 359 | printf("Could not find a device named %ls\n", szLongName); 360 | return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); 361 | } 362 | 363 | return S_OK; 364 | } 365 | -------------------------------------------------------------------------------- /prefs.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://blogs.msdn.com/b/matthew_van_eerde/archive/2014/11/05/draining-the-wasapi-capture-buffer-fully.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | // prefs.h 12 | 13 | class CPrefs { 14 | public: 15 | IMMDevice *m_pMMDevice; 16 | bool m_bInt16; 17 | bool m_bMono; 18 | int m_iSampleRateDivisor; 19 | PWAVEFORMATEX m_pwfx; 20 | 21 | // set hr to S_FALSE to abort but return success 22 | CPrefs(int argc, LPCWSTR argv[], HRESULT &hr); 23 | ~CPrefs(); 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /simpleserver.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2013 for Windows Desktop 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "simpleserver", "simpleserver.vcxproj", "{4250A54B-E935-432B-BC91-C4D94B5BA81B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {4250A54B-E935-432B-BC91-C4D94B5BA81B}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {4250A54B-E935-432B-BC91-C4D94B5BA81B}.Debug|Win32.Build.0 = Debug|Win32 16 | {4250A54B-E935-432B-BC91-C4D94B5BA81B}.Release|Win32.ActiveCfg = Release|Win32 17 | {4250A54B-E935-432B-BC91-C4D94B5BA81B}.Release|Win32.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /simpleserver.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | Win32Proj 15 | {4250A54B-E935-432B-BC91-C4D94B5BA81B} 16 | 17 | 18 | 19 | Application 20 | true 21 | v143 22 | 23 | 24 | Application 25 | false 26 | v143 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | false 40 | 41 | 42 | false 43 | 44 | 45 | 46 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions);UNICODE;_UNICODE 47 | MultiThreadedDebugDLL 48 | Level3 49 | ProgramDatabase 50 | Disabled 51 | 52 | 53 | MachineX86 54 | true 55 | Console 56 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies);avrt.lib;winmm.lib 57 | 58 | 59 | 60 | 61 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);UNICODE;_UNICODE 62 | MultiThreadedDLL 63 | Level3 64 | ProgramDatabase 65 | 66 | 67 | MachineX86 68 | true 69 | Console 70 | true 71 | true 72 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies);avrt.lib;winmm.lib 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /simpletcpserver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://msdn.microsoft.com/en-us/library/windows/desktop/ms737593%28v=vs.85%29.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | #define WIN32_LEAN_AND_MEAN 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "simpletcpserver.h" 20 | 21 | // Need to link with Ws2_32.lib 22 | #pragma comment (lib, "Ws2_32.lib") 23 | // #pragma comment (lib, "Mswsock.lib") 24 | 25 | int SimpleTcpServer::setup() 26 | { 27 | WSADATA wsaData; 28 | int iResult; 29 | 30 | struct addrinfo *result = NULL; 31 | struct addrinfo hints; 32 | 33 | // Initialize Winsock 34 | iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 35 | if (iResult != 0) { 36 | printf("WSAStartup failed with error: %d\n", iResult); 37 | return 1; 38 | } 39 | 40 | ZeroMemory(&hints, sizeof(hints)); 41 | hints.ai_family = AF_INET; 42 | hints.ai_socktype = SOCK_STREAM; 43 | hints.ai_protocol = IPPROTO_TCP; 44 | hints.ai_flags = AI_PASSIVE; 45 | 46 | // Resolve the server address and port 47 | iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); 48 | if ( iResult != 0 ) { 49 | printf("getaddrinfo failed with error: %d\n", iResult); 50 | WSACleanup(); 51 | return 1; 52 | } 53 | 54 | // Create a SOCKET for connecting to server 55 | listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); 56 | if (listenSocket == INVALID_SOCKET) { 57 | printf("socket failed with error: %ld\n", WSAGetLastError()); 58 | freeaddrinfo(result); 59 | WSACleanup(); 60 | return 1; 61 | } 62 | 63 | // Setup the TCP listening socket 64 | iResult = bind( listenSocket, result->ai_addr, (int)result->ai_addrlen); 65 | if (iResult == SOCKET_ERROR) { 66 | printf("bind failed with error: %d\n", WSAGetLastError()); 67 | freeaddrinfo(result); 68 | closesocket(listenSocket); 69 | WSACleanup(); 70 | return 1; 71 | } 72 | 73 | freeaddrinfo(result); 74 | 75 | iResult = listen(listenSocket, SOMAXCONN); 76 | if (iResult == SOCKET_ERROR) { 77 | printf("listen failed with error: %d\n", WSAGetLastError()); 78 | closesocket(listenSocket); 79 | WSACleanup(); 80 | return 1; 81 | } 82 | 83 | dumpLocalIp(); 84 | 85 | return 0; 86 | } 87 | 88 | int SimpleTcpServer::waitForClient() 89 | { 90 | printf("%s ...\n", __FUNCTION__); 91 | // Accept a client socket 92 | clientSocket = accept(listenSocket, NULL, NULL); 93 | if (clientSocket == INVALID_SOCKET) { 94 | printf("accept failed with error: %d\n", WSAGetLastError()); 95 | closesocket(listenSocket); 96 | WSACleanup(); 97 | return 1; 98 | } 99 | 100 | // No longer need server socket 101 | closesocket(listenSocket); 102 | listenSocket = INVALID_SOCKET; 103 | 104 | // Dump socket info 105 | struct sockaddr peerInfo; 106 | int peerInfoSize = sizeof(peerInfo); 107 | int ret = getpeername(clientSocket, &peerInfo, &peerInfoSize); 108 | if (ret == 0) { 109 | WCHAR addrStr[256]; 110 | DWORD addrStrSize = sizeof(addrStr); 111 | ret = WSAAddressToString( 112 | &peerInfo, 113 | peerInfoSize, 114 | NULL, 115 | addrStr, 116 | &addrStrSize); 117 | if (ret == 0) { 118 | printf("Peer addr: %ls\n", addrStr); 119 | } 120 | else { 121 | printf("Error: WSAAddressToString %d\n", WSAGetLastError()); 122 | } 123 | } 124 | else { 125 | printf("Error: getpeername %d\n", WSAGetLastError()); 126 | } 127 | return 0; 128 | } 129 | 130 | #define PI 3.14159265 131 | 132 | // A utility to generate a sine wave. For testing. 133 | short getSineSample() 134 | { 135 | static int sineCounter = 0; 136 | double rad, result; 137 | 138 | rad = sineCounter++; 139 | 140 | // Assuming an output rate of 48khz, this should 141 | // generate a 1khz sine tone 142 | rad = fmod(rad, 48.0); 143 | rad = rad * 2.0 * PI; 144 | result = sin(rad); 145 | 146 | // sin's range is [-1,+1] scale that to something close to 147 | // the range of a short. 148 | return (short)(result * 32000.0); 149 | } 150 | 151 | int SimpleTcpServer::sendData(const char* buf, int length) 152 | { 153 | int iSendResult; 154 | short* bufferToSend; 155 | short* tmpBuffer; 156 | int samplesToSend = 0; 157 | int numCaptureSamples = length / 2; 158 | const short* captureSamples = (const short*)buf; 159 | 160 | // Super-simple stereo to mono conversion by averaging the left and right sample 161 | if (mono || sampleRateDivisor != 1) { 162 | if (length > bufferSize) { 163 | printf("Error: capture buffer too large: %d %d\n", length, bufferSize); 164 | return 0; 165 | } 166 | 167 | bufferToSend = (short*)scratchBuffer; 168 | tmpBuffer = (short*)scratchBuffer; 169 | 170 | int s1, s2; 171 | while (numCaptureSamples > 0) { 172 | numCaptureSamples -= 2; 173 | 174 | // TODO: eliminate all these 'if's in loop 175 | if (divisorCounter == 0) { 176 | if (mono) { 177 | s1 = (*captureSamples++); 178 | s2 = (*captureSamples++); 179 | // Average left and right. Assume 16 bits per sample, stereo 180 | *tmpBuffer++ = (short)((s1 + s2) / 2); 181 | samplesToSend++; 182 | } 183 | else { 184 | *tmpBuffer++ = *captureSamples++; 185 | *tmpBuffer++ = *captureSamples++; 186 | samplesToSend += 2; 187 | } 188 | if (sampleRateDivisor != 1) { 189 | divisorCounter++; 190 | } 191 | } 192 | else { 193 | // Skip 2 samples assuming stereo 194 | captureSamples += 2; 195 | divisorCounter++; 196 | if (divisorCounter == sampleRateDivisor) { 197 | divisorCounter = 0; 198 | } 199 | } 200 | } 201 | } 202 | else { 203 | bufferToSend = (short*)buf; 204 | samplesToSend = numCaptureSamples; 205 | } 206 | 207 | // Receive until the peer shuts down the connection 208 | iSendResult = send(clientSocket, (char*)bufferToSend, samplesToSend * 2, 0); 209 | if (iSendResult == SOCKET_ERROR) { 210 | printf("send failed with error: %d\n", WSAGetLastError()); 211 | closesocket(clientSocket); 212 | WSACleanup(); 213 | return 1; 214 | } 215 | 216 | return 0; 217 | } 218 | 219 | int SimpleTcpServer::shutdown() 220 | { 221 | if (clientSocket != INVALID_SOCKET) { 222 | // shutdown the connection since we're done 223 | int iResult = ::shutdown(clientSocket, SD_SEND); 224 | if (iResult == SOCKET_ERROR) { 225 | printf("shutdown failed with error: %d\n", WSAGetLastError()); 226 | closesocket(clientSocket); 227 | WSACleanup(); 228 | return 1; 229 | } 230 | closesocket(clientSocket); 231 | } 232 | 233 | if (listenSocket != INVALID_SOCKET) { 234 | closesocket(listenSocket); 235 | } 236 | 237 | // cleanup 238 | WSACleanup(); 239 | 240 | return 0; 241 | } 242 | 243 | void SimpleTcpServer::dumpLocalIp() 244 | { 245 | int i; 246 | struct hostent *remoteHost; 247 | struct in_addr addr; 248 | 249 | char **pAlias; 250 | char localHostName[80]; 251 | 252 | if (gethostname(localHostName, sizeof(localHostName)) == SOCKET_ERROR) { 253 | printf("Error: gethostname %d\n", WSAGetLastError()); 254 | return; 255 | } 256 | 257 | remoteHost = gethostbyname(localHostName); 258 | if (remoteHost == NULL) { 259 | printf("Error: gethostbyaddr %d\n", WSAGetLastError()); 260 | return; 261 | } 262 | 263 | printf("Official name: %s\n", remoteHost->h_name); 264 | for (i = 0, pAlias = remoteHost->h_aliases; *pAlias != 0; pAlias++) { 265 | printf("Alternate name #%d: %s\n", ++i, *pAlias); 266 | } 267 | if (remoteHost->h_addrtype == AF_INET) { 268 | i = 0; 269 | while (remoteHost->h_addr_list[i] != 0) { 270 | addr.s_addr = *(u_long *) remoteHost->h_addr_list[i++]; 271 | printf("\n\n\tIPv4 Address #%d: %s\n\n\n", i, inet_ntoa(addr)); 272 | } 273 | } 274 | else if (remoteHost->h_addrtype == AF_INET6) { 275 | printf("Remotehost is an IPv6 address\n"); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /simpletcpserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 kaytat 3 | 4 | Originally derived from the example posted here: 5 | http://msdn.microsoft.com/en-us/library/windows/desktop/ms737593%28v=vs.85%29.aspx 6 | 7 | Since there is no explict license, the Microsoft LPL applies: 8 | http://msdn.microsoft.com/en-us/cc300389.aspx 9 | */ 10 | 11 | #pragma once 12 | 13 | #define DEFAULT_PORT "12345" 14 | 15 | class SimpleTcpServer { 16 | public: 17 | SimpleTcpServer() : 18 | listenSocket(INVALID_SOCKET), 19 | clientSocket(INVALID_SOCKET), 20 | mono(true), 21 | sampleRateDivisor(1), 22 | divisorCounter(0), 23 | bufferSize(0), 24 | scratchBuffer(NULL) { 25 | } 26 | 27 | ~SimpleTcpServer() { 28 | if (scratchBuffer != NULL) { 29 | delete scratchBuffer; 30 | scratchBuffer = NULL; 31 | } 32 | } 33 | 34 | int setup(); 35 | int waitForClient(); 36 | void configure( 37 | bool mono, 38 | unsigned int sampleRateDivisor, 39 | int bufferSize) { 40 | this->mono = mono; 41 | if (sampleRateDivisor >= 1) { 42 | this->sampleRateDivisor = sampleRateDivisor; 43 | } 44 | else { 45 | this->sampleRateDivisor = 1; 46 | } 47 | this->divisorCounter = 0; 48 | this->bufferSize = bufferSize; 49 | this->scratchBuffer = new short[bufferSize / 2]; 50 | } 51 | int sendData(const char* buf, int length); 52 | int shutdown(); 53 | 54 | private: 55 | SOCKET listenSocket; 56 | SOCKET clientSocket; 57 | bool mono; 58 | unsigned int sampleRateDivisor; 59 | unsigned int divisorCounter; 60 | int bufferSize; 61 | short* scratchBuffer; 62 | void dumpLocalIp(); 63 | }; 64 | --------------------------------------------------------------------------------