Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a code path for better shared mode wasapi on windows #231

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

shangjiaxuan
Copy link

Newer versions of windows exposes a event callback based shared mode, which has much smaller latency (up to less than 10ms) and min max frame counts and os mixing engine frame size can be queried through the new interface.

The added test in refresh device tests for the existence and sets a flag of availability in the device (I normally use c++, I don't know if there's bool type in pure c, if not, may need to change the bool to int), and the stream_do_open will open the new interface if detected earlier, and sets the smallest buffer size. The shared run code also added the path for this event based shared mode.

Since the queried size is in frame count, the path was changed to use frame count instead of reference time for better precision.

Tested on my device, before using this setup, the program continues to beep after a breakpoint is hit for a few seconds, corresponding to the default 4 second buffer size (a really large latency for anything, even multimedia playback is not allowed to have this lag). After using the new one, the time shortens to something acceptable (I cannot estimate with my ears).

Of course, setting the buffer size to something like 20ms in original code has similar effect, but that often ends up with lots of overhead. (It seems typically windows updates sound on a 10ms time frame, incompatible buffer sizes will impose overhead and dropouts.)

@shangjiaxuan
Copy link
Author

shangjiaxuan commented Feb 15, 2020

It seems that on my device, windows updates only on a 10ms basis with 48000Hz floating point (480 frames, max and min are the same, increment also the same), and minimum buffer created has a size of 1056. It seems that the audio system on my device always asks for some padding for re-sampling and does some buffering at the same time with the new code path.

It may be that the buffer size is the total software latency on windows on my device, and buffers are only sent to engine after filled completely in shared mode (and the few seconds of sound after a breakpoint has been hit can be explained).

EDIT:
Running the latency.c will has correct timing of sound and console output time, but after breaking in (program execution paused), there will still be a few beeps before silence. The code seems to only take into account of the time between two writes of time. The audio buffer filled, however, is always much larger than the two writes, thus making the test invalid (only tests the performance of between-thread transfer of data.)

@shangjiaxuan
Copy link
Author

It seems that there are also and issues pr not merged for this #174, will look further into it. Also #109 seems related.

@wegylexy
Copy link
Contributor

Something has changed since Windows 10 v2004, does this patch solve those issues too?

Copy link
Contributor

@wegylexy wegylexy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the extra +.

src/wasapi.c Outdated
Comment on lines 103 to 110
+static const IID IID_IAudioEndpointVolume = {
+ //MIDL_INTERFACE("5CDF2C82-841E-4546-9722-0CF74078229A")
+0x5cdf2c82, 0x841e, 0x4546, {0x97, 0x22, 0x0c, 0xf7, 0x40, 0x78, 0x22, 0x9a}
+};
+static const IID IID_IAudioEndpointVolumeCallback = {
+ //MIDL_INTERFACE("657804FA-D6AD-4496-8A60-352752AF4F89")
+0x657804fa, 0xd6ad, 0x4496, {0x8a, 0x60, 0x35, 0x27, 0x52, 0xaf, 0x4f, 0x89}
+};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have extra + in front of these lines.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@wegylexy
Copy link
Contributor

wegylexy commented Aug 1, 2020

You mostly changed for outstream only. How about instream?

@shangjiaxuan
Copy link
Author

This is mainly meant for better implementing a buffer size negotiation and callback based buffer submission. The main code path is no different from the original, only that the buffer can better accommodate the shared mode engine period, thus reducing the buffer size while not making audio dropouts.

I doubt if audio input will benefit from this as long as it is using a callback based implementation, since the available buffer size is determined by the os at wakeup time.

It is possible to change the os audio engine period to something much less than the default 10ms, but I did not have the hardware to test it (mine reports 480 to 480 samples working on native 48kHz float), thus currently only the current period is used.

Also when using smaller frames, like the raw mode on my device, there is a constant flow of dropouts. It seems that the thread scheduler cannot work on that much granularity, and must use the realtime work queue API for scheduling if such precision is needed.

The + was there when I was diffing, I'm sorry for that inconvenience. It was there probably when I was testing to compile as C code or CPP code, since the GUID const reference function prototype is different. The realtime work queue thing may need a lot more effort and change to implement.

@shangjiaxuan
Copy link
Author

WIP

@wegylexy
Copy link
Contributor

wegylexy commented Aug 3, 2020

See also https://chromium.googlesource.com/chromium/src/media/+/refs/heads/master/audio/win/core_audio_util_win.cc for things like GetDevicePeriod, and handling of WAVEFORMATEX that is not WAVEFORMATEXTENSIBLE (e.g. PCM, IEEE_FLOAT).

@shangjiaxuan
Copy link
Author

GetDevicePeriod only retrieves the device period, but in shared mode this does not map to the buffer submission size. In shared mode, the OS audio engine will gather buffer from all the apps and mix them (may or may not be in hardware), and then submit to the output. This shared mode buffer size is typically a few times larger than the output buffer size to enable mixing and resampling different streams to the hardware format. Shared mode cannot work on the buffer size retrieved from GetDevicePeriod without audio glitches. (And the smallest raw mode period typically cannot be achieved by the thread scheduler, with period that may be only 1-2ms, which is close to the thread cpu time frame.)

Thanks again for pointing to a working implementation, I'm adding a bit of non-WAVEFORMATEXTENSIBLE (8 bit and 16 bit pcm support, others may not work and should return invalid) in the WIP pull.

@shangjiaxuan
Copy link
Author

The phnsDefaultDevicePeriod parameter specifies the default scheduling period for a shared-mode stream. The phnsMinimumDevicePeriod parameter specifies the minimum scheduling period for an exclusive-mode stream.

It seems that can be used when audio3 is not available, will put this into the WIP.

Also fix pausing logic that wwnt wrong some time ago.
@shangjiaxuan shangjiaxuan reopened this Aug 3, 2020
@@ -0,0 +1,163 @@
#ifndef __MSVC_STDATOMIC_H
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This atomic implementation is for msvc to build with c compiler, since windows sdk does not include a C atomic implementation (mingw should be configured to exclude this).

Code from github repo:
https://github.com/zenny-chen/simple-stdatomic-for-VS-Clang
under apache 2.0 license.
(maybe add a license note?)

src/config.h Outdated
Comment on lines 1 to 22
/*
* Copyright (c) 2015 Andrew Kelley
*
* This file is part of libsoundio, which is MIT licensed.
* See http://opensource.org/licenses/MIT
*/

#ifndef SOUNDIO_CONFIG_H
#define SOUNDIO_CONFIG_H

#define SOUNDIO_VERSION_MAJOR 2
#define SOUNDIO_VERSION_MINOR 0
#define SOUNDIO_VERSION_PATCH 0
#define SOUNDIO_VERSION_STRING "2.0.0"

/* #undef SOUNDIO_HAVE_JACK */
/* #undef SOUNDIO_HAVE_PULSEAUDIO */
/* #undef SOUNDIO_HAVE_ALSA */
/* #undef SOUNDIO_HAVE_COREAUDIO */
#define SOUNDIO_HAVE_WASAPI

#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config.h is not meant to be checked in, is it?

if (wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
from_channel_mask_layout(wave_format->dwChannelMask, layout);
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreachable due to assert() above.

src/wasapi.c Outdated
Comment on lines 1550 to 1553
dev_w_raw->iaudio3_available=true;
dev_w_shared->iaudio3_available = true;
dev_w_raw->iaudio3_available = false;
dev_w_shared->iaudio3_available = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true or false?

Remove code added to test shared mode without iaudioclient3 and the assertion added when the extensible problem is first known
@shangjiaxuan
Copy link
Author

shangjiaxuan commented Aug 4, 2020

Fixed problems listed. They are mainly code for testing and initial waveformatex support. Sorry that I forgot to look at the diffs and made such mistake.

Also fixed pausing the original shared mode (It seems only stopping the stream, and then supplying buffers to it will somehow restart it on my PC (sdk 1903) currently, may be because of the smaller buffer size from GetPeroid exposed this problem or because of driver change (the original 4 second buffer may be much larger than the time from pause to unpause).)

@wegylexy
Copy link
Contributor

wegylexy commented Aug 4, 2020

It works on v2004. Will test on v1909 soon.

This is mainly a sanity check, since paused stream should not be calling callbacks
@0x08088405
Copy link

Newer versions of windows exposes a event callback based shared mode,

@shangjiaxuan Sorry for bumping an old issue but is event mode exclusive to newer versions of IAudioClient? I know MSDN isn't great but does it mention this anywhere? Writing my own implementation based on libsoundio and that caught my eye.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants