How to get below 10ms latency using WASAPI shared mode?
Asked Answered
C

1

24

According to Microsoft, starting with Windows 10, applications using shared-mode WASAPI can request buffer sizes smaller than 10ms (see https://msdn.microsoft.com/en-us/library/windows/hardware/mt298187%28v=vs.85%29.aspx).

According to the article, achieving such low latencies requires some driver updates, which I did. Using an exclusive-mode render and capture stream, I measured a total round-trip latency (using a hardware loopback cable) of around 13ms. This suggests to me that at least one of the endpoints successfully achieves a latency of < 10ms. (Is this assumption correct?)

The article mentions that applications can use the new IAudioClient3 interface to query the minimum buffer size supported by the Windows audio engine using IAudioClient3::GetSharedModeEnginePeriod(). However, this function always returns 10ms on my system, and any attempt to initialize an audio stream using either IAudioClient::Initialize() or IAudioClient3::InitializeSharedAudioStream() with a period lower than 10ms always results in AUDCLNT_E_INVALID_DEVICE_PERIOD.

Just to be sure, I also disabled any effects processing in the audio drivers. What am I missing? Is it even possible to get low latency from shared mode? See below for some sample code.

#include <windows.h>
#include <atlbase.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <iostream>

#define VERIFY(hr) do {                                    \
  auto temp = (hr);                                        \
  if(FAILED(temp)) {                                       \
    std::cout << "Error: " << #hr << ": " << temp << "\n"; \
    goto error;                                            \
  }                                                        \
} while(0)


int main(int argc, char** argv) {

  HRESULT hr;
  CComPtr<IMMDevice> device;
  AudioClientProperties props;
  CComPtr<IAudioClient> client;
  CComPtr<IAudioClient2> client2;
  CComPtr<IAudioClient3> client3;
  CComHeapPtr<WAVEFORMATEX> format;
  CComPtr<IMMDeviceEnumerator> enumerator; 

  REFERENCE_TIME minTime, maxTime, engineTime;
  UINT32 min, max, fundamental, default_, current;

  VERIFY(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
  VERIFY(enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator)));
  VERIFY(enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &device));
  VERIFY(device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, reinterpret_cast<void**>(&client)));
  VERIFY(client->QueryInterface(&client2));
  VERIFY(client->QueryInterface(&client3));

  VERIFY(client3->GetCurrentSharedModeEnginePeriod(&format, &current));

  // Always fails with AUDCLNT_E_OFFLOAD_MODE_ONLY.
  hr = client2->GetBufferSizeLimits(format, TRUE, &minTime, &maxTime);
  if(hr == AUDCLNT_E_OFFLOAD_MODE_ONLY)
    std::cout << "GetBufferSizeLimits returned AUDCLNT_E_OFFLOAD_MODE_ONLY.\n";
  else if(SUCCEEDED(hr))
    std::cout << "hw min = " << (minTime / 10000.0) << " hw max = " << (maxTime / 10000.0) << "\n";
  else
    VERIFY(hr);

  // Correctly? reports a minimum hardware period of 3ms and audio engine period of 10ms.
  VERIFY(client->GetDevicePeriod(&engineTime, &minTime));
  std::cout << "hw min = " << (minTime / 10000.0) << " engine = " << (engineTime / 10000.0) << "\n";

  // All values are set to a number of frames corresponding to 10ms.
  // This does not change if i change the device's sampling rate in the control panel.
  VERIFY(client3->GetSharedModeEnginePeriod(format, &default_, &fundamental, &min, &max));
  std::cout << "default = " << default_ 
            << " fundamental = " << fundamental 
            << " min = " << min 
            << " max = " << max 
            << " current = " << current << "\n";

  props.bIsOffload = FALSE;
  props.cbSize = sizeof(props);
  props.eCategory = AudioCategory_ForegroundOnlyMedia;
  props.Options = AUDCLNT_STREAMOPTIONS_RAW | AUDCLNT_STREAMOPTIONS_MATCH_FORMAT;

  // Doesn't seem to have any effect regardless of category/options values.
  VERIFY(client2->SetClientProperties(&props));

  format.Free();
  VERIFY(client3->GetCurrentSharedModeEnginePeriod(&format, &current));
  VERIFY(client3->GetSharedModeEnginePeriod(format, &default_, &fundamental, &min, &max));
  std::cout << "default = " << default_ 
            << " fundamental = " << fundamental 
            << " min = " << min 
            << " max = " << max 
            << " current = " << current << "\n";

error:
  CoUninitialize();
  return 0;
}
Crunch answered 26/5, 2016 at 18:16 Comment(8)
Surely this is a driver issue, never not a problem in audio. I'm getting the same basic results, Cirrus Logic CS4208 driver version 6.6001.3.24 and Windows 10.0.10586. You ought to mention yours.Harbaugh
Tested using onboard HD audio using "updated drivers" as mentioned in the article. Will test again using another interface, also i cant seem to find where to get this updated drivers anymore (but i know for a fact that it was windows-supplied drivers specific for low latency audio, so it'd be weird if it really is a driver thing). Ill look into it to see what exactly is on my system.Crunch
Got it. I used the drivers as explained here: msdn.microsoft.com/en-us/library/windows/hardware/…. Quote: "In order to measure the roundtrip latency for different buffer sizes, users need to install a driver that supports small buffers. The inbox HDAudio driver has been updated to support buffer sizes between 128 samples (2.66ms@48kHz) and 480 samples (10ms@48kHz)". Will try using another device tonight.Crunch
Turns out it was a driver issue after all. Just repeated the steps in the link above, now I get a minimum shared mode period of 128 samples (2.67 msec @ 48KHz) reported by GetSharedModeEnginePeriod. Windows 10 10586.545 using an onboard realtek hd audio device (cant find out exactly which version). The microsoft-supplied driver which is now working correctly is called high definition audio device, version 10.0.586.0. Honestly don't know how i missed this while testing last time =) @Hans passant care to make an answer out of your comment so i can award the bounty?Crunch
Why don't you use ASIO ?Che
because he asked for shared accessBallast
More importantly, because most cards don't come with asio drivers. Wasapi is there out of the box.Crunch
I'll add the answer, if no one minds, just so this won't show up on the Unanswered list. What to do with the bounty is up to you, @SjoerdvanKreel. :)Gemma
G
0

Per Hans in the comment above, double-check that you've followed the instructions for Low Latency Audio here.

I'd reboot the machine just to be sure; Windows can be a bit finicky with that kind of thing.

Gemma answered 16/6, 2017 at 13:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.