Re: sio_write blocking?

From: Alexandre Ratchov <alex_at_caoua.org>
Date: Thu, 11 Feb 2021 10:41:55 +0100
On Wed, Feb 10, 2021 at 05:07:05PM -0800, Elijah Stone wrote:
> On Wed, 10 Feb 2021, Alexandre Ratchov wrote:
> 
> > As soon as the hardware consumes buffered samples, space is freed and
> > the same amount could be transmitted with sio_write(). However the code
> > used to transmit the samples may block (eg. network layers may block),
> > and it's difficult to calculate in advance how many bytes could be
> > written without blocking even if we know the amount of free buffer
> > space.
> 
> Ahh, ok; that makes sense, thank you!
> 
> 
> > The SNDCTL_DSP_GETOSPACE-style interfaces used to be used for two
> > different things:
> > 
> > *snip*
> > 
> > (2) determine how many bytes to write. There's no sndio equivalent for
> >     this, the program could use poll() and non-blocking sio_write()
> >     which is the standard unix way to implement event loops.
> 
> This is the closest to my use case.  I'm working on a real-time audio
> library (aimed primarily at games but also other types of applications). It
> supports both a single- and multi-threaded clients.  The model in
> single-threaded mode is, the client calls an update function periodically to
> refill the audio buffer.  Obviously that function can't block, but also
> wants to avoid starvation.
> 
> (Also considering canning the single-threaded mode, as it seems like more
> trouble than it's worth, and it would also obviate this issue.)
> 
> In non-blocking mode, can I assume that I can write buf_avail bytes (per
> your above derivation) without an overrun?  Or is a select/poll loop the way
> to go?

non-blocking sio_write() may write fewer bytes than requested (it will
send data until it blocks or all the data is written). So poll() will
be needed to figure out when to call it again to write the remaining
data.

AFAIU, for games (and real-time synths) the simplest option is to use
blocking sio_write() at the end of the game cycle (once the next frame
is ready to display). In this case, sndio will need to be configured
to buffer at least one game cycle. In pseudo-code:

	/* buffer the equivalent of two video frames */
	par.rate = 48000;
	par.appbufsz = 2 * par.rate / frame_per_second;

	sio_setpar(&par);
	sio_getpar(&par);
	sio_start()

	/* prime with silence (or first chunk of background music) */
	sio_write(silence, par.bufsz * par.pchan * par.bps);

	sample_count = par.rate / frames_per_second;

	while (1) {
		/* check for user input */
		poll(..., 0);
		if (revents & POLLIN)
			process_user_input();

		render_video_frame()
		render_audio_block()

		display_video_frame()
		sio_write(..., sample_count * par.pchan * par.bps);

		wall_clock += sample_count / par.rate;
	}

sio_write() will block, but we've nothing to do at this point. To get
optimal smoothness "sample_count" could be rounded to nearest block
boudary (to sio_par.round boundary).

[...]

If render_video_frame() is so short that it could be neglected (or
split it small parts or defered to a thread), then the same semantincs
could be implemented with a fine-grained event loop with only
non-blocking calls.
Received on Thu Feb 11 2021 - 10:41:55 CET

This archive was generated by hypermail 2.3.0 : Tue Aug 09 2022 - 16:23:51 CEST