다음을 통해 공유


저지연 오디오

이 문서에서는 Windows 10의 오디오 대기 시간 변경에 대해 설명합니다. 애플리케이션 개발자를 위한 API 옵션과 짧은 대기 시간 오디오를 지원하기 위해 수행할 수 있는 드라이버의 변경 내용을 다룹니다. 오디오 대기 시간은 소리가 생성되는 시간과 소리가 들리는 시간 사이의 지연입니다. 오디오 대기 시간이 짧은 것은 다음과 같은 몇 가지 주요 시나리오에서 중요합니다.

  • 프로 오디오
  • 음악 만들기
  • 통신
  • 가상 현실
  • 게임

이 문서의 목표는 다음과 같습니다.

  1. Windows에서 오디오 대기 시간의 원인에 대해 설명합니다.
  2. Windows 10 오디오 스택에서 오디오 대기 시간을 줄이는 변경 내용을 설명합니다.
  3. 애플리케이션 개발자와 하드웨어 제조업체가 오디오 대기 시간이 짧은 애플리케이션 및 드라이버를 개발하기 위해 새 인프라를 활용하는 방법에 대한 참조를 제공합니다.

이 문서에서는 다음을 다룹니다.

  1. 대화형 및 미디어 만들기 시나리오를 위한 AudioGraph API입니다.
  2. 짧은 대기 시간을 지원하기 위해 WASAPI가 변경되었습니다.
  3. 드라이버 DDI의 향상된 기능

용어

용어 묘사
렌더링 대기 시간 애플리케이션이 오디오 데이터 버퍼를 렌더링 API에 제출한 후, 스피커에서 소리가 들리기까지 걸리는 지연 시간.
캡처 대기 시간 마이크에서 소리가 캡처되는 시간 사이에 애플리케이션에서 사용되는 캡처 API로 전송되는 시간까지 지연됩니다.
왕복 대기 시간 마이크에서 소리가 캡처되고, 애플리케이션에서 처리되고, 애플리케이션이 스피커에 렌더링하기 위해 제출하는 시간 사이의 지연입니다. 렌더링 대기 시간 + 캡처 대기 시간과 거의 같습니다.
앱 터치 반응 시간 사용자가 화면을 탭하는 시간 사이에 신호가 애플리케이션으로 전송될 때까지 지연됩니다.
터치 투 사운드 대기 시간 사용자가 화면을 탭한 후, 이벤트가 애플리케이션에 전달되고 스피커를 통해 소리가 들리는 데까지 지연이 발생합니다. 렌더링 대기 시간 + 앱 간 터치 대기 시간과 같습니다.

Windows 오디오 스택

다음 다이어그램에서는 간소화된 버전의 Windows 오디오 스택을 보여 줍니다.

앱, 오디오 엔진 드라이버 및 하드웨어가 포함된 짧은 대기 시간 오디오 스택을 보여 주는 다이어그램

렌더링 경로의 대기 시간에 대한 요약은 다음과 같습니다. 오디오 처리 개체

  1. 애플리케이션은 버퍼에 데이터를 씁니다.

  2. 오디오 엔진은 버퍼에서 데이터를 읽고 처리합니다. 또한 오디오 효과를 API(오디오 처리 개체) 형식으로 로드합니다. APO에 대한 자세한 내용은 Windows 오디오 처리 개체참조하세요.

  3. API의 대기 시간은 API 내의 신호 처리에 따라 달라집니다.

  4. Windows 10 이전에는 오디오 엔진의 대기 시간이 부동 소수점 데이터를 사용하는 애플리케이션의 경우 ~12ms, 정수 데이터를 사용하는 애플리케이션의 경우 최대 6ms였습니다.

  5. Windows 10 이상에서는 모든 애플리케이션에 대해 대기 시간이 1.3ms로 감소했습니다.

  6. 오디오 엔진은 처리된 데이터를 버퍼에 씁니다.

  7. Windows 10 이전에는 버퍼가 항상 ~10ms로 설정되었습니다.

  8. Windows 10부터 버퍼 크기는 오디오 드라이버에 의해 정의됩니다(버퍼에 대한 자세한 내용은 이 문서의 뒷부분에 설명되어 있습니다).

  9. 오디오 드라이버는 버퍼에서 데이터를 읽고 하드웨어에 씁니다.

  10. 하드웨어는 더 많은 오디오 효과의 형태로 데이터를 다시 처리할 수도 있습니다.

  11. 사용자가 스피커에서 오디오를 듣습니다.

캡처 경로의 대기 시간에 대한 요약은 다음과 같습니다.

  1. 오디오는 마이크에서 캡처됩니다.

  2. 하드웨어는 데이터를 처리할 수 있습니다. 예를 들어 오디오 효과를 추가합니다.

  3. 드라이버는 하드웨어에서 데이터를 읽고 버퍼에 데이터를 씁니다.

  4. Windows 10 이전에는 이 버퍼가 항상 10ms로 설정되었습니다.

  5. Windows 10부터 버퍼 크기는 오디오 드라이버에 의해 정의됩니다(아래 세부 정보).

  6. 오디오 엔진은 버퍼에서 데이터를 읽고 처리합니다. 또한 오디오 효과를 API(오디오 처리 개체) 형식으로 로드합니다.

  7. API의 대기 시간은 API 내의 신호 처리에 따라 달라집니다.

  8. Windows 10 이전에는 오디오 엔진의 대기 시간이 부동 소수점 데이터를 사용하는 애플리케이션의 경우 ~6ms, 정수 데이터를 사용하는 애플리케이션의 경우 ~0ms와 같았습니다.

  9. Windows 10 이상에서는 모든 애플리케이션에 대해 대기 시간이 ~0ms로 감소했습니다.

  10. 애플리케이션은 오디오 엔진의 처리가 완료되는 즉시 데이터를 읽을 수 있다는 신호를 받았습니다. 오디오 스택은 배타적 모드 옵션도 제공합니다. 이 경우 데이터는 오디오 엔진을 우회하고 애플리케이션에서 드라이버가 읽는 버퍼로 직접 이동합니다. 그러나 애플리케이션이 단독 모드로 엔드포인트를 여는 경우 해당 엔드포인트를 사용하여 오디오를 렌더링하거나 캡처할 수 있는 다른 애플리케이션은 없습니다.

대기 시간이 짧아야 하는 애플리케이션의 또 다른 인기 있는 대안은 전용 모드를 활용하는 ASIO(오디오 스트림 입력/출력) 모델을 사용하는 것입니다. 사용자가 타사 ASIO 드라이버를 설치한 후 애플리케이션은 애플리케이션에서 ASIO 드라이버로 직접 데이터를 보낼 수 있습니다. 그러나 애플리케이션은 ASIO 드라이버와 직접 대화하는 방식으로 작성되어야 합니다.

두 대안(전용 모드 및 ASIO)에는 고유한 제한 사항이 있습니다. 대기 시간이 짧지만 고유한 제한 사항이 있습니다(그 중 일부는 위에서 설명한 것). 따라서 유연성을 유지하면서 대기 시간을 낮추기 위해 오디오 엔진이 수정되었습니다.

오디오 스택 개선 사항

Windows 10 이상은 대기 시간을 줄이기 위해 세 가지 영역에서 향상되었습니다.

  1. 오디오를 사용하는 모든 애플리케이션은 Windows 8.1에 비해 코드 변경 또는 드라이버 업데이트 없이 왕복 대기 시간이 4.5-16ms 감소합니다(위의 섹션에 설명된 대로).
    1. 부동 소수점 데이터를 사용하는 애플리케이션의 대기 시간은 16ms 낮습니다.
    2. 정수 데이터를 사용하는 애플리케이션의 대기 시간은 4.5ms 낮습니다.
  2. 업데이트된 드라이버가 있는 시스템은 왕복 대기 시간을 더 낮출 수 있습니다.
    1. 드라이버는 짧은 대기 시간 DDI를 사용하여 Windows와 하드웨어 간에 데이터를 전송하는 데 사용되는 버퍼의 지원되는 크기를 보고할 수 있습니다. 이전 Windows 버전에서와 같이 데이터 전송에서 항상 10ms 버퍼를 사용할 필요는 없습니다. 대신 드라이버는 작은 버퍼(예: 5ms, 3ms, 1ms 등)를 사용할 수 있는지 지정할 수 있습니다.
    2. 대기 시간이 짧은 애플리케이션은 짧은 대기 시간 오디오 API(AudioGraph 또는 WASAPI)를 사용하여 드라이버에서 지원하는 버퍼 크기를 쿼리하고 하드웨어 간에 데이터 전송에 사용할 버퍼 크기를 선택할 수 있습니다.
  3. 애플리케이션이 특정 임계값 미만의 버퍼 크기를 사용하여 오디오를 렌더링하고 캡처하는 경우 Windows는 오디오 스트리밍과 다른 하위 시스템 간의 간섭을 방지하는 방식으로 리소스를 관리하는 특수 모드로 전환됩니다. 이렇게 하면 오디오 하위 시스템의 실행 중단이 줄어들고 오디오 결함의 가능성이 최소화됩니다. 애플리케이션이 스트리밍을 중지하면 Windows가 정상 실행 모드로 돌아갑니다. 오디오 하위 시스템은 다음 리소스로 구성됩니다.
    1. 대기 시간이 짧은 오디오를 처리하는 오디오 엔진 스레드입니다.
    2. 드라이버에서 등록한 모든 스레드 및 인터럽트(드라이버 리소스 등록에 대한 섹션에 설명된 짧은 대기 시간 DDI 사용).
    3. 작은 버퍼를 요청하는 애플리케이션 및 작은 버퍼를 요청한 애플리케이션과 동일한 오디오 디바이스 그래프(예: 동일한 신호 처리 모드)를 공유하는 모든 애플리케이션의 오디오 스레드의 일부 또는 전부:
  4. 스트리밍 경로의 AudioGraph 콜백입니다.
  5. 애플리케이션에서 WASAPI를 사용하는 경우 "Audio" 또는 "ProAudio"로 태그가 지정된 작업 항목은 Real-Time 작업 큐 API나 MFCreateMFByteStreamOnStreamEx 에 제출된 것만 해당됩니다.

API 개선 사항

다음 두 개의 Windows 10 API는 짧은 대기 시간 기능을 제공합니다.

사용할 두 API 중 어느 것을 결정하려면 다음을 수행합니다.

  • 새 애플리케이션 개발을 위해 가능한 경우 AudioGraph를 선호합니다.
  • 다음과 같은 경우에만 WASAPI를 사용합니다.
    • AudioGraph에서 제공하는 것보다 더 많은 제어가 필요합니다.
    • AudioGraph에서 제공하는 대기 시간보다 짧은 대기 시간이 필요합니다.

이 문서의 측정 도구 섹션에서는 기본 제공 HDAudio 드라이버를 사용하여 Haswell 시스템의 특정 측정값을 보여 줍니다.

다음 섹션에서는 각 API의 짧은 대기 시간 기능을 설명합니다. 이전 섹션에서 설명한 것처럼 시스템이 최소 대기 시간을 달성하려면 작은 버퍼 크기를 지원하는 업데이트된 드라이버가 있어야 합니다.

오디오그래프

AudioGraph는 Windows 10 이상의 유니버설 Windows 플랫폼 API로, 대화형 및 음악 생성 시나리오를 쉽게 실현하기 위한 것입니다. AudioGraph는 여러 프로그래밍 언어(C++, C#, JavaScript)에서 사용할 수 있으며 간단하고 기능이 풍부한 프로그래밍 모델을 제공합니다.

짧은 대기 시간 시나리오를 대상으로 하기 위해 AudioGraph는 AudioGraphSettings::QuantumSizeSelectionMode 속성을 제공합니다. 이 속성은 아래 표에 표시된 값 중 어느 것이든 될 수 있습니다.

가치 묘사
시스템 기본값 버퍼를 기본 버퍼 크기(~10ms)로 설정합니다.
최저지연겠 버퍼를 드라이버에서 지원하는 최소값으로 설정합니다.
가까운_목표 버퍼 크기를 DesiredSamplesPerQuantum 속성에 정의된 값 또는 드라이버에서 지원하는 DesiredSamplesPerQuantum에 가까운 값으로 설정합니다.

AudioCreation 샘플 짧은 대기 시간에 AudioGraph를 사용하는 방법을 보여 줍니다. 다음 코드 조각은 최소 버퍼 크기를 설정하는 방법을 보여줍니다.

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

WASAPI(Windows 오디오 세션 API)

Windows 10부터 WASAPI가 다음과 같이 향상되었습니다.

  • 애플리케이션이 지정된 오디오 디바이스의 오디오 드라이버에서 지원하는 버퍼 크기(즉, 주기 값)의 범위를 검색할 수 있도록 허용합니다. 이렇게 하면 공유 모드에서 스트림을 열 때 애플리케이션이 기본 버퍼 크기(10ms) 또는 작은 버퍼(10ms 미만) 중에서 선택할 수 있습니다. 애플리케이션에서 버퍼 크기를 지정하지 않으면 기본 버퍼 크기를 사용합니다.
  • 애플리케이션이 오디오 엔진의 현재 형식과 주기성을 검색할 수 있도록 허용합니다. 이렇게 하면 애플리케이션이 오디오 엔진의 현재 설정에 자동으로 맞추도록 할 수 있습니다.
  • 앱이 오디오 엔진에서 다시 샘플링하지 않고 지정한 형식으로 렌더링/캡처하도록 지정하도록 허용

위의 기능은 모든 Windows 디바이스에서 사용할 수 있습니다. 그러나 충분한 리소스와 업데이트된 드라이버가 있는 특정 디바이스는 다른 디바이스보다 더 나은 사용자 환경을 제공합니다.

위의 기능은 IAudioClient3이라는 인터페이스가 IAudioClient2로부터 파생되는 형식으로 제공됩니다.

IAudioClient3 다음 세 가지 메서드를 정의합니다.

메서드 설명
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를 사용하는 애플리케이션에 대해 작업 항목을 만들고 이를 자체 스레드 대신 Audio 또는 Pro Audio로 태그하기 위해 Real-Time 작업 큐 API 또는 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 작업 큐 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에서 이미 처리했기 때문에 등록할 필요가 없습니다. 그러나 미니포트 드라이버가 자체 스레드를 만드는 경우 해당 스레드를 등록해야 합니다.

다음 세 섹션에서는 각 기능에 대해 자세히 설명합니다.

최소 버퍼 크기 선언

드라이버는 Windows, 드라이버 및 하드웨어 간에 오디오 데이터를 이동할 때 다양한 제약 조건에서 작동합니다. 이러한 제약 조건은 메모리와 하드웨어 간에 데이터를 이동하는 물리적 하드웨어 전송 또는 하드웨어 또는 관련 DSP 내의 신호 처리 모듈 때문일 수 있습니다.

Windows 10 버전 1607부터 드라이버는 DEVPKEY_KsAudio_PacketSize_Constraints2 디바이스 속성을 사용하여 버퍼 크기 기능을 표현할 수 있습니다. 이 속성을 사용하면 사용자가 드라이버에서 지원하는 절대 최소 버퍼 크기 및 각 신호 처리 모드에 대한 특정 버퍼 크기 제약 조건을 정의할 수 있습니다. 모드별 제약 조건은 드라이버 최소 버퍼 크기보다 높아야 합니다. 그렇지 않으면 오디오 스택에서 무시됩니다.

예를 들어 다음 코드 조각은 드라이버가 지원되는 절대 최소 버퍼 크기가 2ms라고 선언하는 방법을 보여 주지만 기본 모드는 48kHz 샘플 속도를 가정하는 경우 3ms에 해당하는 128개의 프레임을 지원합니다.

 
//
// 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는 오디오 스트리밍과 기타 하위 시스템 간의 간섭을 방지하기 위해 리소스를 관리할 수 있습니다.

스트림 리소스는 오디오 드라이버가 오디오 스트림을 처리하거나 오디오 데이터 흐름을 확인하는 데 사용하는 모든 리소스입니다. 인터럽트 및 드라이버 소유 스레드라는 두 가지 유형의 스트림 리소스만 지원됩니다. 오디오 드라이버는 리소스를 만든 후 리소스를 등록하고 리소스를 삭제하기 전에 등록을 취소해야 합니다.

오디오 드라이버는 드라이버가 로드될 때 초기화 시 또는 런타임에 리소스를 등록할 수 있습니다(예: I/O 리소스 재균형이 있는 경우). Portcls는 전역 상태를 사용하여 모든 오디오 스트리밍 리소스를 추적합니다.

대기 시간이 매우 짧은 오디오가 필요한 경우와 같은 일부 사용 사례에서 Windows는 오디오 드라이버의 등록된 리소스를 다른 OS, 애플리케이션 및 하드웨어 활동의 간섭으로부터 격리하려고 시도합니다. OS 및 오디오 하위 시스템은 오디오 드라이버의 리소스 등록을 제외하고 오디오 드라이버와 상호 작용하지 않고 필요에 따라 이 작업을 수행합니다.

스트림 리소스를 등록하기 위한 이 요구 사항은 스트리밍 파이프라인 경로에 있는 모든 드라이버가 포트클에 직접 또는 간접적으로 해당 리소스를 등록해야 한다는 것을 의미합니다. 오디오 미니포트 드라이버에는 다음과 같은 옵션이 있습니다.

  • 오디오 미니포트 드라이버는 스택의 아래쪽 드라이버(h/w를 직접 상호 연결)이며, 이 경우 드라이버는 스트림 리소스를 알고 있으며 Portcls에 등록할 수 있습니다.
  • 오디오 미니포트 드라이버는 다른 드라이버(예: 오디오 버스 드라이버)의 도움으로 오디오를 스트리밍합니다. 이러한 다른 드라이버도 Portcls에 등록해야 하는 리소스를 사용합니다. 이러한 병렬/버스 드라이버 스택은 오디오 미니포트 드라이버가 이 정보를 수집하는 데 사용하는 공용(또는 단일 공급업체가 모든 드라이버를 소유하는 경우 프라이빗 인터페이스)을 노출할 수 있습니다.
  • 오디오 미니포트 드라이버는 다른 드라이버(예: hdaudbus)의 도움으로 오디오를 스트리밍합니다. 이러한 다른 드라이버도 Portcls에 등록해야 하는 리소스를 사용합니다. 이러한 병렬/버스 드라이버는 Portcls와 연결하고 해당 리소스를 직접 등록할 수 있습니다. 오디오 미니포트 드라이버는 이러한 다른 PDO(병렬/버스 디바이스)의 리소스에 의존한다는 것을 Portcls에 알려야 합니다. HD 오디오 인프라는 이 옵션을 사용합니다. 즉, HD 오디오 버스 드라이버는 Portcls와 연결되고 자동으로 다음 단계를 수행합니다.
    • 버스 운전기사의 자원을 등록하고
    • 자식 리소스가 부모의 리소스에 의존한다는 것을 Portcls에 알린다. HD 오디오 아키텍처에서 오디오 미니포트 드라이버는 자체 드라이버 소유 스레드 리소스를 등록하기만 하면 됩니다.

노트:

  • 받은 편지함 HDAudio 버스 드라이버 hdaudbus.sys에 의해 열거된 HDAudio 미니포트 함수 드라이버는 hdaudbus.sys에서 이미 HDAudio 인터럽트를 수행했기 때문에 등록할 필요가 없습니다. 그러나 미니포트 드라이버가 자체 스레드를 만드는 경우 해당 스레드를 등록해야 합니다.
  • 스트리밍 리소스를 등록하기 위해서만 Portcls와 연결하는 드라이버는 wdmaudio.inf를 포함하고 portcls.sys(및 종속 파일)를 복사하도록 INF를 업데이트해야 합니다. 새 INF 복사 섹션은 wdmaudio.inf에 정의되어 해당 파일만 복사합니다.
  • Windows 10 이상에서만 실행되는 오디오 드라이버는 다음을 하드 연결할 수 있습니다.
  • 하위 수준 OS에서 실행해야 하는 오디오 드라이버는 다음 인터페이스를 사용할 수 있습니다(미니포트는 IID_IPortClsStreamResourceManager 인터페이스에 대해 QueryInterface를 호출하고 PortCls가 인터페이스를 지원하는 경우에만 리소스를 등록할 수 있습니다).
  • 이러한 DDI는 다음 열거형 및 구조를 사용합니다.

마지막으로, 리소스를 등록하기 위한 목적으로만 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 이상 버전의 일부)를 설치하는 방법을 다음 단계에서 보여 줍니다.
  • 디바이스 관리자를 시작합니다.
  • 사운드 비디오 및 게임 컨트롤러내부 스피커에 해당하는 장치를 두 번 클릭합니다.
  • 다음 창에서 드라이버 탭으로 이동합니다.
  • 업데이트 드라이버 선택 ->내 컴퓨터에서 드라이버 소프트웨어 찾아보기 ->이 컴퓨터 디바이스 드라이버 목록에서 선택하세요.>고화질 오디오 장치 선택하고 다음선택합니다.
  • "드라이버 경고 업데이트" 창이 나타나면 선택합니다.
  • 을(를) 선택한 후을(를) 닫습니다.
  • 시스템을 다시 부팅하라는 메시지가 표시되면 선택하여 다시 부팅합니다.
  • 재부팅 후 시스템은 타사 코덱 드라이버가 아닌 기본 제공 Microsoft HDAudio 드라이버를 사용합니다. 오디오 코덱에 최적 설정을 사용하려는 경우 해당 드라이버로 대체 할 수 있도록 이전에 사용했던 드라이버를 기억하십시오.

그래프는 다양한 버퍼 크기에 대한 WASAPI와 AudioGraph 간의 왕복 대기 시간 차이를 보여 줍니다.

WASAPI와 AudioGraph 간의 대기 시간 차이는 다음과 같은 이유로 인해 발생합니다.

  • AudioGraph는 WASAPI에서 제공하지 않는 렌더링 및 캡처를 동기화하기 위해 캡처 쪽에 하나의 대기 시간 버퍼를 추가합니다. 이 추가 기능을 사용하면 AudioGraph를 사용하여 작성된 애플리케이션의 코드가 간소화됩니다.
  • 시스템에서 6ms보다 큰 버퍼를 사용하는 경우 AudioGraph의 렌더링 쪽에는 대기 시간의 또 다른 버퍼가 있습니다.
  • AudioGraph에는 오디오 효과 캡처를 사용하지 않도록 설정하는 옵션이 없습니다.

샘플

자주 묻는 질문(FAQ)

모든 애플리케이션이 짧은 대기 시간에 새 API를 사용하는 경우 더 좋을까요? 대기 시간이 짧아도 항상 더 나은 사용자 환경이 보장되지 않나요?

반드시 그렇지는 않다. 대기 시간이 짧을 경우 다음과 같은 절충이 있습니다.

  • 대기 시간이 짧으면 전력 소비가 증가합니다. 시스템이 10ms 버퍼를 사용하는 경우, 이는 CPU가 10ms마다 잠에서 깨어나 데이터 버퍼를 채운 후 다시 잠드는 것을 의미합니다. 그러나 시스템에서 1ms 버퍼를 사용하는 경우 CPU가 1ms마다 깨어납니다. 두 번째 시나리오에서는 CPU가 더 자주 깨어나게 되고, 전력 소모가 증가하게 됩니다. 이렇게 하면 배터리 수명이 줄어듭니다.
  • 대부분의 애플리케이션은 최상의 사용자 환경을 제공하기 위해 오디오 효과를 사용합니다. 예를 들어 미디어 플레이어는 충실도가 높은 오디오를 제공하려고 합니다. 통신 애플리케이션은 에코 및 노이즈를 최소화하려고 합니다. 이러한 유형의 오디오 효과를 스트림에 추가하면 대기 시간이 증가합니다. 이러한 애플리케이션은 오디오 대기 시간보다 오디오 품질에 더 관심이 있습니다.

요약하자면, 각 애플리케이션 유형에는 오디오 대기 시간에 대한 요구 사항이 다릅니다. 애플리케이션에 짧은 대기 시간이 필요하지 않은 경우 짧은 대기 시간에 새 API를 사용하면 안 됩니다.

Windows 10 이상으로 업데이트되는 모든 시스템이 작은 버퍼를 지원하도록 자동으로 업데이트되나요? 모든 시스템에서 동일한 최소 버퍼 크기를 지원합니까?

아니요, 시스템이 작은 버퍼를 지원하려면 드라이버를 업데이트해야 합니다. 작은 버퍼를 지원하도록 업데이트할 시스템을 결정하는 것은 OEM에 달려 있습니다. 또한 최신 시스템은 이전 시스템보다 더 작은 버퍼를 지원할 가능성이 높습니다. 새 시스템의 대기 시간은 대부분 이전 시스템보다 낮을 수 있습니다.

드라이버가 작은 버퍼 크기를 지원하는 경우 Windows 10 이상의 모든 애플리케이션은 자동으로 작은 버퍼를 사용하여 오디오를 렌더링하고 캡처합니까?

아니요, 기본적으로 Windows 10 이상의 모든 애플리케이션은 10ms 버퍼를 사용하여 오디오를 렌더링하고 캡처합니다. 애플리케이션이 작은 버퍼를 사용해야 한다면, 이를 위해 새로운 AudioGraph 설정이나 WASAPI IAudioClient3 인터페이스를 사용해야 합니다. 그러나 한 애플리케이션이 작은 버퍼의 사용을 요청하는 경우 오디오 엔진은 특정 버퍼 크기를 사용하여 오디오 전송을 시작합니다. 이 경우 동일한 엔드포인트 및 모드를 사용하는 모든 애플리케이션은 자동으로 해당 작은 버퍼 크기로 전환됩니다. 대기 시간이 짧은 애플리케이션이 종료되면 오디오 엔진이 다시 10ms 버퍼로 전환됩니다.