COM - IAudioClient - Loopbackstream ablauschen



  • Hallo zusammen,

    ich versuche gerade mit den COM-Interfaces IAudio(Capture)Client das abzuhören, was Windows an die Lautsprecher schickt.
    Das Ganze soll ja ab Vista funktionieren, zumindest hab ich dafür eine mehr oder weniger grobe Anleitung im MSDN gefunden:

    http://msdn.microsoft.com/en-us/library/ms679146
    bzw.
    http://msdn.microsoft.com/en-us/library/ms678709

    Ich muss zugeben, meine Erfahrungen mit C++ sind noch stark ausbaufähig, aber ich bin willens daran zu arbeiten und bringe Erfahrung aus Delphi oder Java mit.
    Mir ging es nun erst einmal darum, ein funktionstüchtiges Sample zu erstellen, um zu sehen, ob und wie das überhaupt tut.
    Also hab ich diesen Code also erstmal genommen und versucht ihn so zu in eine Klasse zu stopfen:

    #pragma once
    #include <Audioclient.h>
    #include <mmdeviceapi.h>
    
    class audioListener
    {
    private:
    	IAudioClient *pAudioClient;
    	IAudioCaptureClient *pCaptureClient;
    	WAVEFORMATEXTENSIBLE *WaveFormat;
    public:
    	audioListener(void);
    	~audioListener(void);
    
    	float getOsc();
    };
    
    #include "StdAfx.h"
    #include "audioListener.h"
    
    template <class T>
    void freeAndNull(T t) {
    	if (t != NULL) {
    		t->Release();
    		t = NULL;
    	}
    }
    
    void error(HRESULT hr) {
    	if (FAILED(hr)) {
    		if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS) {
    			hr = HRESULT_CODE(hr);
    		}
    
    		using namespace std;
    		LPTSTR errorMsg = NULL;
    
    		if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, 
    						NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    						(LPTSTR) &errorMsg, 1024, NULL) != 0) {			
    		} else {
    			errorMsg = new TCHAR[255];
    			_stprintf(errorMsg, _T("Error-Code: %#x"), hr);
    		}
    
    		cerr << errorMsg << std::endl;		
    
    		LocalFree(errorMsg);
    	}
    }
    
    audioListener::audioListener(void)
    {
    	IMMDeviceEnumerator *pEnumerator = NULL;
    	IMMDevice *pDevice = NULL;
    
    	// initialize that COM stuff
    	CoInitializeEx(NULL, COINIT_MULTITHREADED);
    
    	// create the MM device enumerator
    	error(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**) &pEnumerator));
    
    	// obtain the standard MM device audio endpoint
    	error(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice));
    
    	// for that device, activate an audio client
    	error(pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**) &pAudioClient));
    
    	// find the used format in that stream
    	WAVEFORMATEX* wfe = NULL;
    	error(pAudioClient->GetMixFormat(&wfe));
    	WaveFormat = reinterpret_cast<WAVEFORMATEXTENSIBLE*>(wfe);
    	// now initialize that thing
    
    	error(pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 10000, 0, wfe, NULL));
    
    	// give us the capture service
    	error(pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**) &pCaptureClient));
    
    	// and start
    	error(pAudioClient->Start());
    
    	// can I do this already?
    //	freeAndNull(pDevice);
    //	freeAndNull(pEnumerator);
    }
    
    audioListener::~audioListener(void)
    {
    	pAudioClient->Stop();
    	CoTaskMemFree(WaveFormat);
    	freeAndNull(pAudioClient);
    	freeAndNull(pCaptureClient);
    }
    
    float audioListener::getOsc() {
    	BYTE *pData;
    	UINT32 availableFrames;
    	DWORD flags;
    
    	// when nothing is there, don't read it
    	error(pCaptureClient->GetNextPacketSize(&availableFrames));
    	if (availableFrames == 0) {
    		return -1.0f;
    	}
    
    	// copy buffer
    	try {
    		error(pCaptureClient->GetBuffer(&pData, &availableFrames, &flags, NULL, NULL));
    	} catch (...) {
    		std::cerr << "Error during buffer copy" << std::endl;
    	}
    
    	if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
    		// silence
    		return -1.0f;
    	} else {
    		// find the maximum in the buffer
    		float result = 0.0f;
    		float *f = (float*) pData;
    
    		for (unsigned int i = 0; i < availableFrames; i++) {
    			result = max(result, *f++);
    		}
    		return result;
    	}
    
    }
    

    Da tun sich jetzt aber zwei Probleme auf:
    1. getOsc() gibt anscheinend immer 0.0 zurück. Auch wenn ich Sachen an die Lautsprecher abspiele, die nicht DRM sind und auch von anderen Tools aufgenommen werden können.
    2. Wenn ich getOsc() in einem Timer alle 50ms oder so calle, endet das immer in einer Exception in der Ausgabe, pCaptureClient->GetBuffer gibt dann 0x88890007 AUDCLNT_E_OUT_OF_ORDER zurück.

    Positiv immerhin, das WaveFormat hat schonmal 6 Kanäle, d.h. der Loopbackstream statt einem Stereomix scheint erstellt zu werden. Daher habe ich die Vermutung ich mache irgendwas Grundsätzliches in C++ falsch... 🙄

    Freue mich über jede hilfreiche Antwort 😃

    MfG



  • Hallo zusammen,

    Die Exception hab ich schonmal als Problem meiner Fehlerbehandlung identifiziert und gefixt.
    Tatsächlich ist es jetzt so, dass IAudioCaptureClient->GetBuffer() genau einmal funktioniert, und dann immer den Error Code: 0x88890007 liefert. Die MSDN meint dazu: "A previous IAudioCaptureClient::GetBuffer call is still in effect."

    Auch das hinzufügen von IAudioCaptureClient->ReleaseBuffer() nach dem Einlesen hat nichts geholfen. Ob ich vorher noch GetNextPacketSize() calle oder nicht, hat anscheinend auch keinen Effekt.

    Mein zentraler Code sieht jetzt so aus:

    float audioListener::getOsc() {
    	BYTE *pData;
    	UINT32 availableFrames;
    	DWORD flags;
    
    	// when nothing is there, don't read it
    	error(pCaptureClient->GetNextPacketSize(&availableFrames));
    	if (availableFrames == 0) {
    		// no frames available
    		return -0.1f;
    	}
    
    	// copy buffer
    	try {
    		if (!error(pCaptureClient->GetBuffer(&pData, &availableFrames, &flags, NULL, NULL))) {
    
    			if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
    				// silence
    				return -0.5f;
    			} else {
    				// find the maximum in the buffer
    				float result = 0.0f;
    				float *f = (float*) pData;
    
    				for (unsigned int i = 0; i < availableFrames; i++) {
    					result = max(result, f[i]);
    				}
    				return result;
    			}
    
    			pCaptureClient->ReleaseBuffer(availableFrames);
    		} else {
    			// error
    			return -1.0f;
    		}
    	} catch (...) {
    		OutputDebugString(_T("Exception during buffer copy"));
    	}
    
    	// extreme error
    	return -2.0f;
    
    }
    

    Kennt sich da keiner ein klein wenig aus oder hat ähnliche Probleme mit "call is still in effect" mal gelöst?
    Zugegebenermaßen ist dieser tief in der WinAPI-liegende Teil eigentlich der Haupt-Grund, warum ich C++ für mein Projekt favorisiert habe. Wenn der nicht tut... 🙄

    MfG


  • Mod

    Das Prinzip ist IMHO so:
    1. Forderst Du den Buffer an.
    2. Benachrichtigt Dich das Audio-Sub-System wenn der Buffer voll ist und Du ihn ausräumen kannst. Dann wird der Buffer wieder neu benutzt.

    D.h. Du bekommstnicht immer einen neuen Buffer...



  • Eigentlich hab ich das nach dem MSDN-Sample so verstanden, dass der intern den Buffer verwaltet und mir jedesmal nen validen Pointer zurückgibt.
    Da wird ja auch in der Schleife ständig mit getBuffer() und releaseBuffer() gearbeitet.

    Inzwischen hab ich auch das hier:
    http://blogs.msdn.com/b/matthew_van_eerde/archive/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear.aspx
    gefunden, was kurioserweise problemlos funktioniert und praktisch (fast) gleich erscheint.
    Da steh ich noch ein bisschen im Wald, was den entscheidenden Unterschied zu meinem Code betrifft...

    Vielleicht meinst Du AUDCLNT_STREAMFLAGS_EVENTCALLBACK?
    Denn in diesem hübscheren Sample dort heißt es auch:

    // call IAudioClient::Initialize
        // note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK
        // do not work together...
        // the "data ready" event never gets set
        // so we're going to do a timer-driven loop
        hr = pAudioClient->Initialize(
            AUDCLNT_SHAREMODE_SHARED,
            AUDCLNT_STREAMFLAGS_LOOPBACK,
            0, 0, pwfx, 0
        );
    

    MfG


  • Mod

    Evtl. vermuschel ich das auch mit der waveApi...



  • So, nachdem ich krankheitsbedingt mich erst heute wieder mit dem Problem beschäftigt habe, hab ichs doch noch in den Griff bekommen.

    In der Tat war nämlich meine erste Vermutung richtig, dass ich mit ReleaseBuffer() den Call abschließen muss.

    whaite schrieb:

    Daher habe ich die Vermutung ich mache irgendwas Grundsätzliches in C++ falsch... 🙄

    Tatsächlich wäre dieser Anfängerfehler aber in jeder anderen Programmiersprache gleich geendet:
    Der scharfe Beobachter stellt fest, dass mein ReleaseBuffer() nach dem return kommt, wo es natürlich wenig Effekt hatte. 😃

    MfG


Log in to reply