Stream rögzítése

Az ügyfél meghívja az IAudioCaptureClient felület metódusait, hogy beolvassa a rögzített adatokat egy végpontpufferből. Az ügyfél megosztott módban és kizárólagos módban osztja meg a végpontpuffert a hangmotorral és a hangeszközzel. Egy adott méretű végpontpuffer lekéréséhez az ügyfél meghívja a IAudioClient::Initialize metódust. A lefoglalt puffer méretének lekéréséhez, amely eltérhet a kért mérettől, az ügyfél meghívja a IAudioClient::GetBufferSize metódust.

A rögzített adatok adatfolyamának a végpontpufferen keresztül történő áthelyezéséhez az ügyfél felváltva meghívja a IAudioCaptureClient::GetBuffer metódust és az IAudioCaptureClient::ReleaseBuffer metódust. Az ügyfél adatcsomagok sorozataként fér hozzá a végpontpufferben lévő adatokhoz. A GetBuffer hívás lekéri a következő rögzített adatcsomagot a pufferből. Miután beolvasta az adatokat a csomagból, az ügyfél meghívja ReleaseBuffer, hogy engedje fel a csomagot, és tegye elérhetővé a rögzítettebb adatok számára.

A csomag mérete eltérhet az egyik GetBuffer hívástól a következőig. GetBufferhívása előtt az ügyfél meghívhatja a IAudioCaptureClient::GetNextPacketSize metódust a következő csomag méretének előzetes lekéréséhez. Emellett az ügyfél meghívhatja az IAudioClient::GetCurrentPadding metódust a pufferben elérhető rögzített adatok teljes mennyiségének lekéréséhez. A csomag mérete bármikor kisebb vagy egyenlő a pufferben rögzített adatok teljes mennyiségénél.

Minden feldolgozási folyamat során az ügyfélnek lehetősége van a rögzített adatok feldolgozására az alábbi módok egyikével:

  • Az ügyfél felváltva hívja meg a GetBuffer és a ReleaseBufferfüggvényt, minden híváspárnál egy csomagot olvasva be, amíg a GetBuffer vissza nem adja az AUDCNT_S_BUFFEREMPTY értéket, jelezve, hogy a puffer kiürült.
  • Az ügyfél minden egyes híváspár előtt hívja a GetNextPacketSize, majd ugyanazon párba tartozóan a GetBuffer és ReleaseBuffer függvényeket, amíg a GetNextPacketSize függvény 0 csomagméretet nem jelez, ami azt jelzi, hogy a puffer üres.

A két technika egyenértékű eredményeket ad.

Az alábbi példakód bemutatja, hogyan rögzíthet hangstreamet az alapértelmezett rögzítési eszközről:

//-----------------------------------------------------------
// 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;
}

Az előző példában a RecordAudioStream függvény egyetlen paramétert vesz fel, pMySink, amely egy ügyfél által definiált osztályhoz, a MyAudioSinkhez tartozó objektumra mutató mutató, két függvénnyel, a CopyData és a SetFormat függvénnyel. A példakód nem tartalmazza a MyAudioSink implementációját, mert:

  • Az osztálytagok egyike sem kommunikál közvetlenül a WASAPI felületeinek egyik metódusával sem.
  • Az osztály többféleképpen is implementálható az ügyfél követelményeitől függően. (Például megírhatja a rögzítési adatokat egy WAV-fájlba.)

A két módszer működésével kapcsolatos információk azonban hasznosak a példa megértéséhez.

A CopyData függvény egy megadott számú hangkeretet másol egy megadott pufferhelyről. A RecordAudioStream függvény a CopyData függvénnyel olvassa és menti a hangadatokat a megosztott pufferből. A SetFormat függvény megadja az adatokhoz használni kívánt CopyData függvény formátumát.

Mindaddig, amíg a MyAudioSink objektum további adatokat igényel, a CopyData függvény a FALSE értéket adja ki a harmadik paraméteren keresztül, amely az előző kódpéldában a bDoneváltozóra mutató mutató. Amikor a MyAudioSink objektum rendelkezik minden szükséges adattal, a CopyData függvény bDone értékét TRUEállítja be, ami miatt a program kilép a RecordAudioStream függvény ciklusából.

A RecordAudioStream függvény egy egy másodperces időtartamú megosztott puffert foglal le. (A lefoglalt puffer időtartama kissé hosszabb lehet.) A fő hurokban a Windows Alvó függvény hívása miatt a program fél másodpercig várakozik. Minden Alvó hívás elején a megosztott puffer üres vagy majdnem üres. Amikor a Alvó hívás visszatér, a megosztott puffer körülbelül félig tele van rögzítési adatokkal.

A IAudioClient::Initialize metódus meghívását követően a stream nyitva marad, amíg az ügyfél nem adja ki az összes hivatkozását az IAudioClient felületre, valamint az ügyfél által az IAudioClient::GetService metóduson keresztül beszerzett szolgáltatási felületekre mutató összes hivatkozást. Az utolsó kiadási hívás bezárja a streamet.

Áramláskezelés