Menangkap Aliran

Klien memanggil metode di antarmuka IAudioCaptureClient untuk membaca data yang diambil dari buffer titik akhir. Klien berbagi buffer titik akhir dengan mesin audio dalam mode bersama dan dengan perangkat audio dalam mode eksklusif. Untuk meminta buffer titik akhir dengan ukuran tertentu, klien memanggil metode IAudioClient::Initialize . Untuk mendapatkan ukuran buffer yang dialokasikan, yang mungkin berbeda dari ukuran yang diminta, klien memanggil metode IAudioClient::GetBufferSize.

Untuk memindahkan aliran data yang diambil melalui buffer titik akhir, klien secara bergantian memanggil metode IAudioCaptureClient::GetBuffer dan metode IAudioCaptureClient::ReleaseBuffer. Klien mengakses data di buffer titik akhir sebagai serangkaian paket data. Panggilan GetBuffer mengambil paket data yang diambil berikutnya dari buffer. Setelah membaca data dari paket, klien memanggil ReleaseBuffer untuk merilis paket dan membuatnya tersedia untuk data yang lebih banyak diambil.

Ukuran paket dapat bervariasi dari satu panggilan GetBuffer ke panggilan berikutnya. Sebelum memanggil GetBuffer, klien memiliki opsi untuk memanggil metode IAudioCaptureClient::GetNextPacketSize untuk mendapatkan ukuran paket berikutnya terlebih dahulu. Selain itu, klien dapat memanggil metode IAudioClient::GetCurrentPadding untuk mendapatkan jumlah total data yang diambil yang tersedia di buffer. Dalam sekejap, ukuran paket selalu kurang dari atau sama dengan jumlah total data yang diambil dalam buffer.

Selama setiap pass pemrosesan, klien memiliki opsi untuk memproses data yang diambil dengan salah satu cara berikut:

  • Klien secara bergantian memanggil GetBuffer dan ReleaseBuffer, membaca satu paket dengan setiap pasangan panggilan, hingga GetBuffer mengembalikan AUDCNT_S_BUFFEREMPTY, menunjukkan bahwa buffer kosong.
  • Klien memanggil GetNextPacketSize sebelum setiap pasangan panggilan ke GetBuffer dan ReleaseBuffer hingga GetNextPacketSize melaporkan ukuran paket 0, yang menunjukkan bahwa buffer kosong.

Dua teknik tersebut menghasilkan hasil yang setara.

Contoh kode berikut menunjukkan cara merekam aliran audio dari perangkat pengambilan default:

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

Dalam contoh sebelumnya, fungsi RecordAudioStream mengambil satu parameter, pMySink, yang merupakan penunjuk ke objek milik kelas yang ditentukan klien, MyAudioSink, dengan dua fungsi, CopyData dan SetFormat. Contoh kode tidak menyertakan implementasi MyAudioSink karena:

  • Tidak ada anggota kelas yang berkomunikasi langsung dengan salah satu metode dalam antarmuka di WASAPI.
  • Kelas dapat diimplementasikan dengan berbagai cara, tergantung pada persyaratan klien. (Misalnya, mungkin menulis data pengambilan ke file WAV.)

Namun, informasi tentang pengoperasian dua metode berguna untuk memahami contoh.

Fungsi CopyData menyalin sejumlah bingkai audio tertentu dari lokasi buffer tertentu. Fungsi RecordAudioStream menggunakan fungsi CopyData untuk membaca dan menyimpan data audio dari buffer bersama. Fungsi SetFormat menentukan format untuk fungsi CopyData yang akan digunakan untuk data.

Selama objek MyAudioSink memerlukan data tambahan, fungsi CopyData menghasilkan nilai FALSE melalui parameter ketiganya, yang, dalam contoh kode sebelumnya, adalah penunjuk ke variabel bDone. Ketika objek MyAudioSink memiliki semua data yang diperlukan, fungsi CopyData diatur bDone ke TRUE, yang menyebabkan program keluar dari perulangan dalam fungsi RecordAudioStream.

Fungsi RecordAudioStream mengalokasikan buffer bersama yang memiliki durasi satu detik. (Buffer yang dialokasikan mungkin memiliki durasi yang sedikit lebih lama.) Dalam perulangan utama, panggilan ke fungsi Windows Sleep menyebabkan program menunggu setengah detik. Pada awal setiap panggilan Tidur , buffer bersama kosong atau hampir kosong. Pada saat panggilan Tidur kembali, buffer bersama sekitar setengah diisi dengan data pengambilan.

Mengikuti panggilan ke metode IAudioClient::Initialize, aliran tetap terbuka sampai klien merilis semua referensinya ke antarmuka IAudioClient dan ke semua referensi ke antarmuka layanan yang diperoleh klien melalui metode IAudioClient::GetService. Panggilan Rilis akhir menutup aliran.

Manajemen Aliran