共用方式為


低延遲音訊

本文討論 Windows 10 中的音訊延遲變更。 它涵蓋應用程式開發人員的 API 選項,以及可用來支援低延遲音訊的驅動程式變更。 音訊延遲是建立聲音的時間和聽到聲音的時間之間的延遲。 對於數個主要案例而言,低音訊延遲很重要,例如:

  • 專業音訊
  • 音樂創作
  • 通信
  • 虛擬實境
  • 遊戲

本檔案的目標是:

  1. 描述 Windows 中音訊延遲的來源。
  2. 說明減少 Windows 10 音訊堆疊中音訊延遲的變更。
  3. 提供應用程式開發人員和硬體製造商如何利用新基礎結構的參考,以開發低音訊延遲的應用程式和驅動程式。

本文涵蓋:

  1. 適用於互動式和媒體創作情境的 AudioGraph API。
  2. WASAPI 中的變更,以支援低延遲。
  3. 驅動程式 DIS 中的增強功能。

術語

術語 描述
轉譯延遲 應用程式將音訊數據的緩衝區提交至渲染 API 與音訊數據從揚聲器被聽到之間的時間延遲。
擷取延遲 將聲音從麥克風捕捉到並傳送至應用程式所使用的擷取 API 之間的延遲時間。
往返延遲 從麥克風擷取聲音的時間延遲,由應用程式處理,並由應用程式提交,以便轉譯至喇叭。 其大致等於呈現延遲 + 擷取延遲。
觸控至應用程式延遲 從用戶點擊螢幕到訊號傳送至應用程式的時間延遲。
觸控到音效延遲 用戶點擊螢幕、事件傳送到應用程式以及透過喇叭聽到聲音之間存在延遲。 它等於轉譯延遲 + 觸控到應用程式延遲。

Windows 音訊堆疊

下圖顯示簡化版本的 Windows 音訊堆疊。

圖表,顯示具有應用程式、音訊引擎驅動程式和硬體的低延遲音訊堆疊。

以下是轉譯路徑中延遲的摘要:音訊處理物件

  1. 應用程式會將數據寫入緩衝區

  2. 音訊引擎會從緩衝區讀取數據並加以處理。 它也會以音訊處理物件 (APOs) 的形式載入音訊效果。 如需 APO 的詳細資訊,請參閱 Windows 音訊處理物件

  3. APOs 的延遲會根據APOs內的訊號處理而有所不同。

  4. 在 Windows 10 之前,音訊引擎的延遲等於 ~12 毫秒,適用於使用浮點數據的應用程式,而使用整數數據的應用程式則等於 ~6 毫秒

  5. 在 Windows 10 和更新版本中,所有應用程式的延遲已縮減為 1.3 毫秒

  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 毫秒,適用於使用浮點數據的應用程式,而使用整數數據的應用程式延遲則約為 0 毫秒。

  9. 在 Windows 10 和更新版本中,所有應用程式的延遲已縮減為 ~0 毫秒。

  10. 應用程式會在音訊引擎完成處理後立即收到數據可供讀取的訊號。 音訊堆疊也提供獨佔模式的選項。 在此情況下,資料會繞過音訊引擎,直接從應用程式進入驅動程式讀取的緩衝區。 不過,如果應用程式以獨佔模式開啟端點,則沒有其他應用程式可以使用該端點來轉譯或擷取音訊。

需要低延遲的應用程式的另一個熱門替代方案是使用 ASIO(音訊串流輸入/輸出)模型,其使用獨佔模式。 在使用者安裝第三方 ASIO 驅動程式之後,應用程式可以將數據直接從應用程式傳送至 ASIO 驅動程式。 不過,應用程式必須以直接與 ASIO 驅動程式交談的方式撰寫。

這兩種替代方案(獨佔模式和 ASIO)都有自己的限制。 它們提供低延遲,但他們有自己的限制(其中一些如上所述)。 因此,音訊引擎已經過修改,以降低延遲,同時保留彈性。

音訊堆疊改進

Windows 10 和更新版本已在三個區域中增強,以減少延遲:

  1. 相較於 Windows 8.1,使用音訊的所有應用程式在無需任何程式碼更改或驅動程式更新的情況下,都會看到回路延遲減少 4.5-16 毫秒(如上一節所述)。
    1. 使用浮點數據的應用程式將具有減少16毫秒的延遲。
    2. 使用整數數據的應用程式會有 4.5 毫秒更低的延遲。
  2. 具有更新驅動程式的系統可提供較低的來回延遲:
    1. 驅動程式可以使用低延遲 DIS 來報告用來在 Windows 與硬體之間傳輸數據的緩衝區支援大小。 數據傳輸不需要一律使用 10 毫秒的緩衝區,就像在舊版 Windows 中所做的一樣。 相反地,驅動程式可以指定是否可以使用小型緩衝區,例如 5 毫秒、3 毫秒、1 毫秒等。
    2. 需要低延遲的應用程式可以使用低延遲音訊 API (AudioGraph 或 WASAPI),來查詢驅動程式支援的緩衝區大小,並選取將用於從硬體往返數據傳輸的緩衝區大小。
  3. 當應用程式使用低於特定閾值的緩衝區大小來轉譯和擷取音訊時,Windows 會進入特殊模式,以避免音訊串流和其他子系統之間的干擾的方式來管理其資源。 這可減少音訊子系統執行中斷,並將音訊故障的機率降到最低。 當應用程式停止串流處理時,Windows 會回到其正常執行模式。 音訊子系統包含下列資源:
    1. 處理低延遲音訊的音訊引擎線程。
    2. 驅動程式已註冊的所有線程和中斷(使用驅動程序資源註冊一節中所述的低延遲 DIS)。
    3. 來自要求小型緩衝區之應用程式的部分或所有音訊執行緒,以及來自所有與任何要求小型緩衝區的應用程式共用相同音訊裝置圖形(例如,相同的訊號處理模式)的應用程式:
  4. 串流路徑上的 AudioGraph 回調函數。
  5. 如果應用程式使用 WASAPI,則只有那些提交到 Real-Time 工作佇列 API 的工作專案MFCreateMFByteStreamOnStreamEx 並被標記為「音訊」或「ProAudio」的項目會被處理。

API 改進

下列兩個 Windows 10 API 提供低延遲功能:

若要判斷要使用哪兩個 API:

  • 盡可能支援 AudioGraph,以便進行新的應用程式開發。
  • 只有在下列專案時才使用 WASAPI:
    • 您需要比 AudioGraph 所提供的更多的控制權。
    • 您需要比 AudioGraph 所提供的低延遲。

本文的 測量工具 部分會顯示使用內建 HDAudio 驅動程式的 Haswell 系統的特定測量數據。

下列各節將說明每個 API 中的低延遲功能。 如上一節所述,為了讓系統達到最低延遲,它必須有支援小型緩衝區大小的更新驅動程式。

AudioGraph

AudioGraph 是 Windows 10 和更新版本中的通用 Windows 平臺 API,旨在輕鬆實現互動式和音樂創作案例。 AudioGraph 提供數種程式設計語言(C++、C#、JavaScript),而且具有簡單且功能豐富的程序設計模型。

為了以低延遲案例為目標,AudioGraph 提供 AudioGraphSettings::QuantumSizeSelectionMode 屬性。 此屬性可以是下表所示的任何值:

價值 描述
系統預設 將緩衝區設定為預設緩衝區大小 (~10 毫秒)
最低延遲 將緩衝區設定為驅動程序支援的最小值
最接近目標 將緩衝區大小設定為等於 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 裝置。 不過,某些具有足夠資源和更新驅動程式的裝置可提供比其他人更好的用戶體驗。

上述功能是由名為 IAudioClient3的介面所提供,其衍生自 IAudioClient2

IAudioClient3 定義下列 3 種方法:

方法 描述
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 的應用程式,使用 Real-Time 工作佇列 APIMFCreateMFByteStreamOnStreamEx 來建立工作項目,並將其標記為 Audio 或 Pro Audio,而不是使用自己的執行緒。 這可讓 Windows 以避免干擾非音訊子系統的方式管理它們。 相反地,Windows 會自動正確管理所有 AudioGraph 線程。 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 工作佇列 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 的應用程式開發人員必須使用音訊類別標記其數據流,以及是否根據每個數據流的功能使用原始訊號處理模式。 Microsoft建議除非了解影響,否則所有音訊數據流都不會使用原始訊號處理模式。 原始模式會略過 OEM 選擇的所有訊號處理,因此:

  • 特定端點的轉譯訊號可能不理想。
  • 擷取訊號可能會以應用程式無法理解的格式出現。
  • 延遲可能會改善。

驅動程式改善

為了讓音訊驅動程式支援低延遲,Windows 10 和更新版本提供下列功能:

  1. [必要]宣告每個模式中支援的最小緩衝區大小。
  2. [選擇性,但建議]改善驅動程式與 Windows 之間數據流的協調。
  3. [選擇性,但建議]註冊驅動程序資源(中斷、線程),以便在低延遲案例中受 Windows 保護。 收件匣 HDAudio 總線驅動程式所列舉的 HDAudio 迷你埠函式驅動程式 hdaudbus.sys 不需要註冊 HDAudio 中斷,因為此動作已由 hdaudbus.sys完成。 不過,如果迷你埠驅動程式建立自己的線程,則需要註冊它們。

下列三節將更深入地說明每個功能。

宣告緩衝區大小下限

驅動程式會在 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 允許驅動程式:

  • 清楚地指出可以供 Windows 使用的緩衝區的哪一半(封包),而不是由作業系統根據編解碼器的連結位置來猜測。 這有助於 Windows 更快速地從音訊問題中復原。
  • 選擇性地優化或簡化其進出 WaveRT 緩衝區的數據傳輸。 這裡的效益大小取決於 DMA 引擎設計或 WaveRT 緩衝區與(可能包括 DSP)硬體之間的資料傳輸機制。
  • 如果驅動程式已在內部累積了擷取數據,則「突發」模式擷取數據的速度比即時更快。 這主要適用於語音啟用案例,但也可在一般串流期間套用。
  • 提供其目前數據流位置的時間戳資訊,而不是 Windows 猜測,可能允許精確的位置資訊。

此 DDI 在使用 DSP 的情況下很有用。 不過,標準 HD 音效驅動程式或其他簡單的循環 DMA 緩衝區設計可能無法在此列出的這些 DDIs 中獲得太多益處。

數個驅動程式例程會傳回 Windows 性能計數器時間戳,以反映裝置擷取或呈現樣本的時間。

在具有複雜 DSP 管線和訊號處理的裝置中,計算精確的時間戳可能具有挑戰性,而且應該深思熟慮地完成。 時間戳不應該反映樣本傳送到 Windows 或從 Windows 傳輸至 DSP 的時間。

若要計算性能計數器值,驅動程式和 DSP 可能會採用下列其中一些方法。

  • 在 DSP 中,使用一些內部的 DSP 時鐘來追蹤範例時間戳。
  • 在驅動程式與 DSP 之間,計算 Windows 性能計數器與 DSP 時鐘之間的相互關聯。 這的程式範圍可以從簡單(但不太精確)到相當複雜或新穎(但更精確)。
  • 請將因訊號處理演算法、管線或硬體傳輸所造成的任何持續延遲列入考量,除非這些延遲已被考慮在內。

sysvad 範例 示範如何使用上述 DDIs。

註冊驅動程序資源

為了協助確保無問題作業,音訊驅動程式必須向 Portcls 註冊其串流資源。 這可讓 Windows 管理資源,以避免音訊串流與其他子系統之間的干擾。

串流資源是音訊驅動程式用來處理音訊數據流或確保音訊數據流的任何資源。 僅支援兩種類型的數據流資源:中斷和驅動程式擁有的線程。 音訊驅動程式應該在建立資源之後註冊資源,並在刪除資源之前取消註冊資源。

音訊驅動程式可以在初始化和載入驅動程式時註冊資源,或是在執行期間,例如在 I/O 資源重新平衡時註冊資源。 Portcls 會使用全域狀態來追蹤所有音訊串流資源。

在某些使用案例中,例如需要非常低延遲音訊的音訊,Windows 會嘗試將音訊驅動程式的已註冊資源與來自其他 OS、應用程式和硬體活動的干擾隔離。 OS 和音訊子系統會視需要執行此動作,而不需與音訊驅動程序互動,但音訊驅動程式註冊資源除外。

註冊串流資源的需求表示串流管線路徑中的所有驅動程式都必須直接或間接向 Portcls 註冊其資源。 音訊迷你埠驅動程式有下列選項:

  • 音訊迷你埠驅動程式是其堆疊的底部驅動程式(直接連接 h/w),在此情況下,驅動程式知道其串流資源,並可向 Portcls 註冊它們。
  • 音訊迷你埠驅動程式會透過其他驅動程式的協助串流音訊(例如音訊總線驅動程式)。 這些其他驅動程式也會使用必須向 Portcls 註冊的資源。 這些平行/總線驅動程式堆疊可以公開公用(或私人介面,如果單一廠商擁有所有驅動程式),音訊迷你埠驅動程式會用來收集此資訊。
  • 音訊迷你埠驅動程式會透過其他驅動程序的協助串流音訊(例如 hdaudbus)。 這些其他驅動程式也會使用必須向 Portcls 註冊的資源。 這些平行/總線驅動程式可以與 Portcls 連結,並直接註冊其資源。 音訊迷你端口驅動程式必須讓 Portcls 知道它們依賴於這些其他並行總線裝置的資源(PDO)。 HD 音訊基礎結構會使用此選項,也就是 HD 音訊總線驅動程式會與 Portcls 連結,並自動執行下列步驟:
    • 註冊其總線驅動程序的資源,以及
    • 通知 Portcls 子系的資源相依於父系的資源。 在 HD 音訊架構中,音訊微型埠驅動程式只需要註冊自己驅動程式的線程資源。

筆記:

  • 收件匣 HDAudio 總線驅動程式所列舉的 HDAudio 迷你埠函式驅動程式 hdaudbus.sys 不需要註冊 HDAudio 中斷,因為此動作已由 hdaudbus.sys完成。 不過,如果迷你埠驅動程式建立自己的線程,則需要註冊它們。
  • 僅用於註冊串流資源的驅動程式需與 Portcls 相關聯,必須更新其 INF 以納入 wdmaudio.inf,並複製 portcls.sys 及其相依檔案。 wdmaudio.inf 中定義了新的 INF 複製區段,只複製這些檔案。
  • 只在 Windows 10 和更新版本中執行的音訊驅動程式可以硬連結至:
  • 必須在下層 OS 上執行的音訊驅動程式可以使用下列介面(迷你埠可以呼叫 IID_IPortClsStreamResourceManager 介面的 QueryInterface,並且只有在 PortCls 支援介面時才註冊其資源)。
  • 這些 DIS,使用此列舉和結構:

最後,為了註冊資源的唯一目的而連結 PortCls 的驅動程式,必須在其 inf 的 DDInstall 區段中新增下列兩行。 音訊迷你埠驅動程式不需要這個,因為它們已在 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 驅動程式,而不是第三方編解碼器驅動程式。 請記住您之前使用哪一個驅動程式,以便如果您想要使用音訊編解碼器的最佳設定,則可以回復至該驅動程式。

圖表顯示 WASAPI 與 AudioGraph 在不同緩衝區大小下的往返延遲差異。

WASAPI 和 AudioGraph 之間的延遲差異是因為下列原因:

  • AudioGraph 會在擷取端新增一個延遲緩衝區,以便同步處理轉譯和擷取,但 WASAPI 未提供。 這個新增功能可簡化使用 AudioGraph 撰寫之應用程式的程式代碼。
  • 當系統使用大於 6 毫秒的緩衝區時,AudioGraph 轉譯端會有另一個延遲緩衝區。
  • AudioGraph 沒有選項可停用擷取音訊效果。

樣品

常見問題

如果所有應用程式都針對低延遲使用新的 API,那不會更好嗎? 低延遲不一定會保證更好的用戶體驗?

不一定。 低延遲有其取捨:

  • 低延遲表示更高的耗電量。 如果系統使用 10 毫秒的緩衝區,表示 CPU 會每 10 毫秒喚醒一次,填滿數據緩衝區並進入睡眠狀態。 不過,如果系統使用 1 毫秒的緩衝區,表示 CPU 會每 1 毫秒喚醒一次。 在第二個案例中,這表示 CPU 會更頻繁地喚醒,耗電量也會增加。 這將減少電池使用時間。
  • 大部分的應用程式都依賴音訊效果來提供最佳的用戶體驗。 例如,媒體播放機想要提供高逼真度音訊。 通訊應用致力於將回音和雜訊降到最低。 將這些類型的音訊效果新增至數據流會增加其延遲。 這些應用程式對音訊品質比音訊延遲更感興趣。

總而言之,每個應用程式類型對於音訊延遲有不同的需求。 如果應用程式不需要低延遲,則不應該將新的 API 用於低延遲。

更新至 Windows 10 和更新版本的所有系統都會自動更新以支援小型緩衝區嗎? 所有系統都支援相同的最小緩衝區大小嗎?

否,為了讓系統支援小型緩衝區,它必須有更新的驅動程式。 由 OEM 決定要更新哪些系統以支援小型緩衝區。 此外,較新的系統較可能支持比較舊系統更小的緩衝區。 新系統中的延遲很可能低於舊系統。

如果驅動程式支援小型緩衝區大小,Windows 10 和更新版本中的所有應用程式都會自動使用小型緩衝區來轉譯和擷取音訊嗎?

否,根據預設,Windows 10 和更新版本中的所有應用程式都會使用 10 毫秒的緩衝區來轉譯和擷取音訊。 如果應用程式需要使用小型緩衝區,則必須使用新的 AudioGraph 設定或 WASAPI IAudioClient3 介面,才能這麼做。 不過,如果一個應用程式要求使用小型緩衝區,則音訊引擎會開始使用該特定緩衝區大小來傳輸音訊。 在此情況下,使用相同端點和模式的所有應用程式都會自動切換至該小型緩衝區大小。 當低延遲應用程式結束時,音訊引擎會再次切換至 10 毫秒的緩衝區。