The problem came up initially with God of War Ragnarok. The game uses two mmdevapi audio clients set up identically (different period is not possible in shared mode anyway, and the game is using the same duration). Upon getting an event it checks with GetCurrentPadding that enough space is available in the first client buffer. Then, if that is the case, it will expect that the same will hold for GetCurrentPadding on another client, and if that doesn't hold it will treat that as some sort of error and recreate the audio client. That effectively results in the stream being recreated each second, delaying the audio updates performed from the same thread and leading to noticable audio popping on some setups.
While that might look as some gory implementation detail, it may actually be not. If the audio clients are from the same device at least, it is natural that the driver would process the data for streams in a single quantum, updating all the shared mode clients in one go. Some testing shows that it is the case on Windows. I am attaching one way of checking that in test. I didn't include that in the MR because after my patch it will behave differently with different Wine drivers (while the others should probably be fixed as well; while not apparent if that worth the effort for legacy alsa driver); also such a test is a bit flaky by nature. On Windows I could not reproduce any case of the second event been not signaled after the first one is. On present Wine the delay is usually some few ms. With my patch the observed delay is some 100-300mcs. That is probably impossible to sync exactly (and maybe the scheduling difference on Windows make it look like all the events are signaled at once ), but this is not the issue: if the app depends on GetCurrentPadding result that is synced with pulse lock anyway.
Thinking of it, once a game uses different audio clients for some reason it is not exactly obvious to support unsynchronized streams timeline: all that should be bound to game time, and apparently should be rendered for the current period, desync updates are convoluted.
Present implementation implements delay adjustments. Just dropping those from existing handling in timer_proc() will immediately introduce underruns. But not because of actual stream timeline difference but because it uses relative delay and processing of stream data / restarting delay adds to it. In my patch the timer is set on absolute Pulse Audio timeline, so such sort of corrections are already included there, and after some testing underruns weren't observed.
Of course it is possible that there is systematic difference in stream time vs global pulse audio clock. But this difference, if present, is supposed to be very small and I hope we may ignore it. If it turns out that we can't for some reason, I think we may at least assume that all the streams on the same device have the same timeline. And then create separate periods per device and adjust the time based on some one stream in the period or on average. But it seems to me that is not needed.
[0001-mmdevapi-tests-Test-audio-clients-sync.patch](/uploads/e325be326736b2551308add1f69c14a4/0001-mmdevapi-tests-Test-audio-clients-sync.patch)