windowcapture
исходный код / APO/SoundpadAPO.cpp

SoundpadAPO.cpp

193 строк · 12,268 байт · модуль APO
  1// SoundpadAPO — Audio Processing Object that mixes soundpad audio into microphone capture.
  2// Uses multiple inheritance for correct COM vtable layout per interface.
  3// Communicates with C# app via Global shared memory.
  4
  5#define WIN32_LEAN_AND_MEAN
  6#include <windows.h>
  7#include <unknwn.h>
  8
  9// ===== Interface GUIDs =====
 10static const GUID IID_IAPO    = {0xFD7F2B29,0x24D0,0x4B5C,{0xB1,0x77,0x59,0x2C,0x39,0xF9,0xCA,0x10}};
 11static const GUID IID_IAPRT   = {0x9E1D6A6D,0xDDBC,0x4E95,{0xA4,0xC7,0xAD,0x64,0xBA,0x37,0x84,0x6C}};
 12static const GUID IID_IAPCfg  = {0x0E5ED805,0xABA6,0x49C3,{0x8F,0x9A,0x2B,0x8C,0x88,0x9C,0x4F,0xA8}};
 13static const GUID IID_IASE    = {0x5FA00F27,0xADD6,0x499A,{0x8A,0x9D,0x6B,0x98,0x52,0x1F,0xA7,0x5B}};
 14static const GUID CLSID_SoundpadAPO = {0xA1B2C3D4,0x1234,0x5678,{0x9A,0xBC,0xDE,0xF0,0x12,0x34,0x56,0x78}};
 15
 16#define SHM_NAME L"Global\\WC_Soundpad_Buf"
 17#define SHM_HEADER_FLOATS 5
 18typedef long long HNSTIME;
 19
 20typedef struct { UINT_PTR pBuffer; UINT32 u32ValidFrameCount; UINT32 u32BufferFlags; UINT32 u32Signature; } APO_CONNECTION_PROPERTY;
 21typedef struct { UINT_PTR pFormat; UINT_PTR pBuffer; UINT32 u32MaxFrameCount; } APO_CONNECTION_DESCRIPTOR;
 22typedef struct { GUID clsidAPO; DWORD Flags; WCHAR szFriendlyName[256]; WCHAR szCopyrightInfo[256];
 23    DWORD u32MajorVersion, u32MinorVersion, u32MinInputConnections, u32MaxInputConnections,
 24    u32MinOutputConnections, u32MaxOutputConnections, u32MaxInstances, u32NumAPOInterfaces; GUID iidAPOInterfaceList[1]; } APO_REG_PROPERTIES;
 25
 26// ===== Abstract interfaces with correct vtable order =====
 27class __declspec(novtable) IAudioProcessingObject : public IUnknown {
 28public:
 29    virtual HRESULT STDMETHODCALLTYPE Reset() = 0;
 30    virtual HRESULT STDMETHODCALLTYPE GetLatency(HNSTIME*) = 0;
 31    virtual HRESULT STDMETHODCALLTYPE GetRegistrationProperties(APO_REG_PROPERTIES**) = 0;
 32    virtual HRESULT STDMETHODCALLTYPE Initialize(UINT32, BYTE*) = 0;
 33    virtual HRESULT STDMETHODCALLTYPE IsInputFormatSupported(IUnknown*, IUnknown*, IUnknown**) = 0;
 34    virtual HRESULT STDMETHODCALLTYPE IsOutputFormatSupported(IUnknown*, IUnknown*, IUnknown**) = 0;
 35    virtual HRESULT STDMETHODCALLTYPE GetInputChannelCount(UINT32*) = 0;
 36};
 37class __declspec(novtable) IAudioProcessingObjectRT : public IUnknown {
 38public:
 39    virtual void STDMETHODCALLTYPE APOProcess(UINT32, APO_CONNECTION_PROPERTY**, UINT32, APO_CONNECTION_PROPERTY**) = 0;
 40    virtual UINT32 STDMETHODCALLTYPE CalcInputFrames(UINT32) = 0;
 41    virtual UINT32 STDMETHODCALLTYPE CalcOutputFrames(UINT32) = 0;
 42};
 43class __declspec(novtable) IAudioProcessingObjectConfiguration : public IUnknown {
 44public:
 45    virtual HRESULT STDMETHODCALLTYPE LockForProcess(UINT32, APO_CONNECTION_DESCRIPTOR**, UINT32, APO_CONNECTION_DESCRIPTOR**) = 0;
 46    virtual HRESULT STDMETHODCALLTYPE UnlockForProcess() = 0;
 47};
 48class __declspec(novtable) IAudioSystemEffects : public IUnknown {};
 49
 50static HMODULE g_hModule = NULL;
 51static LONG g_cRefDll = 0;
 52static int g_apoLogCount = 0;
 53
 54static void ApoLog(const char* msg) {
 55    if (g_apoLogCount > 200) return; // limit log size
 56    g_apoLogCount++;
 57    // Build %LOCALAPPDATA%\WindowCapture\apo_native.log. Was hardcoded to C:\Users\Admin\...,
 58    // which broke APO logging for every other user account / machine (the APO runs inside
 59    // audiodg.exe under the logged-in user). Matches the C# side (SoundpadForm).
 60    WCHAR base[MAX_PATH];
 61    DWORD n = GetEnvironmentVariableW(L"LOCALAPPDATA", base, MAX_PATH);
 62    if (n == 0 || n + 32 >= MAX_PATH) return; // no/too-long LOCALAPPDATA — skip logging
 63    WCHAR dir[MAX_PATH];  wsprintfW(dir,  L"%s\\WindowCapture", base);
 64    CreateDirectoryW(dir, NULL); // ensure folder exists (harmless if it already does)
 65    WCHAR path[MAX_PATH]; wsprintfW(path, L"%s\\apo_native.log", dir);
 66    HANDLE hFile = CreateFileW(path,
 67        FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 68    if (hFile != INVALID_HANDLE_VALUE) {
 69        SYSTEMTIME st; GetLocalTime(&st);
 70        char buf[512];
 71        int len = wsprintfA(buf, "%02d:%02d:%02d.%03d  %s\r\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, msg);
 72        DWORD written; WriteFile(hFile, buf, len, &written, NULL);
 73        CloseHandle(hFile);
 74    }
 75}
 76
 77// ===== SoundpadAPO — multiple inheritance =====
 78class SoundpadAPO : public IAudioProcessingObject, public IAudioProcessingObjectRT,
 79    public IAudioProcessingObjectConfiguration, public IAudioSystemEffects {
 80    LONG m_cRef; UINT32 m_channels; HANDLE m_hMapping; float* m_shmPtr;
 81public:
 82    SoundpadAPO() : m_cRef(1), m_channels(1), m_hMapping(NULL), m_shmPtr(NULL) { InterlockedIncrement(&g_cRefDll); ApoLog("SoundpadAPO created"); }
 83    ~SoundpadAPO() { if (m_shmPtr) UnmapViewOfFile(m_shmPtr); if (m_hMapping) CloseHandle(m_hMapping); InterlockedDecrement(&g_cRefDll); }
 84
 85    // IUnknown — static_cast returns correct vtable pointer for each interface
 86    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) {
 87        if (!ppv) return E_POINTER;
 88        if (riid == IID_IUnknown) *ppv = static_cast<IAudioProcessingObject*>(this);
 89        else if (riid == IID_IAPO) *ppv = static_cast<IAudioProcessingObject*>(this);
 90        else if (riid == IID_IAPRT) *ppv = static_cast<IAudioProcessingObjectRT*>(this);
 91        else if (riid == IID_IAPCfg) *ppv = static_cast<IAudioProcessingObjectConfiguration*>(this);
 92        else if (riid == IID_IASE) *ppv = static_cast<IAudioSystemEffects*>(this);
 93        else { *ppv = NULL; return E_NOINTERFACE; }
 94        AddRef(); return S_OK;
 95    }
 96    ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&m_cRef); }
 97    ULONG STDMETHODCALLTYPE Release() { ULONG r = InterlockedDecrement(&m_cRef); if (r == 0) delete this; return r; }
 98
 99    // IAudioProcessingObject
100    HRESULT STDMETHODCALLTYPE Reset() { return S_OK; }
101    HRESULT STDMETHODCALLTYPE GetLatency(HNSTIME* p) { if (p) *p = 0; return S_OK; }
102    HRESULT STDMETHODCALLTYPE GetRegistrationProperties(APO_REG_PROPERTIES** pp) {
103        if (!pp) return E_POINTER;
104        auto* p = (APO_REG_PROPERTIES*)CoTaskMemAlloc(sizeof(APO_REG_PROPERTIES));
105        if (!p) return E_OUTOFMEMORY; ZeroMemory(p, sizeof(*p));
106        p->clsidAPO = CLSID_SoundpadAPO; p->Flags = 0xD; // must match registry Flags
107        wcscpy_s(p->szFriendlyName, L"Soundpad APO"); wcscpy_s(p->szCopyrightInfo, L"WindowCapture");
108        p->u32MajorVersion = 1; p->u32MinInputConnections = 1; p->u32MaxInputConnections = 1;
109        p->u32MinOutputConnections = 1; p->u32MaxOutputConnections = 1;
110        p->u32MaxInstances = 0xFFFFFFFF; p->u32NumAPOInterfaces = 1; p->iidAPOInterfaceList[0] = IID_IASE;
111        *pp = p; return S_OK;
112    }
113    HRESULT STDMETHODCALLTYPE Initialize(UINT32 cb, BYTE* pby) { ApoLog("Initialize called"); return S_OK; }
114    HRESULT STDMETHODCALLTYPE IsInputFormatSupported(IUnknown*, IUnknown* pReq, IUnknown** ppSupp) { if (ppSupp) { *ppSupp = pReq; if (pReq) pReq->AddRef(); } return S_OK; }
115    HRESULT STDMETHODCALLTYPE IsOutputFormatSupported(IUnknown*, IUnknown* pReq, IUnknown** ppSupp) { if (ppSupp) { *ppSupp = pReq; if (pReq) pReq->AddRef(); } return S_OK; }
116    HRESULT STDMETHODCALLTYPE GetInputChannelCount(UINT32* p) { if (p) *p = m_channels; return S_OK; }
117
118    // IAudioProcessingObjectConfiguration
119    HRESULT STDMETHODCALLTYPE LockForProcess(UINT32, APO_CONNECTION_DESCRIPTOR**, UINT32, APO_CONNECTION_DESCRIPTOR**) {
120        ApoLog("LockForProcess called");
121        if (!m_hMapping) {
122            m_hMapping = OpenFileMappingW(FILE_MAP_READ|FILE_MAP_WRITE, FALSE, SHM_NAME);
123            if (m_hMapping) {
124                m_shmPtr = (float*)MapViewOfFile(m_hMapping, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0);
125                ApoLog(m_shmPtr ? "SHM mapped OK" : "SHM MapViewOfFile FAILED");
126            } else {
127                char buf[128]; wsprintfA(buf, "SHM OpenFileMapping FAILED err=%d", GetLastError()); ApoLog(buf);
128            }
129        }
130        return S_OK;
131    }
132    HRESULT STDMETHODCALLTYPE UnlockForProcess() { return S_OK; }
133
134    // IAudioProcessingObjectRT
135    int m_processCount;
136    void STDMETHODCALLTYPE APOProcess(UINT32 numIn, APO_CONNECTION_PROPERTY** ppIn, UINT32 numOut, APO_CONNECTION_PROPERTY** ppOut) {
137        m_processCount++;
138        if (m_processCount <= 3) {
139            char buf[256]; wsprintfA(buf, "APOProcess call #%d numIn=%d numOut=%d shmPtr=%p vol=%.2f",
140                m_processCount, numIn, numOut, m_shmPtr, m_shmPtr ? m_shmPtr[0] : -1.0f);
141            ApoLog(buf);
142        }
143        if (numIn < 1 || numOut < 1 || !ppIn || !ppOut || !ppIn[0] || !ppOut[0]) return;
144        float* pIn = (float*)(ppIn[0]->pBuffer); float* pOut = (float*)(ppOut[0]->pBuffer);
145        UINT32 frames = ppIn[0]->u32ValidFrameCount; UINT32 total = frames * m_channels;
146        if (ppIn[0]->u32BufferFlags == 1) ZeroMemory(pOut, total * sizeof(float));
147        else if (pIn != pOut) CopyMemory(pOut, pIn, total * sizeof(float));
148        ppOut[0]->u32ValidFrameCount = frames; ppOut[0]->u32BufferFlags = ppIn[0]->u32BufferFlags;
149
150        if (!m_shmPtr && !m_hMapping) { m_hMapping = OpenFileMappingW(FILE_MAP_READ|FILE_MAP_WRITE, FALSE, SHM_NAME);
151            if (m_hMapping) m_shmPtr = (float*)MapViewOfFile(m_hMapping, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0); }
152        if (m_shmPtr && m_shmPtr[0] > 0.0001f) {
153            float vol = m_shmPtr[0]; int rp = (int)m_shmPtr[1]; int ts = (int)m_shmPtr[2];
154            float* smp = m_shmPtr + SHM_HEADER_FLOATS;
155            UINT32 mix = total; if (rp + (int)mix > ts) mix = (ts > rp) ? (UINT32)(ts - rp) : 0;
156            if (mix > 0) { for (UINT32 i = 0; i < mix; i++) pOut[i] += smp[rp + i] * vol;
157                m_shmPtr[1] = (float)(rp + mix); ppOut[0]->u32BufferFlags = 0; }
158            if (rp + (int)mix >= ts) m_shmPtr[0] = 0;
159        }
160    }
161    UINT32 STDMETHODCALLTYPE CalcInputFrames(UINT32 f) { return f; }
162    UINT32 STDMETHODCALLTYPE CalcOutputFrames(UINT32 f) { return f; }
163};
164
165// ===== Class Factory =====
166class SoundpadAPOFactory : public IClassFactory {
167    LONG m_cRef;
168public:
169    SoundpadAPOFactory() : m_cRef(1) { InterlockedIncrement(&g_cRefDll); }
170    ~SoundpadAPOFactory() { InterlockedDecrement(&g_cRefDll); }
171    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown || riid == IID_IClassFactory) { *ppv = this; AddRef(); return S_OK; } *ppv = NULL; return E_NOINTERFACE; }
172    ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&m_cRef); }
173    ULONG STDMETHODCALLTYPE Release() { ULONG r = InterlockedDecrement(&m_cRef); if (r == 0) delete this; return r; }
174    HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pOuter, REFIID riid, void** ppv) {
175        if (pOuter) return CLASS_E_NOAGGREGATION;
176        auto* apo = new SoundpadAPO(); HRESULT hr = apo->QueryInterface(riid, ppv); apo->Release(); return hr; }
177    HRESULT STDMETHODCALLTYPE LockServer(BOOL f) { if (f) InterlockedIncrement(&g_cRefDll); else InterlockedDecrement(&g_cRefDll); return S_OK; }
178};
179
180extern "C" {
181BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID) { if (r == DLL_PROCESS_ATTACH) { g_hModule = h; DisableThreadLibraryCalls(h); } return TRUE; }
182HRESULT STDAPICALLTYPE DllCanUnloadNow() { return (g_cRefDll == 0) ? S_OK : S_FALSE; }
183HRESULT STDAPICALLTYPE DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) {
184    if (rclsid == CLSID_SoundpadAPO) { auto* f = new SoundpadAPOFactory(); HRESULT hr = f->QueryInterface(riid, ppv); f->Release(); return hr; }
185    return CLASS_E_CLASSNOTAVAILABLE; }
186HRESULT STDAPICALLTYPE DllRegisterServer() {
187    WCHAR path[MAX_PATH]; GetModuleFileNameW(g_hModule, path, MAX_PATH); HKEY hKey;
188    if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\CLSID\\{A1B2C3D4-1234-5678-9ABC-DEF012345678}\\InprocServer32", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
189        RegSetValueExW(hKey, NULL, 0, REG_SZ, (BYTE*)path, (DWORD)((wcslen(path)+1)*sizeof(WCHAR)));
190        WCHAR t[] = L"Both"; RegSetValueExW(hKey, L"ThreadingModel", 0, REG_SZ, (BYTE*)t, sizeof(t)); RegCloseKey(hKey); }
191    return S_OK; }
192HRESULT STDAPICALLTYPE DllUnregisterServer() { RegDeleteTreeW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\CLSID\\{A1B2C3D4-1234-5678-9ABC-DEF012345678}"); return S_OK; }
193}