Source file: /~heha/hsn/zqr.zip/src/wasapi.cpp

#include "zqr.h"
//#include <wtypes.h>
#include <mmdeviceapi.h>
#include <Audioclient.h>
#include <tchar.h>
#include <stdio.h>	// _snprintf (bei MBCS statt Unicode)

// Bei mir kommt Müll heraus, wenn ich __uuidof() verwende: GUID = {000..000}
// So ist es kompatibel zum Compiler in alten MSVC6.
// Neueres kann ich nicht debuggen, weil MSVC2019 beim Erstellen abstürzt.
// Sehr wahrscheinlich hängt alles von der richtigen Reihenfolge der richtigen SDK-Include-Verzeichnisse ab.

const CLSID CLSID_MMDeviceEnumerator =	{0xBCDE0395,0xE52F,0x467C,{0x8E,0x3D,0xC4,0x57,0x92,0x91,0x69,0x2E}};	// __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator =	{0xA95664D2,0x9614,0x4F35,{0xA7,0x46,0xDE,0x8D,0xB6,0x36,0x17,0xE6}};	// __uuidof(IMMDeviceEnumerator);
const IID IID_IMMEndpoint =		{0x1BE09788,0x6894,0x4089,{0x85,0x86,0x9A,0x2A,0x6C,0x26,0x5A,0xC5}};	// __uuidof(IMMEndpoint);
const IID IID_IAudioClient =		{0x1CB9AD4C,0xDBFA,0x4c32,{0xB1,0x78,0xC2,0xF5,0x68,0xA7,0x03,0xB2}};	// __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient =	{0xC8ADBD64,0xE71E,0x48a0,{0xA4,0xDE,0x18,0x5C,0x39,0x5C,0xD3,0x17}};	// __uuidof(IAudioCaptureClient);
//const IID PKEY_Device_FriendlyName =	{0xa45c254e,0xdf1c,0x4efd,{0x80,0x20,0x67,0xd1,0x46,0xa8,0x50,0xe0}};
const PROPERTYKEY PKEY_Device_FriendlyName= {{0xa45c254e,0xdf1c,0x4efd,{0x80,0x20,0x67,0xd1,0x46,0xa8,0x50,0xe0}},14};    // DEVPROP_TYPE_STRING

#ifdef _DEBUG
void _cdecl debugmsg(const TCHAR*t,...) {
 va_list va;
 va_start(va,t);
 TCHAR s[256];
 _vsntprintf(s,elemof(s),t,va);
 va_end(va);
 OutputDebugString(s);
}
#endif
/*
static bool PrintEndpoint(UINT i,bool indir,TCHAR*text,void*) {
 debugmsg(TEXT("Endpoint %u: \"%s\" (%d)\n"),i,text,indir);
 return true;
}
*/
IMMDevice*GetEndpoint(char i) {
 IMMDevice*pDevice=0;
 IMMDeviceEnumerator*pEnumerator;
 if (!CoCreateInstance(
		CLSID_MMDeviceEnumerator, NULL,
		CLSCTX_ALL, IID_IMMDeviceEnumerator,
		(void**)&pEnumerator)) {
  IMMDeviceCollection*pCollection;
  if (!pEnumerator->EnumAudioEndpoints(
		eAll,DEVICE_STATE_ACTIVE,
		&pCollection)) {
   pCollection->Item(i,&pDevice);	// Get pointer to device number i.
   pCollection->Release();
  }
  pEnumerator->Release();
 }
 return pDevice;
}


static UINT EnumEndpoints(bool(*cb)(UINT,bool,TCHAR*,void*),void*cbd=0) {
 UINT count=0;
 IMMDeviceEnumerator*pEnumerator;
 if (!CoCreateInstance(
		CLSID_MMDeviceEnumerator, NULL,
		CLSCTX_ALL, IID_IMMDeviceEnumerator,
		(void**)&pEnumerator)) {
  IMMDeviceCollection*pCollection;
  if (!pEnumerator->EnumAudioEndpoints(
		eAll,DEVICE_STATE_ACTIVE,
		&pCollection)) {
   if (!pCollection->GetCount(&count))
   for (UINT i=0; i<count; i++) {
    bool cont=false;    // loop while <cont> is true
    IMMDevice*pDevice;
    if (!pCollection->Item(i,&pDevice)) {	// Get pointer to device number i.
     IMMEndpoint*pEndpoint;
     if (!pDevice->QueryInterface(IID_IMMEndpoint,(void**)&pEndpoint)) {
      EDataFlow dir;
      IPropertyStore*pProps;
      if (!pEndpoint->GetDataFlow(&dir)	// Get endpoint direction
      &&  !pDevice->OpenPropertyStore(STGM_READ,&pProps)) {
       PROPVARIANT varName;
       PropVariantInit(&varName);	// ZeroMemory()
	// Get the endpoint's friendly-name property.
       if (!pProps->GetValue(PKEY_Device_FriendlyName,&varName)) {
#ifdef UNICODE
	cont=cb(i,!!dir,varName.pwszVal,cbd);	// callback
#else
	char s[64];
	WideCharToMultiByte(CP_ACP,0,varName.pwszVal,-1,s,sizeof s,0,0);
        cont=cb(i,!!dir,s,cbd);      
#endif
       }
       PropVariantClear(&varName);
       pProps->Release();
      }
      pEndpoint->Release();
     }
     pDevice->Release();
    }
    if (!cont) break;
   }//for
   pCollection->Release();
  }
  pEnumerator->Release();
 }
 return count;
}

DWORD wasapi::RECORD::thread(void*p) {
 if (!CoInitializeEx(0,COINIT_MULTITHREADED)) {
  ((wasapi::RECORD*)p)->thread();
  CoUninitialize();
 }
 return 0;
}


void wasapi::RECORD::thread() {
 IMMDeviceEnumerator*pEnumerator;
 if (!CoCreateInstance(
           CLSID_MMDeviceEnumerator, NULL,
           CLSCTX_ALL, IID_IMMDeviceEnumerator,
           (void**)&pEnumerator)) {
  IMMDeviceCollection*pCollection;
  if (!pEnumerator->EnumAudioEndpoints(
		eAll,DEVICE_STATE_ACTIVE,
		&pCollection)) {
   UINT count;
   if (!pCollection->GetCount(&count)
   &&  idx<count) {
    IMMDevice*pDevice;
    if (!pCollection->Item(idx,&pDevice)) {	// Get pointer to device number idx
     IMMEndpoint*pEndpoint;
     if (!pDevice->QueryInterface(IID_IMMEndpoint,(void**)&pEndpoint)) {
      EDataFlow dir;
      if (!pEndpoint->GetDataFlow(&dir)) {	// Get endpoint direction
       IAudioClient *pAudioClient;
       if (!pDevice->Activate(
                    IID_IAudioClient, CLSCTX_ALL,
                    NULL, (void**)&pAudioClient)) {
	if (!pAudioClient->Initialize(
		AUDCLNT_SHAREMODE_SHARED,
		(dir==eRender?AUDCLNT_STREAMFLAGS_LOOPBACK:0)
		|AUDCLNT_STREAMFLAGS_EVENTCALLBACK
		|0x80000000	//AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
		|0x08000000,	//AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
		0,
		0,
		&wf->Format,
		NULL)) {
    // Get the size of the allocated buffer.
	 HANDLE ev=CreateEvent(0,0,0,0);
	 if (ev!=INVALID_HANDLE_VALUE) {
	  if (!pAudioClient->SetEventHandle(ev)) {
           UINT32 bufferFrameCount;
           IAudioCaptureClient*pCaptureClient;
           if (!pAudioClient->GetBufferSize(&bufferFrameCount)
           &&  !pAudioClient->GetService(
		IID_IAudioCaptureClient,
		(void**)&pCaptureClient)) {
	    debugmsg(T("record(%u):bufferFrameCount=%u\n"),idx,bufferFrameCount);
    // Notify the audio sink which format to use.
    // Calculate the actual duration of the allocated buffer.
            if (!pAudioClient->Start()) {  // Start recording.
    // Each loop fills about half of the shared buffer.
             bool done=false;
	     size_t fill=0;
// innerhalb der Datenschleife nicht mehr auf <wf> zugreifen,
// es gibt Ärger wenn von außen nBlockAlign geändert wird, während dieser Thread noch läuft
	     size_t nBlockAlign=wf->Format.nBlockAlign;
	     DWORD milliseconds=400;
             do{
        // Sleep for half the buffer duration.
	      HANDLE mo[2]={ev,hTerminate};
	      switch (WaitForMultipleObjects(2,mo,FALSE,milliseconds)) {
	       case 0: {
	        milliseconds=200;
	        UINT32 packetLength;
	        if (pCaptureClient->GetNextPacketSize(&packetLength)) {
	         debugmsg(T("record(%u):GetNextPacketSize() versagt!\n"),idx);
		 done=true;
	        }else while (packetLength) {
		 UINT32 numFramesAvailable;
	         char*pData;
	         DWORD flags;
            // Get the available data in the shared buffer.
	         if (pCaptureClient->GetBuffer(
			(BYTE**)&pData,
			&numFramesAvailable,
			&flags, NULL, NULL)) {
	          debugmsg(T("record(%u):GetBuffer() versagt!\n"),idx);
	// Das passiert wenn der Mikrofonzugriff mittendrin deaktiviert wird.
		  done=true;
		  break;
		 }
//	         debugmsg(T("record(%u):packetLength=%u, numFramesAvailable=%u, flags=%u, pData=%X\n"),idx,packetLength,numFramesAvailable,flags,pData);
// Auf ganze Datenblöcke in Wunschgröße umkopieren und - wenn voll - Callback aufrufen
		 for(size_t l=numFramesAvailable;l;) {
		  size_t l1=l;			// Kopier-Länge
		  size_t space=wantsize-fill;	// Freie Samples in <buffer>
		  if (l1>space) l1=space;	// Minimum beider
		  memcpy(buffer+(fill*nBlockAlign),pData,l1*nBlockAlign);	// kopieren in buffer
		  l-=l1;			// Restlänge (meistens 0)
		  fill+=l1;			// Füllstand
		  pData+=l1*nBlockAlign;	// Datenquellzeiger
		  if (!(space-=l1)) {		// Puffer komplett voll?
	           cb(buffer,DWORD(wantsize),cbd);	// Zum Threaderzeuger schicken (Callback)
		   fill=0;			// Puffer ist wieder leer, ggf. in der nächsten Schleifenrunde Restdaten dort einfügen
		  }
		 }		 

//	         if (flags & AUDCLNT_BUFFERFLAGS_SILENT) pData = 0;  // Tell CopyData to write silence.
// TODO: Ohne ständiges ReleaseBuffer auskommen
	         if (pCaptureClient->ReleaseBuffer(numFramesAvailable)) {done=true;break;}
	         if (pCaptureClient->GetNextPacketSize(&packetLength)) {done=true;break;}
		 else if (packetLength) debugmsg(T("record(%u):GetNextPacketSize() lieferte packetLength=%u\n"),idx,packetLength);
		}
	       }break;
	       case 1: {
	        debugmsg(T("record(%u):Signal hTerminate\n"),idx);
	        done=true; 
	       }break;
	       default: {	// wait_timeout
	        debugmsg(T("record(%u):wait_timeout\n"),idx);
		cb(0,0,cbd);	// Callback informieren
		milliseconds=INFINITE;	// nicht nochmal
	       }
	      }
             }while(!done);
             pAudioClient->Stop();  // Stop recording.
            }else debugmsg(T("record(%u):Start() versagt\n"),idx);
            pCaptureClient->Release();
	   }else debugmsg(T("record(%u):Kein CaptureClient\n"),idx);
	  }
	  CloseHandle(ev);
         }else debugmsg(T("record(%u):Kein Event\n"),idx);
        }else debugmsg(T("record(%u):Initialize() versagt - Mikrofonzugriff gesperrt?\n"),idx); 
	pAudioClient->Release();
       }else debugmsg(T("record(%u):Kein AudioClient\n"),idx);
      }
      pEndpoint->Release();
     }else debugmsg(T("record(%u):Kein Endpoint\n"),idx);
     pDevice->Release();
    }else debugmsg(T("record(%u):Kein Device\n"),idx);
   }
   pCollection->Release();
  }else debugmsg(T("record(%u):Keine Collection\n"),idx);
  pEnumerator->Release();
 }else debugmsg(T("record(%u):Kein Enumerator\n"),idx);
}

wasapi::RECORD::RECORD(UINT i, WAVEFORMATEXTENSIBLE*_wf, size_t ws, cb_t _cb, void*_cbd)
:idx(i),wf(_wf),wantsize(ws),cb(_cb),cbd(_cbd),hThread(0) {
}


bool wasapi::RECORD::start() {
 if (hThread) return false;		// Fehler: Thread läuft bereits
 buffer=new char[wantsize*wf->Format.nBlockAlign];
 if (!buffer) return false;
 hTerminate=CreateEvent(0,0,0,0);	// Einfaches, unsignalisiertes Event
 if (hTerminate==INVALID_HANDLE_VALUE) return false;
 hThread=CreateThread(0,0,thread,this,0,&threadid);
 if (hThread==INVALID_HANDLE_VALUE) {
  CloseHandle(hTerminate);		// Thread-Start-Problem
  hThread=0;
 }
 return !!hThread;
}

bool wasapi::RECORD::alive() {
 if (!hThread) return false;		// Fehler: Thread existiert nicht
 return !!WaitForSingleObject(hThread,0);// Thread beendet? Dann wäre hThread signalisiert und wait() liefert 0
}

bool wasapi::RECORD::stop() {
 if (!hThread) return false;		// Fehler: Thread existiert nicht
 SetEvent(hTerminate);	// dem Thread (in der Schleife) den Endewunsch signalisieren
 WaitForSingleObject(hThread,INFINITE);	// Warten bis Thread tatsächlich beendet wird (könnte auch bereits vorher, bspw. durch COM-Fehler, terminiert worden sein)
 CloseHandle(hThread); hThread=0;
 CloseHandle(hTerminate);
 delete[] buffer;
 return true;
}

wasapi::RECORD::~RECORD() {
 if (hThread) {
  debugmsg(T("Oops! Thread still running!\n"));
 }
}

extern "C" BYTE _fltused;
BYTE _fltused=1;

struct CFC{
 const HWND*h;
 const char*sel;
 COMBOBOXEXITEM*cbei;
};

bool ComboFillCallback(UINT i, bool indir, TCHAR*text, void*p) {
 CFC&cfc=*(CFC*)p;
 cfc.cbei->pszText=text;
 cfc.cbei->lParam=i;
 cfc.cbei->iImage=cfc.cbei->iSelectedImage=indir;
 for (int idx=0; idx<config.NIN; idx++) {
  int k=int(SendMessage(cfc.h[idx],CBEM_INSERTITEM,0,(LPARAM)cfc.cbei));
  if ((char)i==cfc.sel[idx]) ComboBox_SetCurSel(cfc.h[idx],k);
 }
 return true;
}

UINT wasapi::FillCombos(const HWND*h,const char*sel,COMBOBOXEXITEM*cbei) {
 CFC cfc={h,sel,cbei};
 return EnumEndpoints(ComboFillCallback,&cfc);
}
Detected encoding: UTF-80