7
7
#define _WIN32_WINNT 0x0603
8
8
#define NOMINMAX
9
9
10
+ // Makes sure cubeb doesn't try to initialize an IAudioClient3 with unsupported formats, as it forces its own (e.g. unsupported bit depths or sample rates).
11
+ // I'm not 100% sure this is right as IAudioClient::IsFormatSupported() might not be relevant with IAudioClient3, but it probably is.
12
+ #define CHECK_MIXER_FORMAT_SUPPORT 1
13
+ // Force raw output (skips some processing) with IAudioClient3
14
+ #define USE_RAW_OUTPUT 0
15
+ // Forces the output format to match our mixer one
16
+ #define FORCE_MATCHING_OUTPUT_FORMAT 0
17
+ // "Forces" (?) a IAudioClient3 to resample (in high quality) our stream to the device output sample rate
18
+ #define CONVERT_STREAM 0
19
+ // If we are targeting IAudioClient3, we don't want to limit the latency to the default latency (likely 10ms), but to the minimum one.
20
+ // Though it seems that IAudioClient::GetDevicePeriod() returns lower latencies than GetSharedModeEnginePeriod() allows, so that's weird.
21
+ #define ALLOW_MIN_LATENCY 0
22
+ // Can we go lower than the user/client requested latency? This isn't particularly necessary as dolphin should produce enough audio samples to go with latencies < 10ms (which is what it asks for),
23
+ // and the user requested latency is just a suggestion.
24
+ #define FORCE_RESPECT_USER_LATENCY 0
25
+ // IAudioClient3::GetSharedModeEnginePeriod() doesn't seem to work as well as IAudioClient::GetDevicePeriod() and often return a min and max latency of 10ms, even if lower ones would be supported
26
+ #define USE_AUDIO_CLIENT_3_LATENCY 0
27
+
10
28
#include < algorithm>
11
29
#include < atomic>
12
30
#include < audioclient.h>
@@ -1867,6 +1885,45 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params,
1867
1885
return CUBEB_ERROR;
1868
1886
}
1869
1887
1888
+ #if USE_AUDIO_CLIENT_3_LATENCY
1889
+ // TODO: this is likely unreliable as we can't know the actual mixer format cubeb will ask for later on (we'd need to calculate it) (and the min latency could change based on that)
1890
+ com_ptr<IAudioClient3> client3;
1891
+ hr = device->Activate (__uuidof (IAudioClient3), CLSCTX_INPROC_SERVER, NULL ,
1892
+ client3.receive_vpp ());
1893
+ if (SUCCEEDED (hr)) {
1894
+ WAVEFORMATEX * mix_format = nullptr ;
1895
+ hr = client3->GetMixFormat (&mix_format);
1896
+
1897
+ if (SUCCEEDED (hr)) {
1898
+ uint32_t default_period = 0 , fundamental_period = 0 , min_period = 0 ,
1899
+ max_period = 0 ;
1900
+ hr = client3->GetSharedModeEnginePeriod (
1901
+ mix_format, &default_period, &fundamental_period, &min_period,
1902
+ &max_period);
1903
+
1904
+ auto sample_rate = mix_format->nSamplesPerSec ;
1905
+ CoTaskMemFree (mix_format);
1906
+ if (SUCCEEDED (hr)) {
1907
+ // Keep values in the same format as IAudioDevice::GetDevicePeriod()
1908
+ REFERENCE_TIME min_period_rt (frames_to_hns (sample_rate, min_period));
1909
+ REFERENCE_TIME default_period_rt (frames_to_hns (sample_rate, default_period));
1910
+ LOG (" default device period: %I64d, minimum device period: %I64d" ,
1911
+ default_period_rt, min_period_rt);
1912
+
1913
+ #if ALLOW_MIN_LATENCY
1914
+ *latency_frames = min_period;
1915
+ #else
1916
+ *latency_frames = default_period; // This can be 0
1917
+ #endif
1918
+
1919
+ LOG (" Minimum latency in frames: %u" , *latency_frames);
1920
+
1921
+ return CUBEB_OK;
1922
+ }
1923
+ }
1924
+ }
1925
+ #endif
1926
+
1870
1927
com_ptr<IAudioClient> client;
1871
1928
hr = device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, NULL ,
1872
1929
client.receive_vpp ());
@@ -1891,10 +1948,8 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params,
1891
1948
synchronizing the stream and the engine.
1892
1949
http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx
1893
1950
*/
1894
-
1895
- // #ifdef _WIN32_WINNT_WIN10
1896
- #if 0
1897
- *latency_frames = hns_to_frames(params.rate, minimum_period);
1951
+ #if defined(_WIN32_WINNT_WIN10) && ALLOW_MIN_LATENCY
1952
+ *latency_frames = hns_to_frames (params.rate , minimum_period);
1898
1953
#else
1899
1954
*latency_frames = hns_to_frames (params.rate , default_period);
1900
1955
#endif
@@ -1987,7 +2042,10 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
1987
2042
if (hr == S_FALSE) {
1988
2043
/* Channel layout not supported, but WASAPI gives us a suggestion. Use it,
1989
2044
and handle the eventual upmix/downmix ourselves. Ignore the subformat of
1990
- the suggestion, since it seems to always be IEEE_FLOAT. */
2045
+ the suggestion, since it seems to always be IEEE_FLOAT.
2046
+ This fallback doesn't update the bit depth, so if a device
2047
+ only supported bit depths cubeb doesn't support, we will need to
2048
+ make sure the stream is compatible with it (e.g. IAudioClient3 is not) */
1991
2049
LOG (" Using WASAPI suggested format: channels: %d" , closest->nChannels );
1992
2050
XASSERT (closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
1993
2051
WAVEFORMATEXTENSIBLE * closest_pcm =
@@ -2031,12 +2089,12 @@ initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
2031
2089
return CUBEB_OK;
2032
2090
}
2033
2091
2034
- #if 0
2035
2092
bool
2036
2093
initialize_iaudioclient3 (com_ptr<IAudioClient> & audio_client,
2037
2094
cubeb_stream * stm,
2038
2095
const com_heap_ptr<WAVEFORMATEX> & mix_format,
2039
- DWORD flags, EDataFlow direction)
2096
+ DWORD flags, EDataFlow direction,
2097
+ REFERENCE_TIME latency_hns)
2040
2098
{
2041
2099
com_ptr<IAudioClient3> audio_client3;
2042
2100
audio_client->QueryInterface <IAudioClient3>(audio_client3.receive ());
@@ -2072,37 +2130,98 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
2072
2130
// would do this, then stop and use IAudioClient instead.
2073
2131
2074
2132
HRESULT hr;
2133
+
2134
+ #if CHECK_MIXER_FORMAT_SUPPORT
2135
+ WAVEFORMATEX * tmp = nullptr ;
2136
+ // The mixer format here might have bit depths "forced" by cubeb even if the output device doesn't directly support it.
2137
+ // This doesn't work with IAudioClient3 as there it applies no resampling.
2138
+ hr = audio_client3->IsFormatSupported (AUDCLNT_SHAREMODE_SHARED, mix_format.get (), &tmp);
2139
+ if (FAILED (hr)) {
2140
+ LOG (" IAudioClient3 attempted format is not supported: error: %lx" , hr);
2141
+ return false ;
2142
+ }
2143
+ CoTaskMemFree (tmp);
2144
+ #endif
2145
+
2075
2146
uint32_t default_period = 0 , fundamental_period = 0 , min_period = 0 ,
2076
2147
max_period = 0 ;
2148
+ // TODO: review... this returns the same default/min/max latency every time on my PC (10ms, which is high),
2149
+ // independently of the device and its settings (with no other apps running). Not sure if it means that lag would be bigger than with IAudioDevice(1).
2077
2150
hr = audio_client3->GetSharedModeEnginePeriod (
2078
2151
mix_format.get (), &default_period, &fundamental_period, &min_period,
2079
2152
&max_period);
2080
2153
if (FAILED (hr)) {
2081
2154
LOG (" Could not get shared mode engine period: error: %lx" , hr);
2082
2155
return false ;
2083
2156
}
2084
- uint32_t requested_latency = stm->latency;
2085
- if (requested_latency >= default_period) {
2086
- LOG("Requested latency %i greater than default latency %i, not using "
2157
+ uint32_t requested_latency = hns_to_frames (mix_format->nSamplesPerSec , latency_hns);
2158
+ #if FORCE_RESPECT_USER_LATENCY
2159
+ if (requested_latency > max_period) {
2160
+ // Fallback to IAudioClient(1) as it's less restrictive towards bigger latencies
2161
+ LOG (" Requested latency %i greater than max latency %i, not using "
2087
2162
" IAudioClient3" ,
2088
- requested_latency, default_period );
2163
+ requested_latency, max_period );
2089
2164
return false ;
2090
2165
}
2166
+ #endif
2091
2167
LOG (" Got shared mode engine period: default=%i fundamental=%i min=%i max=%i" ,
2092
2168
default_period, fundamental_period, min_period, max_period);
2093
2169
// Snap requested latency to a valid value
2094
2170
uint32_t old_requested_latency = requested_latency;
2171
+ // The period is required to be a multiple of the fundamental period (and >= min and <= max, which should still be true)
2172
+ requested_latency -= requested_latency % fundamental_period;
2095
2173
if (requested_latency < min_period) {
2096
2174
requested_latency = min_period;
2097
2175
}
2098
- requested_latency -= (requested_latency - min_period) % fundamental_period;
2176
+ // This is likely unnecessary, but it won't hurt
2177
+ if (requested_latency > max_period) {
2178
+ requested_latency = max_period;
2179
+ }
2099
2180
if (requested_latency != old_requested_latency) {
2100
2181
LOG (" Requested latency %i was adjusted to %i" , old_requested_latency,
2101
2182
requested_latency);
2102
2183
}
2103
2184
2104
- hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency,
2185
+ #if USE_RAW_OUTPUT || FORCE_MATCHING_OUTPUT_FORMAT
2186
+ AudioClientProperties properties = {0 };
2187
+ properties.cbSize = sizeof (AudioClientProperties);
2188
+ properties.bIsOffload = false ; // TODO: review this (take from chromium)
2189
+ #ifndef __MINGW32__
2190
+ #if USE_RAW_OUTPUT
2191
+ // Raw audio streams skip some kinds of processing, like AEC and AGC.
2192
+ // Do this independent from CUBEB_STREAM_PREF_RAW, we force this with IAudioClient3, to hopefully get the lowest latency/quality and support.
2193
+ properties.Options |= AUDCLNT_STREAMOPTIONS_RAW;
2194
+ #endif
2195
+ #if FORCE_MATCHING_OUTPUT_FORMAT
2196
+ properties.Options |= AUDCLNT_STREAMOPTIONS_MATCH_FORMAT;
2197
+ #endif
2198
+ #endif
2199
+ com_ptr<IAudioClient2> audio_client2;
2200
+ hr = audio_client->QueryInterface <IAudioClient2>(audio_client2.receive ());
2201
+ if (audio_client2) {
2202
+ hr = audio_client2->SetClientProperties (&properties);
2203
+ }
2204
+ if (FAILED (hr)) {
2205
+ LOG (" Could not set IAudioClient2 properties: error: %lx" , hr);
2206
+ // This is not fatal, it should work anyway (AUDCLNT_STREAMOPTIONS_RAW might not be supported)
2207
+ }
2208
+ #endif
2209
+
2210
+ DWORD new_flags = flags;
2211
+ #if CONVERT_STREAM
2212
+ // Always add these flags to IAudioClient3 (they can help if the stream doesn't have the same format as the output)
2213
+ new_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
2214
+ new_flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
2215
+ #endif
2216
+
2217
+ hr = audio_client3->InitializeSharedAudioStream (new_flags, requested_latency,
2105
2218
mix_format.get (), NULL );
2219
+ if (hr == AUDCLNT_E_INVALID_STREAM_FLAG && CONVERT_STREAM) {
2220
+ LOG (" Got AUDCLNT_E_INVALID_STREAM_FLAG, removing some flag" );
2221
+ hr = audio_client3->InitializeSharedAudioStream (flags, requested_latency,
2222
+ mix_format.get (), NULL );
2223
+ }
2224
+
2106
2225
if (SUCCEEDED (hr)) {
2107
2226
return true ;
2108
2227
} else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) {
@@ -2114,22 +2233,48 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
2114
2233
}
2115
2234
2116
2235
uint32_t current_period = 0 ;
2117
- WAVEFORMATEX * current_format = nullptr;
2236
+ WAVEFORMATEX * current_format_ptr = nullptr ;
2118
2237
// We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise
2119
2238
// GetCurrentSharedModeEnginePeriod will return E_POINTER
2120
- hr = audio_client3->GetCurrentSharedModeEnginePeriod(¤t_format ,
2239
+ hr = audio_client3->GetCurrentSharedModeEnginePeriod (¤t_format_ptr ,
2121
2240
¤t_period);
2122
- CoTaskMemFree(current_format);
2123
2241
if (FAILED (hr)) {
2124
2242
LOG (" Could not get current shared mode engine period: error: %lx" , hr);
2125
2243
return false ;
2126
2244
}
2245
+ com_heap_ptr<WAVEFORMATEX> current_format (current_format_ptr);
2246
+ #if 1
2247
+ // Unless some other external app locked the shared mode engine period within our audio initialization,
2248
+ // this likely shouldn't happen
2249
+ if (current_format->nSamplesPerSec != mix_format->nSamplesPerSec )
2250
+ {
2251
+ hr = audio_client3->GetSharedModeEnginePeriod (
2252
+ current_format.get (), &default_period, &fundamental_period, &min_period,
2253
+ &max_period);
2254
+ if (FAILED (hr)) {
2255
+ LOG (" IAudioClient3::GetCurrentSharedModeEnginePeriod() returned a different mixer format (nSamplesPerSec) from IAudioClient::GetMixFormat(); not using IAudioClient3" );
2256
+ return false ;
2257
+ }
2258
+ LOG (" IAudioClient3::GetCurrentSharedModeEnginePeriod() returned a different mixer format (nSamplesPerSec) from IAudioClient::GetMixFormat(); attempting a matching latency" );
2259
+ REFERENCE_TIME current_period_hns = frames_to_hns (current_format->nSamplesPerSec , current_period);
2260
+ current_period = hns_to_frames (mix_format->nSamplesPerSec , current_period_hns);
2261
+ current_period -= current_period % fundamental_period;
2262
+ // Note: the follow up IAudioClient3::InitializeSharedAudioStream() is likely to fail anyway,
2263
+ // given that the period was already locked and we changed it.
2264
+ // The only right way to proceed would be to change the "mix_format->nSamplesPerSec" to "current_format->nSamplesPerSec",
2265
+ // but we can't do that for now.
2266
+ }
2267
+ #endif
2127
2268
2128
- if (current_period >= default_period) {
2129
- LOG("Current shared mode engine period %i too high, not using IAudioClient",
2130
- current_period);
2269
+ #if FORCE_RESPECT_USER_LATENCY
2270
+ // This used to return false if current_period was >= default_latency, but that didn't seem to make sense
2271
+ if (old_requested_latency > current_period) {
2272
+ LOG (" Requested latency %i greater than currently locked shared mode latency %i, not using "
2273
+ " IAudioClient3" ,
2274
+ old_requested_latency, current_period);
2131
2275
return false ;
2132
2276
}
2277
+ #endif
2133
2278
2134
2279
hr = audio_client3->InitializeSharedAudioStream (flags, current_period,
2135
2280
mix_format.get (), NULL );
@@ -2142,7 +2287,6 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
2142
2287
LOG (" Could not initialize shared stream with IAudioClient3: error: %lx" , hr);
2143
2288
return false ;
2144
2289
}
2145
- #endif
2146
2290
2147
2291
#define DIRECTION_NAME (direction == eCapture ? " capture" : " render" )
2148
2292
@@ -2166,6 +2310,8 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
2166
2310
return CUBEB_ERROR;
2167
2311
}
2168
2312
2313
+ const bool has_capture = direction == eCapture || direction == eAll;
2314
+
2169
2315
stm->stream_reset_lock .assert_current_thread_owns ();
2170
2316
// If user doesn't specify a particular device, we can choose another one when
2171
2317
// the given devid is unavailable.
@@ -2202,17 +2348,17 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
2202
2348
2203
2349
/* Get a client. We will get all other interfaces we need from
2204
2350
* this pointer. */
2205
- #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
2206
- hr = device->Activate(__uuidof(IAudioClient3),
2207
- CLSCTX_INPROC_SERVER,
2208
- NULL, audio_client.receive_vpp());
2209
- if (hr == E_NOINTERFACE) {
2210
- #endif
2211
- hr = device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, NULL ,
2212
- audio_client.receive_vpp ());
2213
- #if 0
2351
+ if (!has_capture) {
2352
+ hr = device->Activate (__uuidof (IAudioClient3),
2353
+ CLSCTX_INPROC_SERVER,
2354
+ NULL , audio_client.receive_vpp ());
2355
+ }
2356
+ // IAudioClient3 has problems with capture sessions:
2357
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
2358
+ if (has_capture || hr == E_NOINTERFACE) {
2359
+ hr = device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, NULL ,
2360
+ audio_client.receive_vpp ());
2214
2361
}
2215
- #endif
2216
2362
2217
2363
if (FAILED (hr)) {
2218
2364
LOG (" Could not activate the device to get an audio"
@@ -2341,16 +2487,12 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
2341
2487
}
2342
2488
}
2343
2489
2344
- #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
2345
- if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
2490
+ if (!has_capture && initialize_iaudioclient3 (audio_client, stm, mix_format, flags, direction, latency_hns)) {
2346
2491
LOG (" Initialized with IAudioClient3" );
2347
2492
} else {
2348
- #endif
2349
- hr = audio_client->Initialize (AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0 ,
2350
- mix_format.get (), NULL );
2351
- #if 0
2493
+ hr = audio_client->Initialize (AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0 , mix_format.get (), NULL );
2352
2494
}
2353
- # endif
2495
+
2354
2496
if (FAILED (hr)) {
2355
2497
LOG (" Unable to initialize audio client for %s: %lx." , DIRECTION_NAME, hr);
2356
2498
return CUBEB_ERROR;
@@ -3310,6 +3452,7 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
3310
3452
CUBEB_DEVICE_FMT_S16NE);
3311
3453
ret.default_format = CUBEB_DEVICE_FMT_F32NE;
3312
3454
prop_variant fmtvar;
3455
+ WAVEFORMATEX* wfx = NULL ;
3313
3456
hr = propstore->GetValue (PKEY_AudioEngine_DeviceFormat, &fmtvar);
3314
3457
if (SUCCEEDED (hr) && fmtvar.vt == VT_BLOB) {
3315
3458
if (fmtvar.blob .cbSize == sizeof (PCMWAVEFORMAT)) {
@@ -3319,8 +3462,7 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
3319
3462
ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf .nSamplesPerSec ;
3320
3463
ret.max_channels = pcm->wf .nChannels ;
3321
3464
} else if (fmtvar.blob .cbSize >= sizeof (WAVEFORMATEX)) {
3322
- WAVEFORMATEX * wfx =
3323
- reinterpret_cast <WAVEFORMATEX *>(fmtvar.blob .pBlobData );
3465
+ wfx = reinterpret_cast <WAVEFORMATEX *>(fmtvar.blob .pBlobData );
3324
3466
3325
3467
if (fmtvar.blob .cbSize >= sizeof (WAVEFORMATEX) + wfx->cbSize ||
3326
3468
wfx->wFormatTag == WAVE_FORMAT_PCM) {
@@ -3330,6 +3472,16 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
3330
3472
}
3331
3473
}
3332
3474
3475
+ #if USE_AUDIO_CLIENT_3_LATENCY
3476
+ // Here we guess that an IAudioClient3 stream will successfully be initialized later (it might fail).
3477
+ com_ptr<IAudioClient3> client3;
3478
+ uint32_t def, fun, min, max;
3479
+ if (wfx && SUCCEEDED (dev->Activate (__uuidof (IAudioClient3), CLSCTX_INPROC_SERVER, NULL , client3.receive_vpp ()))
3480
+ && SUCCEEDED (client3->GetSharedModeEnginePeriod (wfx, &def, &fun, &min, &max))) {
3481
+ ret.latency_lo = min;
3482
+ ret.latency_hi = def;
3483
+ } else
3484
+ #endif
3333
3485
if (SUCCEEDED (dev->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER,
3334
3486
NULL , client.receive_vpp ())) &&
3335
3487
SUCCEEDED (client->GetDevicePeriod (&def_period, &min_period))) {
0 commit comments