#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-8 | 0
|