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/ms678709Ich 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
-
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
-
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