低待機時間オーディオ

この記事では、Windows 10 でのオーディオ待機時間の変更について説明します。 アプリケーション開発者向けの API オプションと、待機時間の短いオーディオをサポートするために行うことができるドライバーの変更について説明します。 オーディオ待機時間は、サウンドが作成されてから聞こえるまでの遅延です。 オーディオ待機時間を短くすることは、次のようないくつかの重要なシナリオで重要です。

  • Pro オーディオ
  • 音楽制作
  • 通信
  • 仮想現実
  • ゲーム

この文書の目標は次のとおりです:

  1. Windows でのオーディオ待機時間のソースについて説明します。
  2. Windows 10 オーディオ スタックのオーディオ待機時間を短縮する変更について説明します。
  3. アプリケーション開発者とハードウェア製造元が、オーディオ待機時間が短いアプリケーションとドライバーを開発するために、新しいインフラストラクチャを利用する方法に関するリファレンスを提供します。

この記事には、次の内容が含まれます。

  1. インタラクティブなメディア作成シナリオ用の新しい AudioGraph API.
  2. 低待機時間をサポートするための WASAPI の変更。
  3. ドライバーの DDI の機能強化。

用語

用語 説明
レンダリングの待機時間 アプリケーションがオーディオ データのバッファーをレンダー API に送信するまで、スピーカーから聞こえるまで遅延します。
待機時間をキャプチャする マイクからサウンドがキャプチャされてから、アプリケーションで使用されているキャプチャ API に送信されるまでの遅延。
ラウンドトリップ遅延. マイクからサウンドがキャプチャされ、アプリケーションによって処理され、スピーカーにレンダリングするためにアプリケーションによって送信されるまでの遅延。 レンダリング待機時間 + キャプチャ待機時間とほぼ同じです。
タッチからアプリへの待機時間 ユーザーが画面をタップしてから、信号がアプリケーションに送信されるまでの遅延。
タッチとサウンドの待機時間 ユーザーが画面をタップするまでの遅延、イベントがアプリケーションに送信され、スピーカーを介して音が聞こえます。 これは、レンダリングの待機時間 + タッチからアプリへの待機時間と同じです。

Windows オーディオ スタック

次の図は、Windows オーディオ スタックの簡略化されたバージョンを示しています。

Diagram showing the low latency audio stack with apps, audio engine driver, and hardware.

レンダリング パスの待機時間の概要を次に示します。オーディオ処理オブジェクト

  1. アプリケーションがデータをバッファーに書き込む

  2. オーディオ エンジンは、バッファーからデータを読み取り、処理します。 また、オーディオ処理オブジェクト (APOs) の形式でオーディオ効果を読み込みます。 APOs の詳細については、Windows オーディオ処理オブジェクト を参照してください。

  3. APOs の待機時間は、APOs 内の信号処理によって異なります。

  4. Windows 10 より前のオーディオ エンジンの待機時間は、浮動小数点データを使用するアプリケーションの場合は最大 ~12 ms、整数データを使用するアプリケーションでは最大 ~6msでした。

  5. Windows 10 以降では、すべてのアプリケーションの遅延が ~1.3 ms に短縮されました。

  6. オーディオ エンジンは、処理されたデータをバッファーに書き込みます。

  7. Windows 10 より前は、バッファーは常に最大 10 ミリ秒に設定されていました。

  8. Windows 10 以降では、バッファー サイズはオーディオ ドライバーによって定義されます (バッファーの詳細については、この記事で後述します)。

  9. オーディオ ドライバーは、バッファーからデータを読み取り、ハードウェアに書き込みます。

  10. ハードウェアは、より多くのオーディオ効果の形で再びデータを処理することもできます。

  11. ユーザーがスピーカーから音声を聞きます。

キャプチャ パスの待機時間の概要を次に示します。

  1. マイクからオーディオがキャプチャされます。

  2. ハードウェアはデータを処理できます。 たとえば、オーディオ効果を追加します。

  3. ドライバーは、ハードウェアからデータを読み取り、バッファーにデータを書き込みます。

  4. Windows 10 より前では、このバッファーは常に 10 ミリ秒に設定されていました。

  5. Windows 10 以降では、バッファー サイズはオーディオ ドライバーによって定義されます (詳細については以下を参照)。

  6. オーディオ エンジンはバッファからデータを読み取り、処理します。 また、オーディオ処理オブジェクト (APOs) の形式でオーディオ効果を読み込みます。

  7. APOs の待機時間は、APOs 内の信号処理によって異なります。

  8. Windows 10 より前のオーディオ エンジンの待機時間は、浮動小数点データを使用するアプリケーションの場合は最大 ~6 ms、整数データを使用するアプリケーションでは最大 ~0msでした。

  9. Windows 10 以降では、すべてのアプリケーションの遅延が ~0ms に短縮されました。

  10. アプリケーションは、オーディオ エンジンが処理を終了するとすぐに、データを読み取ることができることを通知されます。 オーディオ スタックには、排他モードのオプションも用意されています。 その場合、データはオーディオ エンジンをバイパスし、ドライバーが読み取るバッファーにアプリケーションから直接移動します。 ただし、アプリケーションが排他的モードでエンドポイントを開いた場合、そのエンドポイントを使用してオーディオをレンダリングまたはキャプチャできる他のアプリケーションはありません。

低待機時間を必要とするアプリケーションのもう 1 つの一般的な代替手段は、排他モードを利用する ASIO (オーディオ ストリーム入力/出力) モデルを使用することです。 ユーザーがサードパーティの ASIO ドライバーをインストールした後、アプリケーションはアプリケーションから ASIO ドライバーに直接データを送信できます。 ただし、アプリケーションは ASIO ドライバーと直接通信するように記述する必要があります。

代替手段 (排他モードと ASIO) には、どちらも独自の制限があります。 待機時間は短くなりますが、独自の制限があります (前述の一部)。 その結果、オーディオ エンジンは、柔軟性を維持しながら待機時間を短縮するために変更されました。

オーディオ スタックの機能強化

Windows 10 以降は、待ち時間を短縮するために、次の 3 つの領域で強化されています。

  1. オーディオを使用するすべてのアプリケーションでは、Windows 8.1 と比較して、コードの変更やドライバーの更新を行わずに、ラウンド トリップ待機時間が 4.5 から 16 ミリ秒短縮されます (上記のセクションで説明したように)。
    1. 浮動小数点データを使用するアプリケーションでは、待機時間が 16 ミリ秒短くなります。
    2. 整数データを使用するアプリケーションでは、待機時間が 4.5 ミリ秒短くなります。
  2. ドライバーが更新されたシステムでは、ラウンド トリップの待機時間がさらに短くなります。
    1. ドライバーは、新しい DDI を使用して、Windows とハードウェアの間でデータを転送するために使用されるバッファーのサポートされているサイズを報告できます。 データ転送では、以前のバージョンの Windows と同様に、常に 10 ミリ秒のバッファーを使用する必要はありません。 代わりに、ドライバーは、たとえば、5 ミリ秒、3 ミリ秒、1 ミリ秒などの小さなバッファーを使用できるかどうかを指定できます。
    2. 待機時間が短いアプリケーションでは、新しいオーディオ API (AudioGraph または WASAPI) を使用して、ドライバーでサポートされているバッファー サイズのクエリを実行し、ハードウェアとの間のデータ転送に使用されるバッファー サイズを選択できます。
  3. アプリケーションが特定のしきい値を下回るバッファー サイズを使用してオーディオをレンダリングおよびキャプチャする場合、Windows は特別なモードに入り、オーディオ ストリーミングと他のサブシステム間の干渉を回避する方法でリソースを管理します。 これにより、オーディオ サブシステムの実行の中断が軽減され、オーディオのグリッチの確率が最小限に抑えられます。 アプリケーションがストリーミングを停止すると、Windows は通常の実行モードに戻ります。 オーディオ サブシステムは、次のリソースで構成されます。
    1. 待機時間の短いオーディオを処理しているオーディオ エンジン スレッド。
    2. ドライバーによって登録されたすべてのスレッドと割り込み (ドライバー リソースの登録に関するセクションで説明されている新しい DDI を使用)。
    3. 小さなバッファーを要求するアプリケーションの一部またはすべてのオーディオ スレッド、および小さなバッファーを要求したアプリケーションと同じオーディオ デバイス グラフ (たとえば、同じ信号処理モード) を共有するすべてのアプリケーションから:
  4. ストリーミング パスの AudioGraph コールバック。
  5. アプリケーションが WASAPI を使用している場合 リアルタイム作業キュー API または MFCreateMFByteStreamOnStreamEx に送信され、「オーディオ」または 「ProAudio」。

API の機能強化

次の 2 つの Windows 10 API は、低待機時間の機能を提供します。

使用する 2 つの API のうちどれを決定するには:

  • 新しいアプリケーション開発では、可能な限り AudioGraph を優先します。
  • 次の場合にのみ WASAPI を使用してください。
    • AudioGraph で提供されるコントロールよりも多くの制御が必要です。
    • AudioGraph で提供される待機時間よりも短い待機時間が必要です。

この記事の測定ツール セクションでは、受信トレイ HDAudio ドライバーを使用した Haswell システムからの特定の測定値を示します。

以降のセクションでは、各 API の待機時間が短い機能について説明します。 前のセクションで説明したように、システムが最小待機時間を実現するには、小さなバッファー サイズをサポートするドライバーを更新する必要があります。

AudioGraph

AudioGraph は Windows 10 以降の新しいユニバーサル Windows プラットフォーム API であり、対話型の音楽作成シナリオを簡単に実現することを目的としています。 AudioGraph は、いくつかのプログラミング言語 (C++、C#、JavaScript) で利用でき、シンプルで機能豊富なプログラミング モデルを備えています。

低遅延シナリオをターゲットにするために、AudioGraph は AudioGraphSettings::QuantumSizeSelectionMode プロパティを提供します。 このプロパティには、次の表に示す任意の値を指定できます。

Value 説明
SystemDefault バッファーを既定のバッファー サイズ (最大 10 ミリ秒) に設定します
LowestLatency ドライバーでサポートされている最小値にバッファーを設定します。
ClosestToDesired バッファー サイズを、DesiredSamplesPerQuantum プロパティで定義された値と等しいか、ドライバーでサポートされている DesiredSamplesPerQuantum に近い値に設定します。

AudioCreation サンプル は、低遅延のために AudioGraph を使用する方法を示しています。 次のコード スニペットは、最小バッファー サイズを設定する方法を示しています。

AudioGraphSettings settings = new AudioGraphSettings(AudioRenderCategory.Media);
settings.QuantumSizeSelectionMode = QuantumSizeSelectionMode.LowestLatency;
CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);

Windows オーディオ セッション API (WASAPI)

Windows 10 以降、WASAPI は次の機能に強化されています。

  • アプリケーションで、特定のオーディオ デバイスのオーディオ ドライバーでサポートされているバッファー サイズの範囲 (つまり、周期性の値) を検出できるようにします。 これにより、アプリケーションは、共有モードでストリームを開くときに、既定のバッファー サイズ (10 ミリ秒) または小さなバッファー (10 ミリ秒未満) を選択できます。 アプリケーションでバッファー サイズが指定されていない場合は、既定のバッファー サイズが使用されます。
  • アプリケーションがオーディオ エンジンの現在の形式と周期性を検出できるようにします。 これにより、アプリケーションはオーディオ エンジンの現在の設定にスナップできます。
  • オーディオ エンジンによる再サンプリングを行わずに、指定した形式でレンダリング/キャプチャすることをアプリで指定できるようにする

上記の機能は、すべての Windows デバイスで使用できます。 ただし、十分なリソースと更新されたドライバーを備えた特定のデバイスでは、他のデバイスよりも優れたユーザー エクスペリエンスが提供されます。

上記の機能は、 IAudioClient2 から派生した IAudioClient3 と呼ばれる新しいインターフェイスによって提供されます。

IAudioClient3 では 、次の 3 つのメソッドが定義されています。

Method 説明
GetCurrentSharedModeEnginePeriod オーディオ エンジンの現在の形式と周期性を返します。
GetSharedModeEnginePeriod 指定されたストリーム形式のエンジンでサポートされている周期性の範囲を返します。
InitializeSharedAudioStream 指定した周期性で共有ストリームを初期化します。

WASAPIAudio サンプル は、低待機時間で IAudioClient3 を使用する方法を示しています。

次のコード スニペットは、システムでサポートされている最も短い待機時間の設定で音楽作成アプリを動作させる方法を示しています。

// 1. Activation

// Get a string representing the Default Audio (Render|Capture) Device
m_DeviceIdString = MediaDevice::GetDefaultAudio(Render|Capture)Id(
Windows::Media::Devices::AudioDeviceRole::Default );

// This call must be made on the main UI thread.  Async operation will call back to
// IActivateAudioInterfaceCompletionHandler::ActivateCompleted, which must be an agile // interface implementation
hr = ActivateAudioInterfaceAsync( m_DeviceIdString->Data(), __uuidof(IAudioClient3),
nullptr, this, &asyncOp );

// 2. Setting the audio client properties – note that low latency offload is not supported

AudioClientProperties audioProps = {0};
audioProps.cbSize = sizeof( AudioClientProperties );
audioProps.eCategory = AudioCategory_Media;

// if the device has System.Devices.AudioDevice.RawProcessingSupported set to true and you want to use raw mode
// audioProps.Options |= AUDCLNT_STREAMOPTIONS_RAW;
//
// if it is important to avoid resampling in the audio engine, set this flag
// audioProps.Options |= AUDCLNT_STREAMOPTIONS_MATCH_FORMAT;


hr = m_AudioClient->SetClientProperties( &audioProps ); if (FAILED(hr)) { ... }

// 3. Querying the legal periods

hr = m_AudioClient->GetMixFormat( &mixFormat ); if (FAILED(hr)) { ... }

hr = m_AudioClient->GetSharedModeEnginePeriod(wfx, &defaultPeriodInFrames, &fundamentalPeriodInFrames, &minPeriodInFrames, &maxPeriodInFrames); if (FAILED(hr)) { ... }

// legal periods are any multiple of fundamentalPeriodInFrames between
// minPeriodInFrames and maxPeriodInFrames, inclusive
// the Windows shared-mode engine uses defaultPeriodInFrames unless an audio client // has specifically requested otherwise

// 4. Initializing a low-latency client

hr = m_AudioClient->InitializeSharedAudioStream(
         AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
         desiredPeriodInFrames,
         mixFormat,
         nullptr); // audio session GUID
         if (AUDCLNT_E_ENGINE_PERIODICITY_LOCKED == hr) {
         /* engine is already running at a different period; call m_AudioClient->GetSharedModeEnginePeriod to see what it is */
         } else if (FAILED(hr)) {
             ...
         }

// 5. Initializing a client with a specific format (if the format needs to be different than the default format)

AudioClientProperties audioProps = {0};
audioProps.cbSize = sizeof( AudioClientProperties );
audioProps.eCategory = AudioCategory_Media;
audioProps.Options |= AUDCLNT_STREAMOPTIONS_MATCH_FORMAT;

hr = m_AudioClient->SetClientProperties( &audioProps );
if (FAILED(hr)) { ... }

hr = m_AudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, appFormat, &closest);
if (S_OK == hr) {
       /* device supports the app format */
} else if (S_FALSE == hr) {
       /* device DOES NOT support the app format; closest supported format is in the "closest" output variable */
} else {
       /* device DOES NOT support the app format, and Windows could not find a close supported format */
}

hr = m_AudioClient->InitializeSharedAudioStream(
       AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
       defaultPeriodInFrames,
       appFormat,
       nullptr); // audio session GUID
if (AUDCLNT_E_ENGINE_FORMAT_LOCKED == hr) {
       /* engine is already running at a different format */
} else if (FAILED(hr)) {
       ...
}

また、Microsoft では、WASAPI を使用するアプリケーションに対して、リアルタイム ワーク キュー APII または MFCreateMFByteStreamOnStreamEx も使用して作業項目を作成し、独自のスレッドではなくオーディオまたはプロ オーディオとしてタグ付けすることをお勧めします。 これにより、Windows は非オーディオ サブシステムの干渉を回避する方法でそれらを管理できます。 これに対し、すべての AudioGraph スレッドは Windows によって自動的に正しく管理されます。 WASAPIAudio サンプルの次のコード スニペットは、MF 作業キュー API の使用方法を示しています。

// Specify Source Reader Attributes
Attributes->SetUnknown( MF_SOURCE_READER_ASYNC_CALLBACK, static_cast<IMFSourceReaderCallback *>(this) );
    if (FAILED( hr ))
    {
        goto exit;
    }
    Attributes->SetString( MF_READWRITE_MMCSS_CLASS_AUDIO, L"Audio" );
    if (FAILED( hr ))
    {
        goto exit;
    }
    Attributes->SetUINT32( MF_READWRITE_MMCSS_PRIORITY_AUDIO, 0 );
    if (FAILED( hr ))
    {
        goto exit;
    }
    // Create a stream from IRandomAccessStream
    hr = MFCreateMFByteStreamOnStreamEx (reinterpret_cast<IUnknown*>(m_ContentStream), &ByteStream );
    if ( FAILED( hr ) )
    {
        goto exit;
    }
    // Create source reader
    hr = MFCreateSourceReaderFromByteStream( ByteStream, Attributes, &m_MFSourceReader );

または、次のコード スニペットは、RT Work Queue API の使用方法を示しています。

#define INVALID_WORK_QUEUE_ID 0xffffffff
DWORD g_WorkQueueId = INVALID_WORK_QUEUE_ID;
//#define MMCSS_AUDIO_CLASS    L"Audio"
//#define MMCSS_PROAUDIO_CLASS L"ProAudio"

STDMETHODIMP TestClass::GetParameters(DWORD* pdwFlags, DWORD* pdwQueue)
{
       HRESULT hr = S_OK;
       *pdwFlags = 0;
       *pdwQueue = g_WorkQueueId;
       return hr;
}

//-------------------------------------------------------
STDMETHODIMP TestClass::Invoke(IRtwqAsyncResult* pAsyncResult)
{
       HRESULT hr = S_OK;
       IUnknown *pState = NULL;
       WCHAR className[20];
       DWORD  bufferLength = 20;
       DWORD taskID = 0;
       LONG priority = 0;

       printf("Callback is invoked pAsyncResult(0x%0x)  Current process id :0x%0x Current thread id :0x%0x\n", (INT64)pAsyncResult, GetCurrentProcessId(), GetCurrentThreadId());

       hr = RtwqGetWorkQueueMMCSSClass(g_WorkQueueId, className, &bufferLength);
       IF_FAIL_EXIT(hr, Exit);

       if (className[0])
       {
              hr = RtwqGetWorkQueueMMCSSTaskId(g_WorkQueueId, &taskID);
              IF_FAIL_EXIT(hr, Exit);

              hr = RtwqGetWorkQueueMMCSSPriority(g_WorkQueueId, &priority);
              IF_FAIL_EXIT(hr, Exit);
              printf("MMCSS: [%ws] taskID (%d) priority(%d)\n", className, taskID, priority);
       }
       else
       {
              printf("non-MMCSS\n");
       }
       hr = pAsyncResult->GetState(&pState);
       IF_FAIL_EXIT(hr, Exit);

Exit:
       return S_OK;
}
//-------------------------------------------------------

int _tmain(int argc, _TCHAR* argv[])
{
       HRESULT hr = S_OK;
       HANDLE signalEvent;
       LONG Priority = 1;
       IRtwqAsyncResult *pAsyncResult = NULL;
       RTWQWORKITEM_KEY workItemKey = NULL;;
       IRtwqAsyncCallback *callback = NULL;
       IUnknown *appObject = NULL;
       IUnknown *appState = NULL;
       DWORD taskId = 0;
       TestClass cbClass;
       NTSTATUS status;

       hr = RtwqStartup();
       IF_FAIL_EXIT(hr, Exit);

       signalEvent = CreateEvent(NULL, true, FALSE, NULL);
       IF_TRUE_ACTION_EXIT(signalEvent == NULL, hr = E_OUTOFMEMORY, Exit);

       g_WorkQueueId = RTWQ_MULTITHREADED_WORKQUEUE;

       hr = RtwqLockSharedWorkQueue(L"Audio", 0, &taskId, &g_WorkQueueId);
       IF_FAIL_EXIT(hr, Exit);

       hr = RtwqCreateAsyncResult(NULL, reinterpret_cast<IRtwqAsyncCallback*>(&cbClass), NULL, &pAsyncResult);
       IF_FAIL_EXIT(hr, Exit);

       hr = RtwqPutWaitingWorkItem(signalEvent, Priority, pAsyncResult, &workItemKey);
       IF_FAIL_EXIT(hr, Exit);

       for (int i = 0; i < 5; i++)
       {
              SetEvent(signalEvent);
              Sleep(30);
              hr = RtwqPutWaitingWorkItem(signalEvent, Priority, pAsyncResult, &workItemKey);
              IF_FAIL_EXIT(hr, Exit);
    }

Exit:
       if (pAsyncResult)
       {
              pAsyncResult->Release();
       }

      if (INVALID_WORK_QUEUE_ID != g_WorkQueueId)
      {
        hr = RtwqUnlockWorkQueue(g_WorkQueueId);
        if (FAILED(hr))
        {
            printf("Failed with RtwqUnlockWorkQueue 0x%x\n", hr);
        }

        hr = RtwqShutdown();
        if (FAILED(hr))
        {
            printf("Failed with RtwqShutdown 0x%x\n", hr);
        }
      }

       if (FAILED(hr))
       {
          printf("Failed with error code 0x%x\n", hr);
       }
       return 0;
}

最後に、WASAPI を使用するアプリケーション開発者は、各ストリームの機能に基づいて、ストリームにオーディオ カテゴリと生信号処理モードを使用するかどうかをタグ付けする必要があります。 影響が理解されない限り、すべてのオーディオ ストリームで生の信号処理モードを使用しないことをお勧めします。 生モードでは、OEM によって選択されたすべての信号処理がバイパスされるため、次のことが行われます。

  • 特定のエンドポイントのレンダリング信号は、最適ではない可能性があります。
  • キャプチャ 信号は、アプリケーションが理解できない形式になる可能性があります。
  • 待機時間が改善される可能性があります。

ドライバーの改善

オーディオ ドライバーが低待機時間をサポートするために、Windows 10 以降には次の機能が用意されています。

  1. [必須]各モードでサポートされる最小バッファー サイズを宣言します。
  2. [省略可能ですが、推奨]ドライバーと Windows の間のデータ フローの調整を改善します。
  3. [省略可能ですが、推奨]ドライバー リソース (割り込み、スレッド) を登録して、待機時間の短いシナリオで Windows で保護できるようにします。 受信トレイ HDAudio バス ドライバーによって列挙される HDAudio ミニポート関数ドライバー hdaudbus.sys HDAudio 割り込みを登録する必要はありません。これは、hdaudbus.sysによって既に行われているのでです。 ただし、ミニポート ドライバーが独自のスレッドを作成する場合は、それらを登録する必要があります。

次の 3 つのセクションでは、各新機能について詳しく説明します。

最小バッファサイズを宣言する

ドライバーは、Windows、ドライバー、およびハードウェア間でオーディオ データを移動するときに、さまざまな制約の下で動作します。 これらの制約は、メモリとハードウェアの間でデータを移動する物理的なハードウェアトランスポート、またはハードウェアまたは関連する DSP 内の信号処理モジュールが原因である可能性があります。

Windows 10 バージョン 1607 以降、ドライバーは、DEVPKEY_KsAudio_PacketSize_Constraints2 デバイス プロパティを使用してバッファー サイズ機能を表現できます。 このプロパティを使用すると、ドライバーでサポートされる絶対最小バッファー サイズと、各信号処理モードの特定のバッファー サイズの制約を定義できます。 モード固有の制約は、ドライバーの最小バッファー サイズよりも大きくする必要があります。それ以外の場合は、オーディオ スタックによって無視されます。

たとえば、次のコード スニペットは、ドライバーでサポートされる最小バッファー サイズが 2 ミリ秒であることをドライバーが宣言する方法を示していますが、既定のモードでは 128 フレームがサポートされています。これは、48 kHz サンプル レートを想定した場合は 3 ミリ秒に相当します。

 
//
// Describe buffer size constraints for WaveRT buffers
//
static struct
{
    KSAUDIO_PACKETSIZE_CONSTRAINTS2 TransportPacketConstraints;
    KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT AdditionalProcessingConstraints[1];
} SysvadWaveRtPacketSizeConstraintsRender =
{
    {
        2 * HNSTIME_PER_MILLISECOND,                // 2 ms minimum processing interval
        FILE_BYTE_ALIGNMENT,                        // 1 byte packet size alignment
        0,                                          // no maximum packet size constraint
        2,                                          // 2 processing constraints follow
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_DEFAULT,          // constraint for default processing mode
            128,                                                // 128 samples per processing frame
            0,                                                  // NA hns per processing frame
        },
    },
    {
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_MOVIE,            // constraint for movie processing mode
            1024,                                               // 1024 samples per processing frame
            0,                                                  // NA hns per processing frame
        },
    }
};

これらの構造の詳細については、次の記事を参照してください。

また、sysvad サンプル では、ドライバーが各モードの最小バッファーを宣言するために、これらのプロパティを使用する方法を示します。

ドライバーと OS の間の調整を改善する

このセクションで説明する DDI を使用すると、ドライバーは次のことが可能になります。

  • コーデック リンクの位置に基づいて OS が推測するのではなく、バッファーの半分 (パケット) を Windows で使用できるかどうかを明確に示します。 これにより、Windows はオーディオの不具合から迅速に回復できます。
  • 必要に応じて、WaveRT バッファーとの間のデータ転送を最適化または簡略化します。 ここでの利点の量は、DMA エンジンの設計または WaveRT バッファーと (場合によっては DSP) ハードウェア間のその他のデータ転送メカニズムによって異なります。
  • "バースト" は、ドライバーが内部的にキャプチャされたデータを蓄積している場合に、リアルタイムよりも高速にデータをキャプチャしました。 これは主に音声アクティブ化シナリオを対象としていますが、通常のストリーミング中にも適用できます。
  • Windows の推測ではなく、現在のストリームの位置に関するタイムスタンプ情報を指定します。これにより、正確な位置情報を得る可能性があります。

この DDI は、DSP が使用される場合に役立ちます。 ただし、標準の HD オーディオ ドライバーやその他の単純な循環 DMA バッファー 設計では、ここに記載されているこれらの新しい DDI に大きな利点が見つからない場合があります。

いくつかのドライバー ルーチンは、デバイスによってサンプルがキャプチャまたは提示された時刻を反映する Windows パフォーマンス カウンターのタイムスタンプを返します。

複雑な DSP パイプラインと信号処理を備えるデバイスでは、正確なタイムスタンプの計算は困難な場合があり、慎重に行う必要があります。 タイムスタンプには、Windows から DSP にサンプルが転送された時刻は反映されません。

パフォーマンス カウンターの値を計算するために、ドライバーと DSP では、次のメソッドの一部が使用される場合があります。

  • DSP 内で、いくつかの内部 DSP ウォール クロックを使用してサンプル タイムスタンプを追跡します。
  • ドライバーと DSP の間で、Windows パフォーマンス カウンターと DSP ウォール クロックの間の相関関係を計算します。 この手順は、単純なもの (ただし、精度は低い) から、かなり複雑なものや新規なもの (ただし、より正確なもの) まで多岐に分けることができます。
  • 別の方法で遅延が考慮されない限り、信号処理アルゴリズム、パイプライン、またはハードウェアのトランスポートに起因する一定の遅延を考慮に入れます。

sysvad サンプ ルは、上記の DDI の使用方法を示しています。

ドライバー リソースを登録する

障害のない操作を確実に行うには、オーディオ ドライバーがストリーミング リソースを Portcls に登録する必要があります。 これにより、Windows はリソースを管理して、オーディオ ストリーミングとその他のサブシステム間の干渉を回避できます。

ストリーム リソースは、オーディオ ストリームを処理したり、オーディオ データ フローを確保したりするためにオーディオ ドライバーによって使用されるすべてのリソースです。 サポートされているストリーム リソースは、割り込みとドライバー所有のスレッドの 2 種類のみです。 オーディオ ドライバーは、リソースの作成後にリソースを登録し、リソースを削除する前にリソースの登録を解除する必要があります。

オーディオ ドライバーは、ドライバーが読み込まれるときに初期化時、または実行時にリソースを登録できます。たとえば、I/O リソースの再調整がある場合などです。 Portcls では、グローバル状態を使用して、すべてのオーディオ ストリーミング リソースを追跡します。

非常に短い待機時間のオーディオを必要とするユース ケースでは、Windows は、オーディオ ドライバーの登録済みリソースを他の OS、アプリケーション、ハードウェア アクティビティからの干渉から分離しようとします。 OS およびオーディオ サブシステムは、オーディオ ドライバーのリソースの登録を除き、オーディオ ドライバーと対話することなく、必要に応じてこれを行います。

ストリーム リソースを登録するこの要件は、ストリーミング パイプライン パス内のすべてのドライバーが、直接または間接的に Portcls にリソースを登録する必要があることを意味します。 オーディオ ミニポート ドライバーには、次のオプションがあります。

  • オーディオ ミニポート ドライバーは、スタックの一番下のドライバー (h/w を直接インターフェイス) です。この場合、ドライバーはそのストリーム リソースを認識し、それらを Portcls に登録できます。
  • オーディオ ミニポート ドライバーは、他のドライバー (オーディオ バス ドライバーの例) の助けを借りてオーディオをストリーミングしています。 これらの他のドライバーでは、Portcls に登録する必要があるリソースも使用されます。 これらの並列/バス ドライバー スタックは、オーディオ ミニポート ドライバーがこの情報を収集するために使用するパブリック (または 1 つのベンダーがすべてのドライバーを所有している場合はプライベート インターフェイス) を公開できます。
  • オーディオ ミニポート ドライバーは、他のドライバー (hdaudbus など) の助けを借りてオーディオをストリーミングしています。 これらの他のドライバーでは、Portcls に登録する必要があるリソースも使用されます。 これらの並列/バス ドライバーは、Portcls とリンクし、リソースを直接登録できます。 オーディオ ミニポート ドライバーは、これらの他の並列/バス デバイス (PDO) のリソースに依存していることを Portcls に知らせる必要があります。 HD オーディオ インフラストラクチャでは、このオプションを使用します。つまり、HD オーディオ バス ドライバーは Portcls とリンクし、次の手順を自動的に実行します。
    • バス ドライバーのリソースを登録します。
    • は、子のリソースが親のリソースに依存していることを Portcls に通知します。 HD オーディオ アーキテクチャでは、オーディオ ミニポート ドライバーは、独自のドライバー所有のスレッド リソースを登録するだけで済みます。

注:

  • 受信トレイ HDAudio バス ドライバーによって列挙される HDAudio ミニポート関数ドライバー hdaudbus.sys HDAudio 割り込みを登録する必要はありません。これは、hdaudbus.sysによって既に行われているのでです。 ただし、ミニポート ドライバーが独自のスレッドを作成する場合は、それらを登録する必要があります。
  • ストリーミング リソースを登録するためにのみ Portcls とリンクするドライバーは、wdmaudio.inf を含むように INF を更新し、portcls.sys (および依存ファイル) をコピーする必要があります。 wdmaudio.inf には、これらのファイルのみをコピーする新しい INF コピー セクションが定義されています。
  • Windows 10 以降でのみ実行されるオーディオ ドライバーは、次にハードリンクできます。
  • 下位レベルの OS で実行する必要があるオーディオ ドライバーは、次のインターフェイスを使用できます (ミニポートは、IID_IPortClsStreamResourceManager インターフェイスの QueryInterface を呼び出し、PortCls がインターフェイスをサポートしている場合にのみ、そのリソースを登録できます)。
  • これらの DDI では、次の列挙体と構造体を使用します。

最後に、リソースを登録する唯一の目的で PortCls をリンクするドライバーは、inf の DDInstall セクションに次の 2 行を追加する必要があります。 オーディオ ミニポート ドライバーは、wdmaudio.inf に既にインクルード/ニーズがあるため、これを必要としません。

[<install-section-name>]
Include=wdmaudio.inf
Needs=WDMPORTCLS.CopyFilesOnly

上記の行では、PortCls とその依存ファイルがインストールされていることを確認します。

測定ツール

ラウンドトリップ遅延を測定するために、ユーザーはスピーカーを介してパルスを再生し、マイクを介してそれらをキャプチャするツールを利用することができます。 次のパスの遅延を測定します。

  1. アプリケーションはレンダー API (AudioGraph または WASAPI) を呼び出してパルスを再生します
  2. オーディオはスピーカーを介して再生されます
  3. 音声はマイクからキャプチャされます
  4. キャプチャ API (AudioGraph または WASAPI) によってパルスが検出されます。さまざまなバッファー サイズのラウンドトリップ待機時間を測定するには、小さなバッファーをサポートするドライバーをインストールする必要があります。 受信トレイ HDAudio ドライバーは、128 サンプル (2.66ms@48kHz) から 480 サンプル (10ms@48kHz) までのバッファー サイズをサポートするように更新されました。 次の手順では、受信トレイ HDAudio ドライバー (Windows 10 以降のすべての SKU の一部) をインストールする方法を示します。
  • デバイス マネージャーを開始します。
  • サウンド ビデオおよびゲーム コントローラー で、内部スピーカーに対応するデバイスをダブルクリックします。
  • 次のウィンドウで、ドライバー タブに移動します。
  • ドライバーの更新 を選択し、>コンピューターでドライバー ソフトウェアを参照する を選択します。この>コンピューターのデバイス ドライバーの一覧 から選択します。>高解像度オーディオ デバイス を選択し、次へ を選択します。
  • "ドライバーの更新に関する警告" というタイトルのウィンドウが表示されたら、 [はい] を選択します。
  • 閉じるを選択します
  • システムを再起動するように求められたら、はい を選択して再起動します。
  • 再起動後、システムはサード パーティ製コーデック ドライバーではなく、受信トレイの Microsoft HDAudio ドライバーを使用します。 オーディオ コーデックに最適な設定を使用する場合は、そのドライバーにフォールバックできるように、前に使用していたドライバーを覚えておいてください。

Graph illustrating roundtrip latency differences between WASAPI and AudioGraph for various buffer sizes.

WASAPI と AudioGraph の待機時間の違いは、次の理由によるものです。

  • AudioGraph は、WASAPI によって提供されないレンダリングとキャプチャを同期するために、キャプチャ側に待機時間のバッファーを 1 つ追加します。 この追加により、AudioGraph を使用して記述されたアプリケーションのコードが簡略化されます。
  • システムが 6 ミリ秒を超えるバッファーを使用している場合、AudioGraph のレンダー側にはもう 1 つの待機時間バッファーがあります。
  • AudioGraph には、オーディオ効果のキャプチャを無効にするオプションはありません。

サンプル

よく寄せられる質問

すべてのアプリケーションで新しい API を使用して待機時間を短くする方が良いでしょうか。 待ち時間が短いと、常にユーザー エクスペリエンスが向上するとは限りませんか?

その必要は必ずしもありません。 待ち時間が短い場合は、次のトレードオフがあります。

  • 待ち時間が短いということは、消費電力が高いことを意味します。 システムが 10 ミリ秒のバッファーを使用している場合、CPU は 10 ミリ秒ごとに起動し、データ バッファーを満たし、スリープ状態になります。 ただし、システムが 1 ミリ秒のバッファーを使用している場合は、CPU が 1 ミリ秒ごとに起動することを意味します。 2 番目のシナリオでは、CPU がより頻繁に起動し、電力消費量が増加することを意味します。 これにより、バッテリ寿命が低下します。
  • ほとんどのアプリケーションは、最適なユーザー エクスペリエンスを提供するためにオーディオ効果に依存しています。 たとえば、メディア プレーヤーは、忠実度の高いオーディオを提供したいと考えています。 通信アプリケーションでは、エコーとノイズを最小限に抑える必要があります。 これらの種類のオーディオ効果をストリームに追加すると、待機時間が長くなります。 これらのアプリケーションは、オーディオ待機時間よりもオーディオ品質に関心があります。

要約すると、アプリケーションの種類ごとに、オーディオ待機時間に関するニーズが異なります。 アプリケーションで低待機時間が必要ない場合は、待ち時間を短くするために新しい API を使用しないでください。

Windows 10 以降に更新されるすべてのシステムは、小さなバッファーをサポートするように自動的に更新されますか? すべてのシステムで同じ最小バッファー サイズがサポートされますか?

いいえ。システムが小さなバッファーをサポートするには、ドライバーを更新する必要があります。 小さなバッファーをサポートするために更新するシステムを決定するのは、OEM の判断です。 また、新しいシステムでは、古いシステムよりも小さなバッファーをサポートする可能性が高くなります。 新しいシステムの待機時間は、古いシステムよりも低くなる可能性が最も高くなります。

ドライバーが小さなバッファー サイズをサポートしている場合、Windows 10 以降のすべてのアプリケーションでは、オーディオのレンダリングとキャプチャに小さなバッファーが自動的に使用されますか?

いいえ。既定では、Windows 10 以降のすべてのアプリケーションでは、オーディオのレンダリングとキャプチャに 10 ミリ秒のバッファーが使用されます。 アプリケーションで小さなバッファーを使用する必要がある場合は、それを行うために、新しい AudioGraph 設定または WASAPI IAudioClient3 インターフェイスを使用する必要があります。 ただし、1 つのアプリケーションが小さなバッファーの使用を要求した場合、オーディオ エンジンはその特定のバッファー サイズを使用してオーディオの転送を開始します。 その場合、同じエンドポイントとモードを使用するすべてのアプリケーションは、その小さなバッファー サイズに自動的に切り替わります。 待機時間の短いアプリケーションが終了すると、オーディオ エンジンは再び 10 ミリ秒のバッファーに切り替わります。