On Monday, June 18, 2012 9:31:04 AM Andrew Eikum wrote:
We chatted a little on IRC this weekend, but thanks again for the advice.
No problem. :)
Mmdevapi sends it data to the lower systems in period-sized chunks, and the reported buffer fill level decreases in period-sized chunks. But the audio clock position is more accurate than that, returning some value between periods, and taking into account latency.
So the pulse driver would likely need a period-sized intermediary buffer, to store the unwritten samples that can't fill a full period (then keep it stored until enough writes are made to fill it and it's written to the stream).
The PA_STREAM_INTERPOLATE_TIMING and PA_STREAM_AUTO_TIMING_UPDATE flags should provide timing with a good bit of granularity, along with regularly being resync'd to the server's clock.
Unfortunately, PulseAudio (and, apparently, every Linux audio API) refuses to make any guarantees at all with regard to things like buffer sizes, period sizes, callback regularity, and latencies.
Right, which is why you can't rely on what PulseAudio sets for the buffer/tlength size, and you have to manage the expected size yourself, but that's easy. Luckilly PulseAudio supports buffer sizes up to about 2 seconds, I believe, which is good enough for mmdevapi. Unlike ALSA and others, Pulse has no problems handling buffers as large as what mmdevapi supports.
PulseAudio doesn't really do periods. It actually disables audio hardware interrupts when it can and uses high-resolution timers to work out where the audio pointers are at any given time. The period values can pretty much just be emulated and neither pulseaudio or the app should care that much.
Latency issues are what my approach tries to improve. Although ALSA's dmix still does perform better for playback, PulseAudio is at least now comparible and actually usable when it comes to responsive/interactive audio.
For the callback regularity issues, keep in mind that you'll never get 100% guarantees with protected mode code on a multi-tasking OS, so as long as apps work as expected, it's "good enough". However, you can improve over Pulse's callbacks by using a background thread that keeps an eye on the amount of data pulseaudio has chewed through, and trigger any callbacks or events yourself as needed.
It'd be handy if there was some page explaining how PulseAudio's data model works. Stuff like where the data is stored, when and how it's transfered, when callbacks are triggered, what the members of pa_buffer_attr actually mean (their documentation is useless), how the stream flags affect stream operation and the callbacks, when the buffer attributes might change and how applications should deal with those changes. This stuff is all unclear to me, and I think it's a big source of my frustration with the API.
The sample data for playback streams is stored in the server, AFAIK. It's transfered when you call pa_stream_write, and it's either done synchronously or asynchronously depending on who "owns" the buffer it's given. With mmdevapi's design, there's no real problem to let pulse maintain ownership and do the writes asynchronously.
Callbacks are triggered when the client receives the appropriate signals from the server. When exactly the server sends the signal depends on what the signal is and the various settings and attributes.
PulseAudio's documentation isn't all that bad, considering. If you want to talk about bad API docs, look at ALSA.
Yeah, I experimented with ADJUST_LATENCY, as it seems to be the trick to getting lower latencies. Then we have to maintain our own buffer, which is why I switched to using the write callback, to have Pulse tell us when it's ready for more data from our internal buffer.
You shouldn't need a shadow buffer with ADJUST_LATENCY. Just write it to pulse as the app writes it to mmdevapi. Why delay? It can handle the buffer sizes.
This also avoids the timing problems with the write callback. The most you'll need is a temporary storage buffer for when an app writes an incomplete period.