├── .gitignore ├── README.md ├── mono-to-stereo.sln └── mono-to-stereo ├── cleanup.h ├── common.h ├── guid.cpp ├── log.h ├── main.cpp ├── mono-to-stereo.cpp ├── mono-to-stereo.filters ├── mono-to-stereo.h ├── mono-to-stereo.vcxproj ├── prefs.cpp └── prefs.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mono-to-stereo 2 | 3 | **No longer maintained:** If you want further support you should look at [one of the forks](https://github.com/ToadKing/mono-to-stereo/forks). 4 | 5 | Takes a mono input and renders it as if it was an interleaved stereo input. Works on MS2109 capture 6 | devices where the audio input is a 96khz mono stream but in actuality is a 48khz stereo stream with 7 | the first left channel sample missing. In order to support this device better, accounting for this 8 | missing first sample is done by default. 9 | 10 | Original code based off of [Matthew van Eerde's loopback-capture](https://github.com/mvaneerde/blog/tree/master/loopback-capture) 11 | project. 12 | 13 | Run `mono-to-stereo.exe -?` for usage instructions. 14 | -------------------------------------------------------------------------------- /mono-to-stereo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30309.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mono-to-stereo", "mono-to-stereo\mono-to-stereo.vcxproj", "{4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Release|Win32 = Release|Win32 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Debug|Win32.Build.0 = Debug|Win32 18 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Debug|x64.ActiveCfg = Debug|x64 19 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Debug|x64.Build.0 = Debug|x64 20 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Release|Win32.ActiveCfg = Release|Win32 21 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Release|Win32.Build.0 = Release|Win32 22 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Release|x64.ActiveCfg = Release|x64 23 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /mono-to-stereo/cleanup.h: -------------------------------------------------------------------------------- 1 | // cleanup.h 2 | 3 | class AudioClientStopOnExit { 4 | public: 5 | AudioClientStopOnExit(IAudioClient *p) : m_p(p) {} 6 | ~AudioClientStopOnExit() { 7 | HRESULT hr = m_p->Stop(); 8 | if (FAILED(hr)) { 9 | ERR(L"IAudioClient::Stop failed: hr = 0x%08x", hr); 10 | } 11 | } 12 | 13 | private: 14 | IAudioClient *m_p; 15 | }; 16 | 17 | class AvRevertMmThreadCharacteristicsOnExit { 18 | public: 19 | AvRevertMmThreadCharacteristicsOnExit(HANDLE hTask) : m_hTask(hTask) {} 20 | ~AvRevertMmThreadCharacteristicsOnExit() { 21 | if (!AvRevertMmThreadCharacteristics(m_hTask)) { 22 | ERR(L"AvRevertMmThreadCharacteristics failed: last error is %d", GetLastError()); 23 | } 24 | } 25 | private: 26 | HANDLE m_hTask; 27 | }; 28 | 29 | class CancelWaitableTimerOnExit { 30 | public: 31 | CancelWaitableTimerOnExit(HANDLE h) : m_h(h) {} 32 | ~CancelWaitableTimerOnExit() { 33 | if (!CancelWaitableTimer(m_h)) { 34 | ERR(L"CancelWaitableTimer failed: last error is %d", GetLastError()); 35 | } 36 | } 37 | private: 38 | HANDLE m_h; 39 | }; 40 | 41 | class CloseHandleOnExit { 42 | public: 43 | CloseHandleOnExit(HANDLE h) : m_h(h) {} 44 | ~CloseHandleOnExit() { 45 | if (!CloseHandle(m_h)) { 46 | ERR(L"CloseHandle failed: last error is %d", GetLastError()); 47 | } 48 | } 49 | 50 | private: 51 | HANDLE m_h; 52 | }; 53 | 54 | class CoTaskMemFreeOnExit { 55 | public: 56 | CoTaskMemFreeOnExit(PVOID p) : m_p(p) {} 57 | ~CoTaskMemFreeOnExit() { 58 | CoTaskMemFree(m_p); 59 | } 60 | 61 | private: 62 | PVOID m_p; 63 | }; 64 | 65 | class CoUninitializeOnExit { 66 | public: 67 | ~CoUninitializeOnExit() { 68 | CoUninitialize(); 69 | } 70 | }; 71 | 72 | class PropVariantClearOnExit { 73 | public: 74 | PropVariantClearOnExit(PROPVARIANT *p) : m_p(p) {} 75 | ~PropVariantClearOnExit() { 76 | HRESULT hr = PropVariantClear(m_p); 77 | if (FAILED(hr)) { 78 | ERR(L"PropVariantClear failed: hr = 0x%08x", hr); 79 | } 80 | } 81 | 82 | private: 83 | PROPVARIANT *m_p; 84 | }; 85 | 86 | class ReleaseOnExit { 87 | public: 88 | ReleaseOnExit(IUnknown *p) : m_p(p) {} 89 | ~ReleaseOnExit() { 90 | m_p->Release(); 91 | } 92 | 93 | private: 94 | IUnknown *m_p; 95 | }; 96 | 97 | class SetEventOnExit { 98 | public: 99 | SetEventOnExit(HANDLE h) : m_h(h) {} 100 | ~SetEventOnExit() { 101 | if (!SetEvent(m_h)) { 102 | ERR(L"SetEvent failed: last error is %d", GetLastError()); 103 | } 104 | } 105 | private: 106 | HANDLE m_h; 107 | }; 108 | 109 | class WaitForSingleObjectOnExit { 110 | public: 111 | WaitForSingleObjectOnExit(HANDLE h) : m_h(h) {} 112 | ~WaitForSingleObjectOnExit() { 113 | DWORD dwWaitResult = WaitForSingleObject(m_h, INFINITE); 114 | if (WAIT_OBJECT_0 != dwWaitResult) { 115 | ERR(L"WaitForSingleObject returned unexpected result 0x%08x, last error is %d", dwWaitResult, GetLastError()); 116 | } 117 | } 118 | 119 | private: 120 | HANDLE m_h; 121 | }; 122 | -------------------------------------------------------------------------------- /mono-to-stereo/common.h: -------------------------------------------------------------------------------- 1 | // common.h 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "log.h" 12 | #include "cleanup.h" 13 | #include "prefs.h" 14 | #include "mono-to-stereo.h" 15 | -------------------------------------------------------------------------------- /mono-to-stereo/guid.cpp: -------------------------------------------------------------------------------- 1 | // guid.cpp 2 | 3 | #include 4 | #include "common.h" 5 | -------------------------------------------------------------------------------- /mono-to-stereo/log.h: -------------------------------------------------------------------------------- 1 | // log.h 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | static inline void LOG(wchar_t* fmt...) { 8 | va_list args; 9 | size_t len; 10 | 11 | va_start(args, fmt); 12 | len = _vscwprintf(fmt, args); 13 | 14 | std::vector buffer(len + 1); 15 | vswprintf_s(buffer.data(), len + 1, fmt, args); 16 | va_end(args); 17 | 18 | WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), buffer.data(), static_cast(len), nullptr, nullptr); 19 | WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), L"\n", 1, nullptr, nullptr); 20 | } 21 | 22 | #define ERR(...) LOG(L"Error: " __VA_ARGS__) 23 | -------------------------------------------------------------------------------- /mono-to-stereo/main.cpp: -------------------------------------------------------------------------------- 1 | // main.cpp 2 | 3 | #include "common.h" 4 | 5 | int do_everything(int argc, LPCWSTR argv[]); 6 | 7 | int _cdecl wmain(int argc, LPCWSTR argv[]) { 8 | HRESULT hr = S_OK; 9 | 10 | hr = CoInitialize(NULL); 11 | if (FAILED(hr)) { 12 | ERR(L"CoInitialize failed: hr = 0x%08x", hr); 13 | return -__LINE__; 14 | } 15 | CoUninitializeOnExit cuoe; 16 | 17 | return do_everything(argc, argv); 18 | } 19 | 20 | int do_everything(int argc, LPCWSTR argv[]) { 21 | HRESULT hr = S_OK; 22 | 23 | // parse command line 24 | CPrefs prefs(argc, argv, hr); 25 | if (FAILED(hr)) { 26 | ERR(L"CPrefs::CPrefs constructor failed: hr = 0x%08x", hr); 27 | return -__LINE__; 28 | } 29 | if (S_FALSE == hr) { 30 | // nothing to do 31 | return 0; 32 | } 33 | 34 | // create a "loopback capture has started" event 35 | HANDLE hStartedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 36 | if (NULL == hStartedEvent) { 37 | ERR(L"CreateEvent failed: last error is %u", GetLastError()); 38 | return -__LINE__; 39 | } 40 | CloseHandleOnExit closeStartedEvent(hStartedEvent); 41 | 42 | // create a "stop capturing now" event 43 | HANDLE hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 44 | if (NULL == hStopEvent) { 45 | ERR(L"CreateEvent failed: last error is %u", GetLastError()); 46 | return -__LINE__; 47 | } 48 | CloseHandleOnExit closeStopEvent(hStopEvent); 49 | 50 | // create arguments for loopback capture thread 51 | LoopbackCaptureThreadFunctionArguments threadArgs; 52 | threadArgs.hr = E_UNEXPECTED; // thread will overwrite this 53 | threadArgs.pMMInDevice = prefs.m_pMMInDevice; 54 | threadArgs.pMMOutDevice = prefs.m_pMMOutDevice; 55 | threadArgs.iBufferMs = prefs.m_iBufferMs; 56 | threadArgs.bSkipFirstSample = prefs.m_bSkipFirstSample; 57 | threadArgs.hStartedEvent = hStartedEvent; 58 | threadArgs.hStopEvent = hStopEvent; 59 | threadArgs.nFrames = 0; 60 | 61 | HANDLE hThread = CreateThread( 62 | NULL, 0, 63 | LoopbackCaptureThreadFunction, &threadArgs, 64 | 0, NULL 65 | ); 66 | if (NULL == hThread) { 67 | ERR(L"CreateThread failed: last error is %u", GetLastError()); 68 | return -__LINE__; 69 | } 70 | CloseHandleOnExit closeThread(hThread); 71 | 72 | // wait for either capture to start or the thread to end 73 | HANDLE waitArray[2] = { hStartedEvent, hThread }; 74 | DWORD dwWaitResult; 75 | dwWaitResult = WaitForMultipleObjects( 76 | ARRAYSIZE(waitArray), waitArray, 77 | FALSE, INFINITE 78 | ); 79 | 80 | if (WAIT_OBJECT_0 + 1 == dwWaitResult) { 81 | ERR(L"Thread aborted before starting to capture: hr = 0x%08x", threadArgs.hr); 82 | return -__LINE__; 83 | } 84 | 85 | if (WAIT_OBJECT_0 != dwWaitResult) { 86 | ERR(L"Unexpected WaitForMultipleObjects return value %u", dwWaitResult); 87 | return -__LINE__; 88 | } 89 | 90 | // at this point capture is running 91 | // wait for the user to press a key or for capture to error out 92 | { 93 | WaitForSingleObjectOnExit waitForThread(hThread); 94 | SetEventOnExit setStopEvent(hStopEvent); 95 | HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); 96 | 97 | if (INVALID_HANDLE_VALUE == hStdIn) { 98 | ERR(L"GetStdHandle returned INVALID_HANDLE_VALUE: last error is %u", GetLastError()); 99 | return -__LINE__; 100 | } 101 | 102 | LOG(L"%s", L"Press Enter to quit..."); 103 | 104 | FlushConsoleInputBuffer(hStdIn); 105 | 106 | HANDLE rhHandles[2] = { hThread, hStdIn }; 107 | 108 | bool bKeepWaiting = true; 109 | while (bKeepWaiting) { 110 | 111 | dwWaitResult = WaitForMultipleObjects(2, rhHandles, FALSE, INFINITE); 112 | 113 | switch (dwWaitResult) { 114 | 115 | case WAIT_OBJECT_0: // hThread 116 | ERR(L"%s", L"The thread terminated early - something bad happened"); 117 | bKeepWaiting = false; 118 | break; 119 | 120 | case WAIT_OBJECT_0 + 1: // hStdIn 121 | // see if any of them was an Enter key-up event 122 | INPUT_RECORD rInput[128]; 123 | DWORD nEvents; 124 | if (!ReadConsoleInput(hStdIn, rInput, ARRAYSIZE(rInput), &nEvents)) { 125 | ERR(L"ReadConsoleInput failed: last error is %u", GetLastError()); 126 | bKeepWaiting = false; 127 | } 128 | else { 129 | for (DWORD i = 0; i < nEvents; i++) { 130 | if ( 131 | KEY_EVENT == rInput[i].EventType && 132 | VK_RETURN == rInput[i].Event.KeyEvent.wVirtualKeyCode && 133 | !rInput[i].Event.KeyEvent.bKeyDown 134 | ) { 135 | LOG(L"%s", L"Stopping capture..."); 136 | bKeepWaiting = false; 137 | break; 138 | } 139 | } 140 | // if none of them were Enter key-up events, 141 | // continue waiting 142 | } 143 | break; 144 | 145 | default: 146 | ERR(L"WaitForMultipleObjects returned unexpected value 0x%08x", dwWaitResult); 147 | bKeepWaiting = false; 148 | break; 149 | } // switch 150 | } // while 151 | } // naked scope 152 | 153 | // at this point the thread is definitely finished 154 | 155 | DWORD exitCode; 156 | if (!GetExitCodeThread(hThread, &exitCode)) { 157 | ERR(L"GetExitCodeThread failed: last error is %u", GetLastError()); 158 | return -__LINE__; 159 | } 160 | 161 | if (0 != exitCode) { 162 | ERR(L"Capture thread exit code is %u; expected 0", exitCode); 163 | return -__LINE__; 164 | } 165 | 166 | if (S_OK != threadArgs.hr) { 167 | ERR(L"Thread HRESULT is 0x%08x", threadArgs.hr); 168 | return -__LINE__; 169 | } 170 | 171 | // let prefs' destructor call mmioClose 172 | 173 | return 0; 174 | } 175 | -------------------------------------------------------------------------------- /mono-to-stereo/mono-to-stereo.cpp: -------------------------------------------------------------------------------- 1 | // mono-to-stereo.cpp 2 | 3 | #include "common.h" 4 | #include 5 | 6 | HRESULT LoopbackCapture( 7 | IMMDevice* pMMInDevice, 8 | IMMDevice* pMMOutDevice, 9 | int iBufferMs, 10 | bool bSkipFirstSample, 11 | HANDLE hStartedEvent, 12 | HANDLE hStopEvent, 13 | PUINT32 pnFrames 14 | ); 15 | 16 | DWORD WINAPI LoopbackCaptureThreadFunction(LPVOID pContext) { 17 | LoopbackCaptureThreadFunctionArguments* pArgs = 18 | (LoopbackCaptureThreadFunctionArguments*)pContext; 19 | 20 | pArgs->hr = CoInitialize(NULL); 21 | if (FAILED(pArgs->hr)) { 22 | ERR(L"CoInitialize failed: hr = 0x%08x", pArgs->hr); 23 | return 0; 24 | } 25 | CoUninitializeOnExit cuoe; 26 | 27 | pArgs->hr = LoopbackCapture( 28 | pArgs->pMMInDevice, 29 | pArgs->pMMOutDevice, 30 | pArgs->iBufferMs, 31 | pArgs->bSkipFirstSample, 32 | pArgs->hStartedEvent, 33 | pArgs->hStopEvent, 34 | &pArgs->nFrames 35 | ); 36 | 37 | return 0; 38 | } 39 | 40 | HRESULT LoopbackCapture( 41 | IMMDevice* pMMInDevice, 42 | IMMDevice* pMMOutDevice, 43 | int iBufferMs, 44 | bool bSkipFirstSample, 45 | HANDLE hStartedEvent, 46 | HANDLE hStopEvent, 47 | PUINT32 pnFrames 48 | ) { 49 | HRESULT hr; 50 | 51 | // activate an IAudioClient 52 | IAudioClient* pAudioClient; 53 | hr = pMMInDevice->Activate( 54 | __uuidof(IAudioClient), 55 | CLSCTX_ALL, NULL, 56 | (void**)&pAudioClient 57 | ); 58 | if (FAILED(hr)) { 59 | ERR(L"IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr); 60 | return hr; 61 | } 62 | ReleaseOnExit releaseAudioClient(pAudioClient); 63 | 64 | // get the default device periodicity 65 | REFERENCE_TIME hnsDefaultDevicePeriod; 66 | hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL); 67 | if (FAILED(hr)) { 68 | ERR(L"IAudioClient::GetDevicePeriod failed: hr = 0x%08x", hr); 69 | return hr; 70 | } 71 | 72 | // get the default device format 73 | WAVEFORMATEX* pwfx; 74 | hr = pAudioClient->GetMixFormat(&pwfx); 75 | if (FAILED(hr)) { 76 | ERR(L"IAudioClient::GetMixFormat failed: hr = 0x%08x", hr); 77 | return hr; 78 | } 79 | CoTaskMemFreeOnExit freeMixFormat(pwfx); 80 | 81 | if (pwfx->nChannels != 1) { 82 | ERR(L"device doesn't have 1 channel, has %d", pwfx->nChannels); 83 | return E_UNEXPECTED; 84 | } 85 | 86 | if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { 87 | auto pwfxExtensible = reinterpret_cast(pwfx); 88 | if (!IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pwfxExtensible->SubFormat) && !IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pwfxExtensible->SubFormat)) { 89 | OLECHAR subFormatGUID[39]; 90 | StringFromGUID2(pwfxExtensible->SubFormat, subFormatGUID, _countof(subFormatGUID)); 91 | ERR(L"extensible input format not PCM, got %s", subFormatGUID); 92 | return E_UNEXPECTED; 93 | } 94 | } 95 | else if (pwfx->wFormatTag != WAVE_FORMAT_PCM && pwfx->wFormatTag != WAVE_FORMAT_IEEE_FLOAT) { 96 | ERR(L"input format not PCM, got %d", pwfx->wFormatTag); 97 | return E_UNEXPECTED; 98 | } 99 | 100 | pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; 101 | 102 | UINT32 nBlockAlign = pwfx->nBlockAlign; 103 | *pnFrames = 0; 104 | 105 | // call IAudioClient::Initialize 106 | hr = pAudioClient->Initialize( 107 | AUDCLNT_SHAREMODE_SHARED, 108 | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 109 | 0, 0, pwfx, 0 110 | ); 111 | if (FAILED(hr)) { 112 | ERR(L"IAudioClient::Initialize failed: hr = 0x%08x", hr); 113 | return hr; 114 | } 115 | 116 | // activate an IAudioCaptureClient 117 | IAudioCaptureClient* pAudioCaptureClient; 118 | hr = pAudioClient->GetService( 119 | __uuidof(IAudioCaptureClient), 120 | (void**)&pAudioCaptureClient 121 | ); 122 | if (FAILED(hr)) { 123 | ERR(L"IAudioClient::GetService(IAudioCaptureClient) failed: hr = 0x%08x", hr); 124 | return hr; 125 | } 126 | ReleaseOnExit releaseAudioCaptureClient(pAudioCaptureClient); 127 | 128 | // register with MMCSS 129 | DWORD nTaskIndex = 0; 130 | HANDLE hTask = AvSetMmThreadCharacteristics(L"Audio", &nTaskIndex); 131 | if (NULL == hTask) { 132 | DWORD dwErr = GetLastError(); 133 | ERR(L"AvSetMmThreadCharacteristics failed: last error = %u", dwErr); 134 | return HRESULT_FROM_WIN32(dwErr); 135 | } 136 | AvRevertMmThreadCharacteristicsOnExit unregisterMmcss(hTask); 137 | 138 | // create the event timer 139 | HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 140 | if (hEvent == NULL) 141 | { 142 | DWORD dwErr = GetLastError(); 143 | ERR(L"CreateEvent failed: last error = %u", dwErr); 144 | return HRESULT_FROM_WIN32(dwErr); 145 | } 146 | CloseHandleOnExit closeEvent(hEvent); 147 | 148 | pAudioClient->SetEventHandle(hEvent); 149 | 150 | // call IAudioClient::Start 151 | hr = pAudioClient->Start(); 152 | if (FAILED(hr)) { 153 | ERR(L"IAudioClient::Start failed: hr = 0x%08x", hr); 154 | return hr; 155 | } 156 | AudioClientStopOnExit stopAudioClient(pAudioClient); 157 | 158 | // update format for stereo conversion 159 | pwfx->nChannels *= 2; 160 | pwfx->nSamplesPerSec /= 2; 161 | pwfx->nBlockAlign *= 2; 162 | UINT32 OutputBlockAlign = pwfx->nBlockAlign; 163 | 164 | // set up output device 165 | IAudioClient* pAudioOutClient; 166 | hr = pMMOutDevice->Activate( 167 | __uuidof(IAudioClient), CLSCTX_ALL, 168 | NULL, (void**)&pAudioOutClient); 169 | if (FAILED(hr)) { 170 | ERR(L"IMMDevice::Activate(IAudioClient) failed (output): hr = 0x%08x", hr); 171 | return hr; 172 | } 173 | 174 | hr = pAudioOutClient->Initialize( 175 | AUDCLNT_SHAREMODE_SHARED, 176 | 0, 177 | static_cast(iBufferMs) * 10000, 178 | 0, 179 | pwfx, 180 | NULL); 181 | if (FAILED(hr)) { 182 | ERR(L"IAudioClient::Initialize failed (output): hr = 0x%08x", hr); 183 | return hr; 184 | } 185 | 186 | IAudioRenderClient* pRenderClient; 187 | hr = pAudioOutClient->GetService( 188 | __uuidof(IAudioRenderClient), 189 | (void**)&pRenderClient); 190 | 191 | // Get the actual size of the allocated buffer. 192 | UINT32 clientBufferFrameCount; 193 | hr = pAudioOutClient->GetBufferSize(&clientBufferFrameCount); 194 | if (FAILED(hr)) { 195 | ERR(L"IAudioClient::GetBufferSize failed (output): hr = 0x%08x", hr); 196 | return hr; 197 | } 198 | 199 | // Grab half the buffer for the initial fill operation. 200 | BYTE* tmp; 201 | hr = pRenderClient->GetBuffer(clientBufferFrameCount / 2, &tmp); 202 | if (FAILED(hr)) { 203 | ERR(L"IAudioClient::GetBuffer failed (output): hr = 0x%08x", hr); 204 | return hr; 205 | } 206 | 207 | hr = pRenderClient->ReleaseBuffer(clientBufferFrameCount / 2, AUDCLNT_BUFFERFLAGS_SILENT); 208 | if (FAILED(hr)) { 209 | ERR(L"IAudioCaptureClient::ReleaseBuffer failed (output): hr = 0x%08x", hr); 210 | return hr; 211 | } 212 | 213 | hr = pAudioOutClient->Start(); 214 | if (FAILED(hr)) { 215 | ERR(L"IAudioClient::Start failed (output): hr = 0x%08x", hr); 216 | return hr; 217 | } 218 | 219 | SetEvent(hStartedEvent); 220 | 221 | // loopback capture loop 222 | HANDLE waitArray[2] = { hStopEvent, hEvent }; 223 | DWORD dwWaitResult; 224 | 225 | bool bDone = false; 226 | 227 | std::vector lastSample; 228 | if (bSkipFirstSample) { 229 | lastSample.resize(nBlockAlign); 230 | } 231 | 232 | while (!bDone) { 233 | dwWaitResult = WaitForMultipleObjects( 234 | ARRAYSIZE(waitArray), waitArray, 235 | FALSE, INFINITE 236 | ); 237 | 238 | if (WAIT_OBJECT_0 == dwWaitResult) { 239 | LOG(L"Received stop event after %u frames", *pnFrames); 240 | bDone = true; 241 | continue; // exits loop 242 | } 243 | 244 | if (WAIT_OBJECT_0 + 1 != dwWaitResult) { 245 | ERR(L"Unexpected WaitForMultipleObjects return value %u after %u frames", dwWaitResult, *pnFrames); 246 | return E_UNEXPECTED; 247 | } 248 | 249 | for (;;) { 250 | // get the captured data 251 | BYTE* pData; 252 | BYTE* pOutData; 253 | UINT32 nNextPacketSize; 254 | UINT32 nNumFramesToRead; 255 | DWORD dwFlags; 256 | 257 | hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize); 258 | if (FAILED(hr)) { 259 | ERR(L"IAudioCaptureClient::GetNextPacketSize failed after %u frames: hr = 0x%08x", *pnFrames, hr); 260 | return hr; 261 | } 262 | 263 | if (nNextPacketSize == 0) { 264 | break; 265 | } 266 | 267 | hr = pAudioCaptureClient->GetBuffer( 268 | &pData, 269 | &nNumFramesToRead, 270 | &dwFlags, 271 | NULL, 272 | NULL 273 | ); 274 | if (FAILED(hr)) { 275 | ERR(L"IAudioCaptureClient::GetBuffer failed after %u frames: hr = 0x%08x", *pnFrames, hr); 276 | return hr; 277 | } 278 | 279 | if (nNextPacketSize != nNumFramesToRead) { 280 | ERR(L"GetNextPacketSize and GetBuffer values don't match (%u and %u) after %u frames", nNextPacketSize, nNumFramesToRead, *pnFrames); 281 | return E_UNEXPECTED; 282 | } 283 | 284 | if (AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY == dwFlags) { 285 | if (*pnFrames != 0) { 286 | LOG(L"Probably spurious glitch reported after %u frames", *pnFrames); 287 | } 288 | } 289 | else if (0 != dwFlags) { 290 | LOG(L"IAudioCaptureClient::GetBuffer set flags to 0x%08x after %u frames", dwFlags, *pnFrames); 291 | return E_UNEXPECTED; 292 | } 293 | 294 | if (nNumFramesToRead % 1 != 0) { 295 | ERR("frames to output is odd (%u), will miss the last sample after %u frames", nNumFramesToRead, *pnFrames); 296 | } 297 | 298 | UINT32 output_frames_to_write = nNumFramesToRead / 2; 299 | 300 | LONG lBytesToWrite = output_frames_to_write * OutputBlockAlign; 301 | 302 | for (;;) { 303 | hr = pRenderClient->GetBuffer(output_frames_to_write, &pOutData); 304 | if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) { 305 | ERR(L"%s", L"buffer overflow!"); 306 | Sleep(1); 307 | continue; 308 | } 309 | if (FAILED(hr)) { 310 | ERR(L"IAudioCaptureClient::GetBuffer failed (output) after %u frames: hr = 0x%08x", *pnFrames, hr); 311 | return hr; 312 | } 313 | break; 314 | } 315 | 316 | if (bSkipFirstSample) { 317 | memcpy(pOutData, lastSample.data(), nBlockAlign); 318 | memcpy(pOutData + nBlockAlign, pData, static_cast(lBytesToWrite) - nBlockAlign); 319 | memcpy(lastSample.data(), pData + lBytesToWrite - nBlockAlign, nBlockAlign); 320 | } 321 | else { 322 | memcpy(pOutData, pData, lBytesToWrite); 323 | } 324 | 325 | hr = pRenderClient->ReleaseBuffer(output_frames_to_write, 0); 326 | if (FAILED(hr)) { 327 | ERR(L"IAudioCaptureClient::ReleaseBuffer failed (output) after %u frames: hr = 0x%08x", *pnFrames, hr); 328 | return hr; 329 | } 330 | 331 | hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead); 332 | if (FAILED(hr)) { 333 | ERR(L"IAudioCaptureClient::ReleaseBuffer failed after %u frames: hr = 0x%08x", *pnFrames, hr); 334 | return hr; 335 | } 336 | 337 | *pnFrames += nNumFramesToRead; 338 | } 339 | } // capture loop 340 | 341 | return hr; 342 | } 343 | -------------------------------------------------------------------------------- /mono-to-stereo/mono-to-stereo.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | 32 | 33 | Header Files 34 | 35 | 36 | Header Files 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | -------------------------------------------------------------------------------- /mono-to-stereo/mono-to-stereo.h: -------------------------------------------------------------------------------- 1 | // mono-to-stereo.h 2 | 3 | // call CreateThread on this function 4 | // feed it the address of a LoopbackCaptureThreadFunctionArguments 5 | // it will capture via loopback from the IMMDevice 6 | // and dump output to the HMMIO 7 | // until the stop event is set 8 | // any failures will be propagated back via hr 9 | 10 | #define VERSION L"0.5" 11 | 12 | struct LoopbackCaptureThreadFunctionArguments { 13 | IMMDevice *pMMInDevice; 14 | IMMDevice *pMMOutDevice; 15 | int iBufferMs; 16 | bool bSkipFirstSample; 17 | HANDLE hStartedEvent; 18 | HANDLE hStopEvent; 19 | UINT32 nFrames; 20 | HRESULT hr; 21 | }; 22 | 23 | DWORD WINAPI LoopbackCaptureThreadFunction(LPVOID pContext); 24 | -------------------------------------------------------------------------------- /mono-to-stereo/mono-to-stereo.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {4463F7EB-16DC-4C5E-A9CB-9B4E5A18E2E9} 23 | mono-to-stereo 24 | 10.0 25 | mono-to-stereo 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | true 37 | v142 38 | MultiByte 39 | 40 | 41 | Application 42 | false 43 | v142 44 | true 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Level4 74 | Disabled 75 | true 76 | true 77 | UNICODE;_UNICODE;%(PreprocessorDefinitions) 78 | 79 | 80 | true 81 | Console 82 | avrt.lib;ole32.lib;winmm.lib 83 | 84 | 85 | 86 | 87 | Level4 88 | Disabled 89 | true 90 | true 91 | UNICODE;_UNICODE;%(PreprocessorDefinitions) 92 | 93 | 94 | true 95 | Console 96 | avrt.lib;ole32.lib;winmm.lib 97 | 98 | 99 | 100 | 101 | Level4 102 | MaxSpeed 103 | true 104 | true 105 | true 106 | true 107 | UNICODE;_UNICODE;%(PreprocessorDefinitions) 108 | MultiThreaded 109 | 110 | 111 | true 112 | true 113 | true 114 | Console 115 | avrt.lib;ole32.lib;winmm.lib 116 | 117 | 118 | 119 | 120 | Level4 121 | MaxSpeed 122 | true 123 | true 124 | true 125 | true 126 | UNICODE;_UNICODE;%(PreprocessorDefinitions) 127 | MultiThreaded 128 | 129 | 130 | true 131 | true 132 | true 133 | Console 134 | avrt.lib;ole32.lib;winmm.lib 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /mono-to-stereo/prefs.cpp: -------------------------------------------------------------------------------- 1 | // prefs.cpp 2 | 3 | #include "common.h" 4 | 5 | #define DEFAULT_BUFFER_MS 64 6 | 7 | void usage(LPCWSTR exe); 8 | HRESULT get_default_device(IMMDevice **ppMMDevice); 9 | HRESULT list_devices(); 10 | HRESULT list_devices_with_direction(EDataFlow direction, const wchar_t *direction_label); 11 | HRESULT get_specific_device(LPCWSTR szLongName, EDataFlow direction, IMMDevice **ppMMDevice); 12 | 13 | void usage(LPCWSTR exe) { 14 | LOG( 15 | L"mono-to-stereo v%s\n" 16 | L"\n" 17 | L"%ls -?\n" 18 | L"%ls --list-devices\n" 19 | L"%ls [--in-device \"Device long name\"] [--out-device \"Device long name\"] [--buffer-size 128] [--no-skip-first-sample]\n" 20 | L"\n" 21 | L" -? prints this message.\n" 22 | L" --list-devices displays the long names of all active capture and render devices.\n" 23 | L" --in-device captures from the specified device to capture (\"Digital Audio Interface (USB Digital Audio)\" if omitted)\n" 24 | L" --out-device device to stream stereo audio to (default if omitted)\n" 25 | L" --buffer-size set the size of the audio buffer, in milliseconds (default to %dms)\n" 26 | L" --no-skip-first-sample do not skip the first channel sample", 27 | VERSION, exe, exe, exe, DEFAULT_BUFFER_MS 28 | ); 29 | } 30 | 31 | CPrefs::CPrefs(int argc, LPCWSTR argv[], HRESULT &hr) 32 | : m_pMMInDevice(NULL) 33 | , m_pMMOutDevice(NULL) 34 | , m_iBufferMs(DEFAULT_BUFFER_MS) 35 | , m_bSkipFirstSample(true) 36 | { 37 | switch (argc) { 38 | case 2: 39 | if (0 == _wcsicmp(argv[1], L"-?") || 0 == _wcsicmp(argv[1], L"/?")) { 40 | // print usage but don't actually capture 41 | hr = S_FALSE; 42 | usage(argv[0]); 43 | return; 44 | } 45 | else if (0 == _wcsicmp(argv[1], L"--list-devices")) { 46 | // list the devices but don't actually capture 47 | hr = list_devices(); 48 | 49 | // don't actually play 50 | if (S_OK == hr) { 51 | hr = S_FALSE; 52 | return; 53 | } 54 | } 55 | // intentional fallthrough 56 | 57 | default: 58 | // loop through arguments and parse them 59 | for (int i = 1; i < argc; i++) { 60 | 61 | // --in-device 62 | if (0 == _wcsicmp(argv[i], L"--in-device")) { 63 | if (NULL != m_pMMInDevice) { 64 | ERR(L"%s", L"Only one --device switch is allowed"); 65 | hr = E_INVALIDARG; 66 | return; 67 | } 68 | 69 | if (i++ == argc) { 70 | ERR(L"%s", L"--device switch requires an argument"); 71 | hr = E_INVALIDARG; 72 | return; 73 | } 74 | 75 | hr = get_specific_device(argv[i], eCapture, &m_pMMInDevice); 76 | if (FAILED(hr)) { 77 | return; 78 | } 79 | 80 | continue; 81 | } 82 | 83 | // --out-device 84 | if (0 == _wcsicmp(argv[i], L"--out-device")) { 85 | if (NULL != m_pMMOutDevice) { 86 | ERR(L"%s", L"Only one --device switch is allowed"); 87 | hr = E_INVALIDARG; 88 | return; 89 | } 90 | 91 | if (i++ == argc) { 92 | ERR(L"%s", L"--device switch requires an argument"); 93 | hr = E_INVALIDARG; 94 | return; 95 | } 96 | 97 | hr = get_specific_device(argv[i], eRender, &m_pMMOutDevice); 98 | if (FAILED(hr)) { 99 | return; 100 | } 101 | 102 | continue; 103 | } 104 | 105 | // --buffer-size 106 | if (0 == _wcsicmp(argv[i], L"--buffer-size")) { 107 | if (i++ == argc) { 108 | ERR(L"%s", L"--buffer-size switch requires an argument"); 109 | hr = E_INVALIDARG; 110 | return; 111 | } 112 | 113 | m_iBufferMs = _wtoi(argv[i]); 114 | if (m_iBufferMs <= 0) { 115 | ERR(L"%s", L"invalid buffer size given"); 116 | hr = E_INVALIDARG; 117 | return; 118 | } 119 | 120 | continue; 121 | } 122 | 123 | // --no-skip-first-sample 124 | if (0 == _wcsicmp(argv[i], L"--no-skip-first-sample")) { 125 | m_bSkipFirstSample = false; 126 | continue; 127 | } 128 | 129 | ERR(L"Invalid argument %ls", argv[i]); 130 | hr = E_INVALIDARG; 131 | return; 132 | } 133 | 134 | // open default device if not specified 135 | if (NULL == m_pMMInDevice) { 136 | hr = get_specific_device(L"Digital Audio Interface (USB Digital Audio)", eCapture, &m_pMMInDevice); 137 | if (FAILED(hr)) { 138 | return; 139 | } 140 | } 141 | 142 | // open default device if not specified 143 | if (NULL == m_pMMOutDevice) { 144 | hr = get_default_device(&m_pMMOutDevice); 145 | if (FAILED(hr)) { 146 | return; 147 | } 148 | } 149 | } 150 | } 151 | 152 | CPrefs::~CPrefs() { 153 | if (NULL != m_pMMInDevice) { 154 | m_pMMInDevice->Release(); 155 | } 156 | 157 | if (NULL != m_pMMOutDevice) { 158 | m_pMMOutDevice->Release(); 159 | } 160 | } 161 | 162 | HRESULT get_default_device(IMMDevice **ppMMDevice) { 163 | HRESULT hr = S_OK; 164 | IMMDeviceEnumerator *pMMDeviceEnumerator; 165 | 166 | // activate a device enumerator 167 | hr = CoCreateInstance( 168 | __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 169 | __uuidof(IMMDeviceEnumerator), 170 | (void**)&pMMDeviceEnumerator 171 | ); 172 | if (FAILED(hr)) { 173 | ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr); 174 | return hr; 175 | } 176 | ReleaseOnExit releaseMMDeviceEnumerator(pMMDeviceEnumerator); 177 | 178 | // get the default render endpoint 179 | hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, ppMMDevice); 180 | if (FAILED(hr)) { 181 | ERR(L"IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x", hr); 182 | return hr; 183 | } 184 | 185 | return S_OK; 186 | } 187 | 188 | HRESULT list_devices() { 189 | HRESULT hr; 190 | 191 | hr = list_devices_with_direction(eRender, L"render"); 192 | if (FAILED(hr)) { 193 | return hr; 194 | } 195 | 196 | LOG(L""); 197 | 198 | hr = list_devices_with_direction(eCapture, L"capture"); 199 | if (FAILED(hr)) { 200 | return hr; 201 | } 202 | 203 | return hr; 204 | } 205 | 206 | HRESULT list_devices_with_direction(EDataFlow direction, const wchar_t *direction_label) { 207 | HRESULT hr = S_OK; 208 | 209 | // get an enumerator 210 | IMMDeviceEnumerator *pMMDeviceEnumerator; 211 | 212 | hr = CoCreateInstance( 213 | __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 214 | __uuidof(IMMDeviceEnumerator), 215 | (void**)&pMMDeviceEnumerator 216 | ); 217 | if (FAILED(hr)) { 218 | ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr); 219 | return hr; 220 | } 221 | ReleaseOnExit releaseMMDeviceEnumerator(pMMDeviceEnumerator); 222 | 223 | IMMDeviceCollection *pMMDeviceCollection; 224 | 225 | // get all the active render endpoints 226 | hr = pMMDeviceEnumerator->EnumAudioEndpoints( 227 | direction, DEVICE_STATE_ACTIVE, &pMMDeviceCollection 228 | ); 229 | if (FAILED(hr)) { 230 | ERR(L"IMMDeviceEnumerator::EnumAudioEndpoints failed: hr = 0x%08x", hr); 231 | return hr; 232 | } 233 | ReleaseOnExit releaseMMDeviceCollection(pMMDeviceCollection); 234 | 235 | UINT count; 236 | hr = pMMDeviceCollection->GetCount(&count); 237 | if (FAILED(hr)) { 238 | ERR(L"IMMDeviceCollection::GetCount failed: hr = 0x%08x", hr); 239 | return hr; 240 | } 241 | LOG(L"Active %s endpoints found: %u", direction_label, count); 242 | 243 | for (UINT i = 0; i < count; i++) { 244 | IMMDevice *pMMDevice; 245 | 246 | // get the "n"th device 247 | hr = pMMDeviceCollection->Item(i, &pMMDevice); 248 | if (FAILED(hr)) { 249 | ERR(L"IMMDeviceCollection::Item failed: hr = 0x%08x", hr); 250 | return hr; 251 | } 252 | ReleaseOnExit releaseMMDevice(pMMDevice); 253 | 254 | // open the property store on that device 255 | IPropertyStore *pPropertyStore; 256 | hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore); 257 | if (FAILED(hr)) { 258 | ERR(L"IMMDevice::OpenPropertyStore failed: hr = 0x%08x", hr); 259 | return hr; 260 | } 261 | ReleaseOnExit releasePropertyStore(pPropertyStore); 262 | 263 | // get the long name property 264 | PROPVARIANT pv; PropVariantInit(&pv); 265 | hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); 266 | if (FAILED(hr)) { 267 | ERR(L"IPropertyStore::GetValue failed: hr = 0x%08x", hr); 268 | return hr; 269 | } 270 | PropVariantClearOnExit clearPv(&pv); 271 | 272 | if (VT_LPWSTR != pv.vt) { 273 | ERR(L"PKEY_Device_FriendlyName variant type is %u - expected VT_LPWSTR", pv.vt); 274 | return E_UNEXPECTED; 275 | } 276 | 277 | LOG(L" %ls", pv.pwszVal); 278 | } 279 | 280 | return S_OK; 281 | } 282 | 283 | HRESULT get_specific_device(LPCWSTR szLongName, EDataFlow direction, IMMDevice **ppMMDevice) { 284 | HRESULT hr = S_OK; 285 | 286 | *ppMMDevice = NULL; 287 | 288 | // get an enumerator 289 | IMMDeviceEnumerator *pMMDeviceEnumerator; 290 | 291 | hr = CoCreateInstance( 292 | __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 293 | __uuidof(IMMDeviceEnumerator), 294 | (void**)&pMMDeviceEnumerator 295 | ); 296 | if (FAILED(hr)) { 297 | ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr); 298 | return hr; 299 | } 300 | ReleaseOnExit releaseMMDeviceEnumerator(pMMDeviceEnumerator); 301 | 302 | IMMDeviceCollection *pMMDeviceCollection; 303 | 304 | // get all the active render endpoints 305 | hr = pMMDeviceEnumerator->EnumAudioEndpoints( 306 | direction, DEVICE_STATE_ACTIVE, &pMMDeviceCollection 307 | ); 308 | if (FAILED(hr)) { 309 | ERR(L"IMMDeviceEnumerator::EnumAudioEndpoints failed: hr = 0x%08x", hr); 310 | return hr; 311 | } 312 | ReleaseOnExit releaseMMDeviceCollection(pMMDeviceCollection); 313 | 314 | UINT count; 315 | hr = pMMDeviceCollection->GetCount(&count); 316 | if (FAILED(hr)) { 317 | ERR(L"IMMDeviceCollection::GetCount failed: hr = 0x%08x", hr); 318 | return hr; 319 | } 320 | 321 | for (UINT i = 0; i < count; i++) { 322 | IMMDevice *pMMDevice; 323 | 324 | // get the "n"th device 325 | hr = pMMDeviceCollection->Item(i, &pMMDevice); 326 | if (FAILED(hr)) { 327 | ERR(L"IMMDeviceCollection::Item failed: hr = 0x%08x", hr); 328 | return hr; 329 | } 330 | ReleaseOnExit releaseMMDevice(pMMDevice); 331 | 332 | // open the property store on that device 333 | IPropertyStore *pPropertyStore; 334 | hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore); 335 | if (FAILED(hr)) { 336 | ERR(L"IMMDevice::OpenPropertyStore failed: hr = 0x%08x", hr); 337 | return hr; 338 | } 339 | ReleaseOnExit releasePropertyStore(pPropertyStore); 340 | 341 | // get the long name property 342 | PROPVARIANT pv; PropVariantInit(&pv); 343 | hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); 344 | if (FAILED(hr)) { 345 | ERR(L"IPropertyStore::GetValue failed: hr = 0x%08x", hr); 346 | return hr; 347 | } 348 | PropVariantClearOnExit clearPv(&pv); 349 | 350 | if (VT_LPWSTR != pv.vt) { 351 | ERR(L"PKEY_Device_FriendlyName variant type is %u - expected VT_LPWSTR", pv.vt); 352 | return E_UNEXPECTED; 353 | } 354 | 355 | // is it a match? 356 | if (0 == _wcsicmp(pv.pwszVal, szLongName)) { 357 | // did we already find it? 358 | if (NULL == *ppMMDevice) { 359 | *ppMMDevice = pMMDevice; 360 | pMMDevice->AddRef(); 361 | } 362 | else { 363 | ERR(L"Found (at least) two devices named %ls", szLongName); 364 | return E_UNEXPECTED; 365 | } 366 | } 367 | } 368 | 369 | if (NULL == *ppMMDevice) { 370 | ERR(L"Could not find a device named %ls", szLongName); 371 | return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); 372 | } 373 | 374 | return S_OK; 375 | } 376 | -------------------------------------------------------------------------------- /mono-to-stereo/prefs.h: -------------------------------------------------------------------------------- 1 | // prefs.h 2 | 3 | class CPrefs { 4 | public: 5 | IMMDevice *m_pMMInDevice; 6 | IMMDevice *m_pMMOutDevice; 7 | int m_iBufferMs; 8 | bool m_bSkipFirstSample; 9 | 10 | // set hr to S_FALSE to abort but return success 11 | CPrefs(int argc, LPCWSTR argv[], HRESULT &hr); 12 | ~CPrefs(); 13 | 14 | }; 15 | --------------------------------------------------------------------------------