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


Монопольный режим Потоки

Как описано ранее, если приложение открывает поток в монопольном режиме, приложение имеет монопольное использование устройства аудио конечной точки, которое воспроизводит или записывает поток. В отличие от этого, несколько приложений могут совместно использовать устройство аудио конечной точки, открыв потоки общего режима на устройстве.

Монопольный доступ к звуковому устройству может блокировать важные системные звуки, предотвращать взаимодействие с другими приложениями и иначе ухудшать взаимодействие с пользователем. Чтобы устранить эти проблемы, приложение с потоком монопольного режима обычно отменяет управление звуковым устройством, если приложение не является процессом переднего плана или не активно потоковой передачи.

Задержка потока — это задержка, встроенная в путь к данным, который подключает буфер конечной точки приложения с устройством звуковой конечной точки. Для потока отрисовки задержка — это максимальная задержка с момента, когда приложение записывает пример в буфер конечной точки до момента, когда образец услышится через динамики. Для потока записи задержка — это максимальная задержка с момента ввода звука в микрофон до момента, когда приложение может считывать образец для этого звука из буфера конечной точки.

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

Чтобы достичь наименьшей задержки потока, приложению может потребоваться как специальное звуковое оборудование, так и компьютерная система, которая легко загружена. Управление звуковым оборудованием за пределы его ограничений или загрузка системы с конкурирующими задачами с высоким приоритетом может привести к сбою в аудиопотоке с низкой задержкой. Например, для потока отрисовки может произойти сбой, если приложению не удается записать в буфер конечной точки, прежде чем звуковое оборудование считывает буфер, или если оборудование не сможет считывать буфер до времени, когда буфер планируется воспроизвести. Как правило, приложение, которое предназначено для работы на широком спектре звукового оборудования и в широком спектре систем, должно расслабить свои требования к времени достаточно, чтобы избежать сбоев во всех целевых средах.

Windows Vista имеет несколько функций для поддержки приложений, требующих низкой задержки аудиопотоков. Как описано в компонентах аудио в режиме пользователя, приложения, выполняющие критически важные операции, могут вызывать функции службы планировщика мультимедиа (MMCSS), чтобы увеличить приоритет потока без запрета ресурсов ЦП для приложений с низким приоритетом. Кроме того, метод IAudioClient::Initialize поддерживает флаг AUDCLNT_STREAMFLAGS_EVENTCALLBACK, который позволяет потоку буферного обслуживания приложения запланировать выполнение, когда новый буфер становится доступным из звукового устройства. С помощью этих функций поток приложения может снизить неопределенность при выполнении, тем самым уменьшая риск сбоев в аудиопотоке с низкой задержкой.

Драйверы для старых звуковых адаптеров, скорее всего, будут использовать интерфейс драйвера устройства WaveCyclic или WavePci (DDI), в то время как драйверы для новых звуковых адаптеров, скорее всего, поддерживают DDI WaveRT. Для приложений в монопольном режиме драйверы WaveRT могут обеспечить лучшую производительность, чем драйверы WaveCyclic или WavePci, но драйверы WaveRT требуют дополнительных аппаратных возможностей. Эти возможности включают возможность совместного использования аппаратных буферов непосредственно с приложениями. При прямом совместном использовании системное вмешательство не требуется для передачи данных между монопольным приложением и звуковым оборудованием. В отличие от этого, драйверы WaveCyclic и WavePci подходят для старых, менее способных звуковых адаптеров. Эти адаптеры используют системное программное обеспечение для транспорта блоков данных (подключенных к пакетам запросов системного ввода-вывода или irPs) между буферами приложений и аппаратными буферами. Кроме того, USB-устройства используют системное программное обеспечение для передачи данных между буферами приложений и аппаратными буферами. Чтобы повысить производительность приложений в монопольном режиме, которые подключаются к звуковым устройствам, которые полагаются на систему для транспорта данных, WASAPI автоматически увеличивает приоритет системных потоков, которые передают данные между приложениями и оборудованием. WASAPI использует MMCSS для повышения приоритета потока. В Windows Vista, если системный поток управляет транспортом данных для потока воспроизведения звука в монопольном режиме с форматом PCM и периодом устройства менее 10 миллисекунд, WASAPI назначает имя задачи MMCSS "Pro Audio" потоку. Если период устройства потока превышает 10 миллисекундах или равен 10 миллисекундам, WASAPI назначает имя задачи MMCSS "Audio" потоку. Дополнительные сведения о DDK-идентификаторах WaveCyclic, WavePci и WaveRT см. в документации по Windows DDK. Сведения о выборе соответствующего периода устройства см. в разделе IAudioClient::GetDevicePeriod.

Как описано в элементах управления томами сеансов, WASAPI предоставляет интерфейсы ISimpleAudioVolume, IChannelAudioVolume и IAudioStreamVolume для управления уровнями томов аудиопотоков общего режима. Однако элементы управления в этих интерфейсах не влияют на потоки монопольного режима. Вместо этого приложения, которые управляют потоками в монопольном режиме, обычно используют интерфейс IAudioEndpointVolume в API EndpointVolume для управления уровнями томов этих потоков. Сведения об этом интерфейсе см. в разделе "Элементы управления томами конечной точки".

Для каждого устройства воспроизведения и устройства записи в системе пользователь может контролировать, может ли устройство использоваться в монопольном режиме. Если пользователь отключает монопольное использование устройства, устройство можно использовать для воспроизведения или записи только потоков общего режима.

Если пользователь включает монопольное использование устройства, пользователь также может контролировать, будет ли запрос приложения использовать устройство в монопольном режиме, что приведет к использованию устройства приложениями, которые в настоящее время могут воспроизводить или записывать потоки общего режима через устройство. Если выключается, запрос приложения на монопольное управление устройством завершается успешно, если устройство в настоящее время не используется, или если устройство используется в общем режиме, но запрос завершается ошибкой, если другое приложение уже имеет монопольный контроль над устройством. Если выключение отключено, запрос приложения на монопольное управление устройством завершается успешно, если устройство не используется в данный момент, но запрос завершается ошибкой, если устройство уже используется в общем режиме или в монопольном режиме.

В Windows Vista параметры по умолчанию для устройства конечной точки аудио приведены ниже.

  • Устройство можно использовать для воспроизведения или записи потоков монопольного режима.
  • Запрос на использование устройства для воспроизведения или записи потока в монопольном режиме превысят любой поток общего режима, который в настоящее время воспроизводится или записывается через устройство.

Изменение параметров монопольного режима воспроизведения или записи устройства

  1. Щелкните правой кнопкой мыши значок говорящего в области уведомлений, расположенной справа от панели задач, и выберите "Воспроизведение устройств " или "Устройства записи". (В качестве альтернативы запустите панель управления мультимедиа Windows, Mmsys.cpl из окна командной строки. Дополнительные сведения см. в разделе "Примечания" в DEVICE_STATE_XXX константы.)
  2. После появления окна "Звук" выберите "Воспроизведение" или "Запись". Затем выберите запись в списке имен устройств и нажмите кнопку "Свойства".
  3. После появления окна свойств нажмите кнопку "Дополнительно".
  4. Чтобы разрешить приложениям использовать устройство в монопольном режиме, проверка поле с меткой "Разрешить приложениям принимать монопольный контроль над этим устройством". Чтобы отключить монопольное использование устройства, снимите флажок проверка.
  5. Если включен монопольный режим использования устройства, можно указать, будет ли выполнен запрос на монопольное управление устройством, если устройство в настоящее время воспроизводит или записывает потоки общего режима. Чтобы обеспечить приоритет приложений в монопольном режиме по сравнению с приложениями общего режима, проверка поле с меткой "Предоставление приоритета приложений в монопольном режиме". Чтобы запретить приоритет приложений в монопольном режиме в общем режиме, снимите флажок проверка.

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

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

В предыдущем примере кода функция PlayExclusiveStream выполняется в потоке приложения, который обслуживает буферы конечных точек во время воспроизведения потока отрисовки. Функция принимает один параметр pMySource, который является указателем на объект, принадлежащий клиентскому классу MyAudioSource. Этот класс имеет две функции-члены, LoadData и SetFormat, которые вызываются в примере кода. MyAudioSource описан в разделе "Отрисовка потока".

Функция PlayExclusiveStream вызывает вспомогательную функцию GetStreamFormat, которая согласовывается с устройством отрисовки по умолчанию, чтобы определить, поддерживает ли устройство формат потока в монопольном режиме, который подходит для использования приложением. Код функции GetStreamFormat не отображается в примере кода; это связано с тем, что сведения о его реализации полностью зависят от требований приложения. Однако можно просто описать операцию функции GetStreamFormat— вызывает метод IAudioClient::IsFormatSupported один или несколько раз, чтобы определить, поддерживает ли устройство подходящий формат. Требования приложения определяют форматы GetStreamFormat, представленные методу IsFormatSupported , и порядок их отображения. Дополнительные сведения о IsFormatSupported см. в разделе "Форматы устройств".

После вызова GetStreamFormat функция PlayExclusiveStream вызывает метод IAudioClient::GetDevicePeriod , чтобы получить минимальный период устройства, поддерживаемый звуковым оборудованием. Затем функция вызывает метод IAudioClient::Initialize для запроса длительности буфера, равного минимальному периоду. Если вызов выполнен успешно, метод Initialize выделяет два буфера конечных точек, каждый из которых равен минимальному периоду. Позже, когда аудиопоток начинается, приложение и звуковое оборудование будут совместно использовать два буфера в режиме "ping-pong", то есть приложение записывает в один буфер, оборудование считывает из другого буфера.

Перед запуском потока функция PlayExclusiveStream выполняет следующие действия:

  • Создает и регистрирует дескриптор событий, с помощью которого он получит уведомления, когда буферы становятся готовыми к заполнению.
  • Заполняет первый буфер данными из источника звука, чтобы уменьшить задержку, начиная с момента запуска потока до момента прослушивания исходного звука.
  • Вызывает функцию AvSetMmThreadCharacteristics, чтобы запросить, чтобы MMCSS увеличил приоритет потока, в котором выполняется PlayExclusiveStream. (Когда поток перестанет работать, Вызов функции AvRevertMmThreadCharacteristics восстанавливает исходный приоритет потока.)

Дополнительные сведения об AvSetMmThreadCharacteristics и AvRevertMmThreadCharacteristics см. в документации по пакету SDK для Windows.

Во время выполнения потока каждая итерация цикла во время выполнения в предыдущем примере кода заполняет один буфер конечной точки. Между итерациями вызов функции WaitForSingleObject ожидает передачи сигнала дескриптора события. При сигнале дескриптора текст цикла выполняет следующие действия:

  1. Вызывает метод IAudioRenderClient::GetBuffer, чтобы получить следующий буфер.
  2. Заполняет буфер.
  3. Вызывает метод IAudioRenderClient::ReleaseBuffer, чтобы освободить буфер.

Дополнительные сведения о WaitForSingleObject см. в документации по пакету SDK для Windows.

Если звуковой адаптер управляется драйвером WaveRT, сигнал дескриптор событий привязан к уведомлениям передачи DMA из звукового оборудования. Для USB-звукового устройства или звукового устройства, управляемого драйвером WaveCyclic или WavePci, сигнал дескриптор событий привязан к завершениям IRPs, которые передают данные из буфера приложения в аппаратный буфер.

Приведенный выше пример кода отправляет звуковое оборудование и компьютерную систему к их ограничениям производительности. Во-первых, чтобы уменьшить задержку потока, приложение планирует использовать минимальный период устройства, который будет поддерживать звуковое оборудование. Во-вторых, чтобы поток надежно выполнялся в течение каждого периода устройства, вызов функции AvSetMmThreadCharacteristics задает для параметра TaskName значение "Pro Audio", которое в Windows Vista имеет имя задачи по умолчанию с наивысшим приоритетом. Рассмотрите возможность расслабления требований к времени приложения без ущерба для его полезности. Например, приложение может запланировать его поток обслуживания буфера, чтобы использовать период, превышающий минимальное значение. Более длительный период может безопасно разрешить использование более низкого приоритета потока.

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