Поделиться через


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

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

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

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

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