Erfassen eines Datenstroms
Der Client ruft die Methoden in der IAudioCaptureClient-Schnittstelle auf, um erfasste Daten aus einem Endpunktpuffer zu lesen. Der Client teilt den Endpunktpuffer mit der Audio-Engine im freigegebenen Modus und mit dem Audiogerät im exklusiven Modus. Um einen Endpunktpuffer einer bestimmten Größe anzufordern, ruft der Client die IAudioClient::Initialize-Methode auf . Um die Größe des zugeordneten Puffers abzurufen, die sich von der angeforderten Größe unterscheiden kann, ruft der Client die IAudioClient::GetBufferSize-Methode auf .
Zum Verschieben eines Datenstroms durch den Endpunktpuffer ruft der Client abwechselnd die IAudioCaptureClient::GetBuffer-Methode und die IAudioCaptureClient::ReleaseBuffer-Methode auf. Der Client greift als Reihe von Datenpaketen auf die Daten im Endpunktpuffer zu. Der GetBuffer-Aufruf ruft das nächste Paket mit erfassten Daten aus dem Puffer ab. Nach dem Lesen der Daten aus dem Paket ruft der Client ReleaseBuffer auf, um das Paket freizugeben und für mehr erfasste Daten verfügbar zu machen.
Die Paketgröße kann je nach GetBuffer-Aufruf zum nächsten variieren. Vor dem Aufrufen von GetBuffer hat der Client die Möglichkeit, die IAudioCaptureClient::GetNextPacketSize-Methode aufzurufen, um die Größe des nächsten Pakets im Voraus abzurufen. Darüber hinaus kann der Client die IAudioClient::GetCurrentPadding-Methode aufrufen, um die Gesamtmenge der erfassten Daten abzurufen, die im Puffer verfügbar sind. Die Paketgröße ist jederzeit kleiner oder gleich der Gesamtmenge der erfassten Daten im Puffer.
Während jedes Verarbeitungsdurchlaufs hat der Client die Möglichkeit, die erfassten Daten auf eine der folgenden Arten zu verarbeiten:
- Der Client ruft abwechselnd GetBuffer und ReleaseBuffer auf und liest ein Paket mit jedem Aufrufpaar, bis GetBuffer AUDCNT_S_BUFFEREMPTY zurückgibt, was angibt, dass der Puffer leer ist.
- Der Client ruft GetNextPacketSize vor jedem Paar von Aufrufen von GetBuffer und ReleaseBuffer auf, bis GetNextPacketSize eine Paketgröße von 0 meldet, was angibt, dass der Puffer leer ist.
Die beiden Techniken liefern gleichwertige Ergebnisse.
Im folgenden Codebeispiel wird gezeigt, wie Sie einen Audiodatenstrom vom Standardaufnahmegerät aufzeichnen:
//-----------------------------------------------------------
// Record an audio stream from the default audio capture
// device. The RecordAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data from the
// capture device. The main loop runs every 1/2 second.
//-----------------------------------------------------------
// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC 10000000
#define REFTIMES_PER_MILLISEC 10000
#define EXIT_ON_ERROR(hres) \
if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk) \
if ((punk) != NULL) \
{ (punk)->Release(); (punk) = NULL; }
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
HRESULT RecordAudioStream(MyAudioSink *pMySink)
{
HRESULT hr;
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
REFERENCE_TIME hnsActualDuration;
UINT32 bufferFrameCount;
UINT32 numFramesAvailable;
IMMDeviceEnumerator *pEnumerator = NULL;
IMMDevice *pDevice = NULL;
IAudioClient *pAudioClient = NULL;
IAudioCaptureClient *pCaptureClient = NULL;
WAVEFORMATEX *pwfx = NULL;
UINT32 packetLength = 0;
BOOL bDone = FALSE;
BYTE *pData;
DWORD flags;
hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&pEnumerator);
EXIT_ON_ERROR(hr)
hr = pEnumerator->GetDefaultAudioEndpoint(
eCapture, eConsole, &pDevice);
EXIT_ON_ERROR(hr)
hr = pDevice->Activate(
IID_IAudioClient, CLSCTX_ALL,
NULL, (void**)&pAudioClient);
EXIT_ON_ERROR(hr)
hr = pAudioClient->GetMixFormat(&pwfx);
EXIT_ON_ERROR(hr)
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
0,
hnsRequestedDuration,
0,
pwfx,
NULL);
EXIT_ON_ERROR(hr)
// Get the size of the allocated buffer.
hr = pAudioClient->GetBufferSize(&bufferFrameCount);
EXIT_ON_ERROR(hr)
hr = pAudioClient->GetService(
IID_IAudioCaptureClient,
(void**)&pCaptureClient);
EXIT_ON_ERROR(hr)
// Notify the audio sink which format to use.
hr = pMySink->SetFormat(pwfx);
EXIT_ON_ERROR(hr)
// Calculate the actual duration of the allocated buffer.
hnsActualDuration = (double)REFTIMES_PER_SEC *
bufferFrameCount / pwfx->nSamplesPerSec;
hr = pAudioClient->Start(); // Start recording.
EXIT_ON_ERROR(hr)
// Each loop fills about half of the shared buffer.
while (bDone == FALSE)
{
// Sleep for half the buffer duration.
Sleep(hnsActualDuration/REFTIMES_PER_MILLISEC/2);
hr = pCaptureClient->GetNextPacketSize(&packetLength);
EXIT_ON_ERROR(hr)
while (packetLength != 0)
{
// Get the available data in the shared buffer.
hr = pCaptureClient->GetBuffer(
&pData,
&numFramesAvailable,
&flags, NULL, NULL);
EXIT_ON_ERROR(hr)
if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
pData = NULL; // Tell CopyData to write silence.
}
// Copy the available capture data to the audio sink.
hr = pMySink->CopyData(
pData, numFramesAvailable, &bDone);
EXIT_ON_ERROR(hr)
hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
EXIT_ON_ERROR(hr)
hr = pCaptureClient->GetNextPacketSize(&packetLength);
EXIT_ON_ERROR(hr)
}
}
hr = pAudioClient->Stop(); // Stop recording.
EXIT_ON_ERROR(hr)
Exit:
CoTaskMemFree(pwfx);
SAFE_RELEASE(pEnumerator)
SAFE_RELEASE(pDevice)
SAFE_RELEASE(pAudioClient)
SAFE_RELEASE(pCaptureClient)
return hr;
}
Im vorherigen Beispiel verwendet die RecordAudioStream-Funktion einen einzelnen Parameter, pMySink
, der ein Zeiger auf ein Objekt ist, das zu einer clientdefinierten Klasse, MyAudioSink, mit den beiden Funktionen CopyData und SetFormat gehört. Der Beispielcode enthält die Implementierung von MyAudioSink aus folgenden Gründen nicht:
- Keines der Klassenmember kommuniziert direkt mit einer der Methoden in den Schnittstellen in WASAPI.
- Die -Klasse kann abhängig von den Anforderungen des Clients auf verschiedene Arten implementiert werden. (Es kann z. B. die Erfassungsdaten in eine WAV-Datei schreiben.)
Informationen zum Betrieb der beiden Methoden sind jedoch hilfreich, um das Beispiel zu verstehen.
Die CopyData-Funktion kopiert eine angegebene Anzahl von Audioframes von einem angegebenen Pufferspeicherort. Die RecordAudioStream-Funktion verwendet die CopyData-Funktion, um die Audiodaten aus dem freigegebenen Puffer zu lesen und zu speichern. Die SetFormat-Funktion gibt das Format für die CopyData-Funktion an, die für die Daten verwendet werden soll.
Solange das MyAudioSink-Objekt zusätzliche Daten benötigt, gibt die CopyData-Funktion den Wert FALSE über ihren dritten Parameter aus, der im vorherigen Codebeispiel ein Zeiger auf die Variable bDone
ist. Wenn das MyAudioSink-Objekt über alle benötigten Daten verfügt, legt die CopyData-Funktion auf TRUE festbDone
, wodurch das Programm die Schleife in der RecordAudioStream-Funktion beendet.
Die RecordAudioStream-Funktion weist einen freigegebenen Puffer zu, der eine Dauer von einer Sekunde hat. (Der zugeordnete Puffer kann eine etwas längere Dauer haben.) Innerhalb der Standard-Schleife bewirkt der Aufruf der Windows-Standbyfunktion, dass das Programm eine halbe Sekunde wartet. Zu Beginn jedes Standbyaufrufs ist der freigegebene Puffer leer oder fast leer. Wenn der Standbyaufruf zurückgibt, ist der freigegebene Puffer etwa zur Hälfte mit Aufzeichnungsdaten gefüllt.
Nach dem Aufruf der IAudioClient::Initialize-Methode bleibt der Stream geöffnet, bis der Client alle Verweise auf die IAudioClient-Schnittstelle und auf alle Verweise auf Dienstschnittstellen freigibt, die der Client über die IAudioClient::GetService-Methode abgerufen hat. Der letzte Release-Aufruf schließt den Stream.
Zugehörige Themen