Отрисовка потока

Клиент вызывает методы в интерфейсе IAudioRenderClient для записи данных отрисовки в буфер конечной точки. Для потока общего режима клиент предоставляет общий доступ к буферу конечной точки с звуковой подсистемой. Для потока монопольного режима клиент использует буфер конечной точки с звуковым устройством. Чтобы запросить буфер конечной точки определенного размера, клиент вызывает метод IAudioClient::Initialize . Чтобы получить размер выделенного буфера, который может отличаться от запрошенного размера, клиент вызывает метод IAudioClient::GetBufferSize.

Чтобы переместить поток данных отрисовки через буфер конечной точки, клиент также вызывает метод IAudioRenderClient::GetBuffer и метод IAudioRenderClient::ReleaseBuffer. Клиент обращается к данным в буфере конечной точки в виде ряда пакетов данных. Вызов GetBuffer извлекает следующий пакет, чтобы клиент смог заполнить его данными отрисовки. После записи данных в пакет клиент вызывает ReleaseBuffer , чтобы добавить завершенный пакет в очередь отрисовки.

Для буфера отрисовки значение заполнения, которое сообщается методом IAudioClient::GetCurrentPadding , представляет объем данных отрисовки, которые помещается в очередь для воспроизведения в буфере. Приложение отрисовки может использовать значение заполнения, чтобы определить, сколько новых данных он может безопасно записывать в буфер без риска перезаписи ранее записанных данных, которые звуковой механизм еще не считывал из буфера. Доступное пространство — это просто размер буфера минус размер заполнения. Клиент может запросить размер пакета, представляющий некоторые или все это доступное пространство в следующем вызове GetBuffer.

Размер пакета выражается в аудиокадрах. Аудиокадр в потоке PCM — это набор примеров (набор содержит один образец для каждого канала в потоке), который воспроизводится или записывается одновременно (часы галочки). Таким образом, размер звукового кадра — это размер выборки, умноженный на количество каналов в потоке. Например, размер кадра для потока стерео (2-канал) с 16-разрядными образцами составляет четыре байта.

В следующем примере кода показано, как воспроизводить аудиопоток на устройстве отрисовки по умолчанию:

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

В предыдущем примере функция PlayAudioStream принимает один параметр, pMySourceкоторый является указателем на объект, принадлежащий к клиентскому классу MyAudioSource, с двумя функциями-членами, LoadData и SetFormat. Пример кода не включает реализацию MyAudioSource, так как:

  • Ни один из членов класса не взаимодействует напрямую с любым из методов в интерфейсах в WASAPI.
  • Класс можно реализовать различными способами в зависимости от требований клиента. (Например, он может считывать данные отрисовки из WAV-файла и выполнять преобразование во всплывающем формате в формат потока.)

Однако некоторые сведения о работе двух функций полезны для понимания примера.

Функция LoadData записывает указанное количество звуковых кадров (первый параметр) в указанное расположение буфера (второй параметр). (Размер звукового кадра — это количество каналов в потоке, умноженное на размер выборки.) Функция PlayAudioStream использует LoadData для заполнения частей общего буфера звуковыми данными. Функция SetFormat указывает формат функции LoadData, используемой для данных. Если функция LoadData может записывать по крайней мере один кадр в указанное расположение буфера, но заканчивается из данных до записи указанного количества кадров, то он записывает молчание в оставшиеся кадры.

Если LoadData успешно записывает по крайней мере один кадр реальных данных (не молчание) в указанное расположение буфера, он выводит 0 через третий параметр, который в предыдущем примере кода является указателем вывода на flags переменную. Если LoadData выходит из данных и не может записывать даже один кадр в указанное расположение буфера, он ничего не записывает в буфер (даже не молчание), и записывает значение AUDCLNT_BUFFERFLAGS_SILENT в flags переменную. Переменная flags передает это значение методу IAudioRenderClient::ReleaseBuffer, который отвечает, заполняя указанное количество кадров в буфере молчанием.

В вызове метода IAudioClient::Initialize функция PlayAudioStream в предыдущем примере запрашивает общий буфер с длительностью 1 секунды. (Выделенный буфер может иметь немного более длинную длительность.) В начальных вызовах методов IAudioRenderClient::GetBuffer и IAudioRenderClient::ReleaseBuffer функция заполняет весь буфер перед вызовом метода IAudioClient::Start, чтобы начать воспроизведение буфера.

В основном цикле функция итеративно заполняет половину буфера через пол-секунды. Непосредственно перед каждым вызовом функции спящего режима Windows в основном цикле буфер заполнен или почти заполнен. При возвращении вызова спящего режима буфер составляет около половины полного. Цикл заканчивается после окончательного вызова функции LoadData задает flags переменную значением AUDCLNT_BUFFERFLAGS_SILENT. На этом этапе буфер содержит по крайней мере один кадр реальных данных, и он может содержать не более половины секунды реальных данных. Оставшаяся часть буфера содержит молчание. Вызов спящего режима , следующий за циклом, обеспечивает достаточно времени (половина секунды) для воспроизведения всех оставшихся данных. Молчание, которое следует за данными, предотвращает нежелательные звуки перед вызовом метода IAudioClient::Stop , останавливает звуковой поток. Дополнительные сведения о спячем см. в документации по пакету SDK для Windows.

После вызова метода IAudioClient::Initialize поток остается открытым, пока клиент не освобождает все ссылки на интерфейс IAudioClient и все ссылки на интерфейсы служб, полученные клиентом с помощью метода IAudioClient::GetService. Последний вызов выпуска закрывает поток.

Функция PlayAudioStream в предыдущем примере кода вызывает функцию CoCreateInstance , чтобы создать перечислитель для устройств аудио конечной точки в системе. Если ранее вызываемая программа не вызывала функцию CoCreateInstance или CoInitializeEx для инициализации библиотеки COM, вызов CoCreateInstance завершится ошибкой. Дополнительные сведения о CoCreateInstance, CoCreateInstance и CoInitializeEx см. в документации по пакету SDK для Windows.

Управление потоками