Merender Aliran

Klien memanggil metode di antarmuka IAudioRenderClient untuk menulis data penyajian ke buffer titik akhir. Untuk aliran mode bersama, klien berbagi buffer titik akhir dengan mesin audio. Untuk aliran mode eksklusif, klien berbagi buffer titik akhir dengan perangkat audio. 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 penyajian melalui buffer titik akhir, klien secara bergantian memanggil metode IAudioRenderClient::GetBuffer dan metode IAudioRenderClient::ReleaseBuffer. Klien mengakses data di buffer titik akhir sebagai serangkaian paket data. Panggilan GetBuffer mengambil paket berikutnya sehingga klien dapat mengisinya dengan data penyajian. Setelah menulis data ke paket, klien memanggil ReleaseBuffer untuk menambahkan paket yang telah selesai ke antrean penyajian.

Untuk buffer penyajian, nilai padding yang dilaporkan oleh metode IAudioClient::GetCurrentPadding mewakili jumlah data penyajian yang diantrekan untuk dimainkan di buffer. Aplikasi penyajian dapat menggunakan nilai padding untuk menentukan berapa banyak data baru yang dapat ditulis dengan aman ke buffer tanpa risiko menimpa data yang ditulis sebelumnya yang belum dibaca mesin audio dari buffer. Ruang yang tersedia hanyalah ukuran buffer dikurangi ukuran padding. Klien dapat meminta ukuran paket yang mewakili beberapa atau semua ruang yang tersedia ini dalam panggilan GetBuffer berikutnya.

Ukuran paket diekspresikan dalam bingkai audio. Bingkai audio dalam aliran PCM adalah sekumpulan sampel (set berisi satu sampel untuk setiap saluran dalam aliran) yang diputar atau direkam pada saat yang sama (jam centang). Dengan demikian, ukuran bingkai audio adalah ukuran sampel yang dikalikan dengan jumlah saluran dalam aliran. Misalnya, ukuran bingkai untuk aliran stereo (2 saluran) dengan sampel 16-bit adalah empat byte.

Contoh kode berikut menunjukkan cara memutar aliran audio pada perangkat penyajian default:

//-----------------------------------------------------------
// Play an audio stream on the default audio rendering
// device. The PlayAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data to the
// rendering device. The inner 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_IAudioRenderClient = __uuidof(IAudioRenderClient);

HRESULT PlayAudioStream(MyAudioSource *pMySource)
{
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
    REFERENCE_TIME hnsActualDuration;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioClient *pAudioClient = NULL;
    IAudioRenderClient *pRenderClient = NULL;
    WAVEFORMATEX *pwfx = NULL;
    UINT32 bufferFrameCount;
    UINT32 numFramesAvailable;
    UINT32 numFramesPadding;
    BYTE *pData;
    DWORD flags = 0;

    hr = CoCreateInstance(
           CLSID_MMDeviceEnumerator, NULL,
           CLSCTX_ALL, IID_IMMDeviceEnumerator,
           (void**)&pEnumerator);
    EXIT_ON_ERROR(hr)

    hr = pEnumerator->GetDefaultAudioEndpoint(
                        eRender, 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)

    // Tell the audio source which format to use.
    hr = pMySource->SetFormat(pwfx);
    EXIT_ON_ERROR(hr)

    // Get the actual size of the allocated buffer.
    hr = pAudioClient->GetBufferSize(&bufferFrameCount);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetService(
                         IID_IAudioRenderClient,
                         (void**)&pRenderClient);
    EXIT_ON_ERROR(hr)

    // Grab the entire buffer for the initial fill operation.
    hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
    EXIT_ON_ERROR(hr)

    // Load the initial data into the shared buffer.
    hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
    EXIT_ON_ERROR(hr)

    hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
    EXIT_ON_ERROR(hr)

    // Calculate the actual duration of the allocated buffer.
    hnsActualDuration = (double)REFTIMES_PER_SEC *
                        bufferFrameCount / pwfx->nSamplesPerSec;

    hr = pAudioClient->Start();  // Start playing.
    EXIT_ON_ERROR(hr)

    // Each loop fills about half of the shared buffer.
    while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
    {
        // Sleep for half the buffer duration.
        Sleep((DWORD)(hnsActualDuration/REFTIMES_PER_MILLISEC/2));

        // See how much buffer space is available.
        hr = pAudioClient->GetCurrentPadding(&numFramesPadding);
        EXIT_ON_ERROR(hr)

        numFramesAvailable = bufferFrameCount - numFramesPadding;

        // Grab all the available space in the shared buffer.
        hr = pRenderClient->GetBuffer(numFramesAvailable, &pData);
        EXIT_ON_ERROR(hr)

        // Get next 1/2-second of data from the audio source.
        hr = pMySource->LoadData(numFramesAvailable, pData, &flags);
        EXIT_ON_ERROR(hr)

        hr = pRenderClient->ReleaseBuffer(numFramesAvailable, flags);
        EXIT_ON_ERROR(hr)
    }

    // Wait for last data in buffer to play before stopping.
    Sleep((DWORD)(hnsActualDuration/REFTIMES_PER_MILLISEC/2));

    hr = pAudioClient->Stop();  // Stop playing.
    EXIT_ON_ERROR(hr)

Exit:
    CoTaskMemFree(pwfx);
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pAudioClient)
    SAFE_RELEASE(pRenderClient)

    return hr;
}

Dalam contoh sebelumnya, fungsi PlayAudioStream mengambil satu parameter, pMySource, yang merupakan penunjuk ke objek milik kelas yang ditentukan klien, MyAudioSource, dengan dua fungsi anggota, LoadData dan SetFormat. Contoh kode tidak menyertakan implementasi MyAudioSource 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, ia mungkin membaca data penyajian dari file WAV dan melakukan konversi on-the-fly ke format aliran.)

Namun, beberapa informasi tentang pengoperasian dua fungsi berguna untuk memahami contoh.

Fungsi LoadData menulis jumlah bingkai audio tertentu (parameter pertama) ke lokasi buffer tertentu (parameter kedua). (Ukuran bingkai audio adalah jumlah saluran dalam aliran yang dikalikan dengan ukuran sampel.) Fungsi PlayAudioStream menggunakan LoadData untuk mengisi bagian buffer bersama dengan data audio. Fungsi SetFormat menentukan format untuk fungsi LoadData yang akan digunakan untuk data. Jika fungsi LoadData dapat menulis setidaknya satu bingkai ke lokasi buffer yang ditentukan tetapi kehabisan data sebelum menulis jumlah bingkai yang ditentukan, maka ia menulis keheningan ke bingkai yang tersisa.

Selama LoadData berhasil menulis setidaknya satu bingkai data nyata (tidak diam) ke lokasi buffer yang ditentukan, loadData menghasilkan 0 melalui parameter ketiganya, yang, dalam contoh kode sebelumnya, adalah penunjuk output ke flags variabel. Ketika LoadData kehabisan data dan tidak dapat menulis bahkan satu bingkai ke lokasi buffer yang ditentukan, loadData tidak menulis apa pun ke buffer (bahkan tidak diam), dan menulis nilai AUDCLNT_BUFFERFLAGS_SILENT ke flags variabel. Variabel flags menyampaikan nilai ini ke metode IAudioRenderClient::ReleaseBuffer , yang merespons dengan mengisi jumlah bingkai yang ditentukan dalam buffer dengan keheningan.

Dalam panggilannya ke metode IAudioClient::Initialize , fungsi PlayAudioStream dalam contoh sebelumnya meminta buffer bersama yang memiliki durasi satu detik. (Buffer yang dialokasikan mungkin memiliki durasi yang sedikit lebih lama.) Dalam panggilan awalnya ke metode IAudioRenderClient::GetBuffer dan IAudioRenderClient::ReleaseBuffer, fungsi mengisi seluruh buffer sebelum memanggil metode IAudioClient::Start untuk mulai memutar buffer.

Dalam perulangan utama, fungsi secara berulang mengisi setengah dari buffer pada interval setengah detik. Tepat sebelum setiap panggilan ke fungsi Windows Sleep di perulangan utama, buffer penuh atau hampir penuh. Ketika panggilan Tidur kembali, buffer sekitar setengah penuh. Perulangan berakhir setelah panggilan akhir ke fungsi LoadData mengatur flags variabel ke nilai AUDCLNT_BUFFERFLAGS_SILENT. Pada saat itu, buffer berisi setidaknya satu bingkai data nyata, dan mungkin berisi sebanyak setengah detik data nyata. Sisa buffer berisi keheningan. Panggilan Tidur yang mengikuti perulangan memberikan waktu yang cukup (setengah detik) untuk memutar semua data yang tersisa. Keheningan yang mengikuti data mencegah suara yang tidak diinginkan sebelum panggilan ke metode IAudioClient::Stop menghentikan aliran audio. Untuk informasi selengkapnya tentang Tidur, lihat dokumentasi Windows SDK.

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.

Fungsi PlayAudioStream dalam contoh kode sebelumnya memanggil fungsi CoCreateInstance untuk membuat enumerator untuk perangkat titik akhir audio dalam sistem. Kecuali program panggilan sebelumnya disebut fungsi CoCreateInstance atau CoInitializeEx untuk menginisialisasi pustaka COM, panggilan CoCreateInstance akan gagal. Untuk informasi selengkapnya tentang CoCreateInstance, CoCreateInstance, dan CoInitializeEx, lihat dokumentasi Windows SDK.

Manajemen Aliran