Aliran Mode Eksklusif

Seperti yang dijelaskan sebelumnya, jika aplikasi membuka aliran dalam mode eksklusif, aplikasi memiliki penggunaan eksklusif perangkat titik akhir audio yang memutar atau merekam aliran. Sebaliknya, beberapa aplikasi dapat berbagi perangkat titik akhir audio dengan membuka aliran mode bersama pada perangkat.

Akses mode eksklusif ke perangkat audio dapat memblokir suara sistem penting, mencegah interoperabilitas dengan aplikasi lain, dan sebaliknya menurunkan pengalaman pengguna. Untuk mengurangi masalah ini, aplikasi dengan aliran mode eksklusif biasanya melepaskan kontrol perangkat audio ketika aplikasi bukan proses latar depan atau tidak secara aktif streaming.

Latensi aliran adalah penundaan yang melekat pada jalur data yang menghubungkan buffer titik akhir aplikasi dengan perangkat titik akhir audio. Untuk aliran penyajian, latensi adalah penundaan maksimum sejak aplikasi menulis sampel ke buffer titik akhir hingga saat sampel didengar melalui speaker. Untuk aliran pengambilan, latensi adalah penundaan maksimum dari waktu suara memasuki mikrofon ke waktu aplikasi dapat membaca sampel untuk suara tersebut dari buffer titik akhir.

Aplikasi yang menggunakan aliran mode eksklusif sering melakukannya karena memerlukan latensi rendah di jalur data antara perangkat titik akhir audio dan utas aplikasi yang mengakses buffer titik akhir. Biasanya, utas ini berjalan dengan prioritas yang relatif tinggi dan menjadwalkan diri mereka untuk berjalan pada interval berkala yang dekat dengan atau sama dengan interval berkala yang memisahkan pemrosesan berturut-turut yang diteruskan oleh perangkat keras audio. Selama setiap pass, perangkat keras audio memproses data baru di buffer titik akhir.

Untuk mencapai latensi aliran terkecil, aplikasi mungkin memerlukan perangkat keras audio khusus, dan sistem komputer yang dimuat dengan ringan. Mendorong perangkat keras audio di luar batas waktunya atau memuat sistem dengan tugas berprioritas tinggi dapat menyebabkan kesalahan dalam aliran audio latensi rendah. Misalnya, untuk aliran penyajian, kesalahan dapat terjadi jika aplikasi gagal menulis ke buffer titik akhir sebelum perangkat keras audio membaca buffer, atau jika perangkat keras gagal membaca buffer sebelum waktu buffer dijadwalkan untuk diputar. Biasanya, aplikasi yang dimaksudkan untuk berjalan pada berbagai perangkat keras audio dan dalam berbagai sistem harus melonggarkan persyaratan waktunya cukup untuk menghindari gangguan di semua lingkungan target.

Windows Vista memiliki beberapa fitur untuk mendukung aplikasi yang memerlukan aliran audio latensi rendah. Seperti yang dibahas dalam Komponen Audio Mode Pengguna, aplikasi yang melakukan operasi kritis waktu dapat memanggil fungsi Multimedia Class Scheduler Service (MMCSS) untuk meningkatkan prioritas utas tanpa menolak sumber daya CPU ke aplikasi berprioritas lebih rendah. Selain itu, metode IAudioClient::Initialize mendukung bendera AUDCLNT_STREAMFLAGS_EVENTCALLBACK yang memungkinkan utas layanan buffer aplikasi untuk menjadwalkan eksekusinya terjadi ketika buffer baru tersedia dari perangkat audio. Dengan menggunakan fitur-fitur ini, utas aplikasi dapat mengurangi ketidakpastian tentang kapan akan dijalankan, sehingga mengurangi risiko gangguan dalam aliran audio latensi rendah.

Driver untuk adaptor audio yang lebih lama kemungkinan akan menggunakan antarmuka driver perangkat WaveCyclic atau WavePci (DDI), sedangkan driver untuk adaptor audio yang lebih baru lebih mungkin mendukung WaveRT DDI. Untuk aplikasi mode eksklusif, driver WaveRT dapat memberikan performa yang lebih baik daripada driver WaveCyclic atau WavePci, tetapi driver WaveRT memerlukan kemampuan perangkat keras tambahan. Kemampuan ini mencakup kemampuan untuk berbagi buffer perangkat keras langsung dengan aplikasi. Dengan berbagi langsung, tidak ada intervensi sistem yang diperlukan untuk mentransfer data antara aplikasi mode eksklusif dan perangkat keras audio. Sebaliknya, driver WaveCyclic dan WavePci cocok untuk adaptor audio yang lebih lama dan kurang mampu. Adaptor ini mengandalkan perangkat lunak sistem untuk mengangkut blok data (dilampirkan ke paket permintaan I/O sistem, atau RUN) antara buffer aplikasi dan buffer perangkat keras. Selain itu, perangkat audio USB mengandalkan perangkat lunak sistem untuk mengangkut data antara buffer aplikasi dan buffer perangkat keras. Untuk meningkatkan performa aplikasi mode eksklusif yang terhubung ke perangkat audio yang mengandalkan sistem untuk transportasi data, WASAPI secara otomatis meningkatkan prioritas utas sistem yang mentransfer data antara aplikasi dan perangkat keras. WASAPI menggunakan MMCSS untuk meningkatkan prioritas utas. Di Windows Vista, jika utas sistem mengelola transportasi data untuk aliran pemutaran audio mode eksklusif dengan format PCM dan periode perangkat kurang dari 10 milidetik, WASAPI menetapkan nama tugas MMCSS "Pro Audio" ke utas. Jika periode perangkat aliran lebih besar dari atau sama dengan 10 milidetik, WASAPI menetapkan nama tugas MMCSS "Audio" ke utas. Untuk informasi selengkapnya tentang DDI WaveCyclic, WavePci, dan WaveRT, lihat dokumentasi Windows DDK. Untuk informasi tentang memilih periode perangkat yang sesuai, lihat IAudioClient::GetDevicePeriod.

Seperti yang dijelaskan dalam Kontrol Volume Sesi, WASAPI menyediakan antarmuka ISimpleAudioVolume, IChannelAudioVolume, dan IAudioStreamVolume untuk mengontrol tingkat volume aliran audio mode bersama. Namun, kontrol dalam antarmuka ini tidak berpengaruh pada aliran mode eksklusif. Sebagai gantinya, aplikasi yang mengelola aliran mode eksklusif biasanya menggunakan antarmuka IAudioEndpointVolume di Api EndpointVolume untuk mengontrol tingkat volume aliran tersebut. Untuk informasi tentang antarmuka ini, lihat Kontrol Volume Titik Akhir.

Untuk setiap perangkat pemutaran dan perangkat pengambilan dalam sistem, pengguna dapat mengontrol apakah perangkat dapat digunakan dalam mode eksklusif. Jika pengguna menonaktifkan penggunaan mode eksklusif perangkat, perangkat dapat digunakan untuk memutar atau merekam hanya aliran mode bersama.

Jika pengguna mengaktifkan penggunaan mode eksklusif perangkat, pengguna juga dapat mengontrol apakah permintaan oleh aplikasi untuk menggunakan perangkat dalam mode eksklusif akan mendahului penggunaan perangkat oleh aplikasi yang saat ini mungkin sedang memutar atau merekam aliran mode bersama melalui perangkat. Jika pemisahan diaktifkan, permintaan oleh aplikasi untuk mengambil kontrol eksklusif atas perangkat berhasil jika perangkat saat ini tidak digunakan, atau jika perangkat digunakan dalam mode bersama, tetapi permintaan gagal jika aplikasi lain sudah memiliki kontrol eksklusif terhadap perangkat. Jika pemisahan dinonaktifkan, permintaan oleh aplikasi untuk mengambil kontrol eksklusif atas perangkat berhasil jika perangkat saat ini tidak digunakan, tetapi permintaan gagal jika perangkat sudah digunakan baik dalam mode bersama atau dalam mode eksklusif.

Di Windows Vista, pengaturan default untuk perangkat titik akhir audio adalah sebagai berikut:

  • Perangkat dapat digunakan untuk memutar atau merekam aliran mode eksklusif.
  • Permintaan untuk menggunakan perangkat untuk memutar atau merekam aliran mode eksklusif akan mengawali aliran mode bersama yang saat ini sedang diputar atau direkam melalui perangkat.

Untuk mengubah pengaturan mode eksklusif pemutaran atau perangkat perekaman

  1. Klik kanan ikon speaker di area pemberitahuan, yang terletak di sisi kanan taskbar, dan pilih Pemutaran Perangkat atau Perangkat Perekaman. (Sebagai alternatif, jalankan panel kontrol multimedia Windows, Mmsys.cpl, dari jendela Prompt Perintah. Untuk informasi selengkapnya, lihat Keterangan di DEVICE_STATE_XXX Konstanta.)
  2. Setelah jendela Suara muncul, pilih Pemutaran atau Perekaman. Selanjutnya, pilih entri dalam daftar nama perangkat, dan klik Properti.
  3. Setelah jendela Properti muncul, klik Tingkat Lanjut.
  4. Untuk mengaktifkan aplikasi menggunakan perangkat dalam mode eksklusif, centang kotak berlabel Izinkan aplikasi untuk mengambil kontrol eksklusif perangkat ini. Untuk menonaktifkan penggunaan mode eksklusif perangkat, kosongkan kotak centang.
  5. Jika penggunaan mode eksklusif perangkat diaktifkan, Anda dapat menentukan apakah permintaan untuk kontrol eksklusif perangkat akan berhasil jika perangkat saat ini memutar atau merekam aliran mode bersama. Untuk memberikan prioritas aplikasi mode eksklusif daripada aplikasi mode bersama, centang kotak berlabel Berikan prioritas aplikasi mode eksklusif. Untuk menolak prioritas aplikasi mode eksklusif daripada aplikasi mode bersama, kosongkan kotak centang.

Contoh kode berikut menunjukkan cara memutar aliran audio latensi rendah pada perangkat penyajian audio yang dikonfigurasi untuk digunakan dalam mode eksklusif:

//-----------------------------------------------------------
// Play an exclusive-mode stream on the default audio
// rendering device. The PlayExclusiveStream function uses
// event-driven buffering and MMCSS to play the stream at
// the minimum latency supported by the device.
//-----------------------------------------------------------

// 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 PlayExclusiveStream(MyAudioSource *pMySource)
{
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = 0;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioClient *pAudioClient = NULL;
    IAudioRenderClient *pRenderClient = NULL;
    WAVEFORMATEX *pwfx = NULL;
    HANDLE hEvent = NULL;
    HANDLE hTask = NULL;
    UINT32 bufferFrameCount;
    BYTE *pData;
    DWORD flags = 0;
    DWORD taskIndex = 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)

    // Call a helper function to negotiate with the audio
    // device for an exclusive-mode stream format.
    hr = GetStreamFormat(pAudioClient, &pwfx);
    EXIT_ON_ERROR(hr)

    // Initialize the stream to play at the minimum latency.
    hr = pAudioClient->GetDevicePeriod(NULL, &hnsRequestedDuration);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->Initialize(
                         AUDCLNT_SHAREMODE_EXCLUSIVE,
                         AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
                         hnsRequestedDuration,
                         hnsRequestedDuration,
                         pwfx,
                         NULL);
    if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
        // Align the buffer if needed, see IAudioClient::Initialize() documentation
        UINT32 nFrames = 0;
        hr = pAudioClient->GetBufferSize(&nFrames);
        EXIT_ON_ERROR(hr)
        hnsRequestedDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC / pwfx->nSamplesPerSec * nFrames + 0.5);
        hr = pAudioClient->Initialize(
            AUDCLNT_SHAREMODE_EXCLUSIVE,
            AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
            hnsRequestedDuration,
            hnsRequestedDuration,
            pwfx,
            NULL);
    }
    EXIT_ON_ERROR(hr)

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

    // Create an event handle and register it for
    // buffer-event notifications.
    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (hEvent == NULL)
    {
        hr = E_FAIL;
        goto Exit;
    }

    hr = pAudioClient->SetEventHandle(hEvent);
    EXIT_ON_ERROR(hr);

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

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

    // To reduce latency, load the first buffer with data
    // from the audio source before starting the stream.
    hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
    EXIT_ON_ERROR(hr)

    hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
    EXIT_ON_ERROR(hr)

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

    // Ask MMCSS to temporarily boost the thread priority
    // to reduce glitches while the low-latency stream plays.
    hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
    if (hTask == NULL)
    {
        hr = E_FAIL;
        EXIT_ON_ERROR(hr)
    }

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

    // Each loop fills one of the two buffers.
    while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
    {
        // Wait for next buffer event to be signaled.
        DWORD retval = WaitForSingleObject(hEvent, 2000);
        if (retval != WAIT_OBJECT_0)
        {
            // Event handle timed out after a 2-second wait.
            pAudioClient->Stop();
            hr = ERROR_TIMEOUT;
            goto Exit;
        }

        // Grab the next empty buffer from the audio device.
        hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
        EXIT_ON_ERROR(hr)

        // Load the buffer with data from the audio source.
        hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
        EXIT_ON_ERROR(hr)

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

    // Wait for the last buffer to play before stopping.
    Sleep((DWORD)(hnsRequestedDuration/REFTIMES_PER_MILLISEC));

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

Exit:
    if (hEvent != NULL)
    {
        CloseHandle(hEvent);
    }
    if (hTask != NULL)
    {
        AvRevertMmThreadCharacteristics(hTask);
    }
    CoTaskMemFree(pwfx);
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pAudioClient)
    SAFE_RELEASE(pRenderClient)

    return hr;
}

Dalam contoh kode sebelumnya, fungsi PlayExclusiveStream berjalan di utas aplikasi yang melayani buffer titik akhir saat aliran penyajian sedang diputar. Fungsi ini mengambil parameter tunggal, pMySource, yang merupakan penunjuk ke objek milik kelas yang ditentukan klien, MyAudioSource. Kelas ini memiliki dua fungsi anggota, LoadData dan SetFormat, yang dipanggil dalam contoh kode. MyAudioSource dijelaskan dalam Merender Stream.

Fungsi PlayExclusiveStream memanggil fungsi pembantu, GetStreamFormat, yang bernegosiasi dengan perangkat penyajian default untuk menentukan apakah perangkat mendukung format aliran mode eksklusif yang cocok untuk digunakan oleh aplikasi. Kode untuk fungsi GetStreamFormat tidak muncul dalam contoh kode; itu karena detail implementasinya sepenuhnya bergantung pada persyaratan aplikasi. Namun, pengoperasian fungsi GetStreamFormat dapat dijelaskan dengan sederhana—fungsi ini memanggil metode IAudioClient::IsFormatSupported satu atau beberapa kali untuk menentukan apakah perangkat mendukung format yang sesuai. Persyaratan aplikasi menentukan format mana yang disajikan GetStreamFormat ke metode IsFormatSupported dan urutan di mana ia menyajikannya. Untuk informasi selengkapnya tentang IsFormatSupported, lihat Format Perangkat.

Setelah panggilan GetStreamFormat, fungsi PlayExclusiveStream memanggil metode IAudioClient::GetDevicePeriod untuk mendapatkan periode perangkat minimum yang didukung oleh perangkat keras audio. Selanjutnya, fungsi memanggil metode IAudioClient::Initialize untuk meminta durasi buffer yang sama dengan periode minimum. Jika panggilan berhasil, metode Inisialisasi mengalokasikan dua buffer titik akhir, yang masing-masing sama dalam durasi hingga periode minimum. Kemudian, ketika aliran audio mulai berjalan, aplikasi dan perangkat keras audio akan berbagi dua buffer dengan cara "ping-pong"—yaitu, sementara aplikasi menulis ke satu buffer, perangkat keras membaca dari buffer lain.

Sebelum memulai aliran, fungsi PlayExclusiveStream melakukan hal berikut:

  • Membuat dan mendaftarkan handel peristiwa di mana ia akan menerima pemberitahuan ketika buffer siap untuk diisi.
  • Mengisi buffer pertama dengan data dari sumber audio untuk mengurangi penundaan dari saat aliran mulai berjalan saat suara awal didengar.
  • Memanggil fungsi AvSetMmThreadCharacteristics untuk meminta agar MMCSS meningkatkan prioritas utas tempat PlayExclusiveStream dijalankan. (Saat aliran berhenti berjalan, Panggilan fungsi AvRevertMmThreadCharacteristics memulihkan prioritas utas asli.)

Untuk informasi selengkapnya tentang AvSetMmThreadCharacteristics dan AvRevertMmThreadCharacteristics, lihat dokumentasi Windows SDK.

Saat aliran berjalan, setiap iterasi perulangan sementara dalam contoh kode sebelumnya mengisi satu buffer titik akhir. Di antara iterasi, panggilan fungsi WaitForSingleObject menunggu handel peristiwa disinyalir. Ketika handel diberi sinyal, isi perulangan melakukan hal berikut:

  1. Memanggil metode IAudioRenderClient::GetBuffer untuk mendapatkan buffer berikutnya.
  2. Mengisi buffer.
  3. Memanggil metode IAudioRenderClient::ReleaseBuffer untuk merilis buffer.

Untuk informasi selengkapnya tentang WaitForSingleObject, lihat dokumentasi Windows SDK.

Jika adaptor audio dikontrol oleh driver WaveRT, sinyal handel peristiwa terkait dengan pemberitahuan transfer DMA dari perangkat keras audio. Untuk perangkat audio USB, atau untuk perangkat audio yang dikontrol oleh driver WaveCyclic atau WavePci, sinyal handel peristiwa terkait dengan penyelesaian RUN yang mentransfer data dari buffer aplikasi ke buffer perangkat keras.

Contoh kode sebelumnya mendorong perangkat keras audio dan sistem komputer ke batas performanya. Pertama, untuk mengurangi latensi aliran, aplikasi menjadwalkan utas layanan buffer untuk menggunakan periode perangkat minimum yang akan didukung perangkat keras audio. Kedua, untuk memastikan bahwa utas dijalankan dengan andal dalam setiap periode perangkat, panggilan fungsi AvSetMmThreadCharacteristics mengatur parameter TaskName ke "Pro Audio", yaitu, di Windows Vista, nama tugas default dengan prioritas tertinggi. Pertimbangkan apakah persyaratan waktu aplikasi Anda mungkin dilonggarkan tanpa mengorbankan kegunaannya. Misalnya, aplikasi mungkin menjadwalkan utas layanan buffer-nya untuk menggunakan periode yang lebih panjang dari minimum. Periode yang lebih lama mungkin dengan aman memungkinkan penggunaan prioritas utas yang lebih rendah.

Manajemen Aliran