[alsa-devel] [PATCH] Wrong latency in pulseaudio plugin breaks Adobe Flash Player

Philip Spencer pspencer at fields.utoronto.ca
Thu Feb 16 16:48:12 CET 2012


When Adobe Flash player is playing a live stream in "live" (no internal 
latency) mode[1], through alsa's pulseaudio plugin, the audio is a 
half-second out of sync with the video. This is with alsa-plugins version 
1.0.25.[2]

I see the problem is one already reported on the bug tracker several years 
ago (issue #3944). The plugin sets pulseaudio's target buffer length to
match the IO buffer length, and sets the prebuffering attribute to match
the IO buffer length less one period.

In our case, the IO buffer is 500ms long, the IO period length is 20ms 
(for 16KHz Speex soud packets), and the application requests that playback 
start immediately (by setting the start_threshold software parameter very 
low).

However, the plugin ignores start_threshold and sets the pulseaudio target 
buffer length to 500ms, meaning pulseaudio plays the sound with a 
half-second delay.

The fix initially suggested in the bug report was to implement the 
start_threshold parameter and use it to lower the prebuffering attribute 
but this does not work: playback STARTS sooner, but when dropouts and 
underruns occur pulseaudio increases the latency to match the target 
buffer length. It is necessary to lower the target buffer length too.

Below is a proposed patch (my apologies if I've missed some 
subtleties as I've never looked at the alsa source code before). The patch
changes the plugin to do the following:

   -- decouple the length of the fake ring buffer seen by ALSA (which needs
      to match the IO buffer length) from the "target length" parameter
      sent to pulseaudio (which needs to reflect the desired latency),
      adding a new variable to the structure to store the former value.
      Currently they are forced to be the same value.

   -- when the start_threshold software parameter is seen, lower both
      the target length and prebuffering attributes to match it, except
      don't set them less than one IO period or longer than the IO buffer
      length

   -- when hardware parameters are seen, don't mess with the target length
      or prebuffering attributes if already set by software.

It doesn't currently tell pulseaudio to change its parameters if the
start_threshold is enountered after the stream is already started, but
that could be added too if necessary with
          if (pcm->stream) pa_stream_set_buffer_attr( ... )

I hope this patch (or something like it) can get put into an alsa release
since otherwise the Flash Player 2-way interaction app we're developing 
won't be usable by Linux users unless it runs in about 750ms time-delayed 
mode!

Thank you,

Philip

[1] Normally Flash Player plays prerecorded streams with a slight internal 
buffering delay, and in this mode it requests the audio be buffered for 
500ms in addition to its own internal delay, and it automatically delays 
video frames for 500ms beyond its own internal delay to compensate, so 
there are no out-of-sync problems with prerecorded streams or live streams 
played in regular buffered mode.

[2] In 1.0.23 with low-sample-rate streams there was no sync problem but
each sound sample was truncated, resulting in distorted and crackly sound; 
this problem has been fixed in 1.0.25 -- thanks!

--------------------------------------------+-------------------------------
Philip Spencer  pspencer at fields.utoronto.ca | Director of Computing Services
Room 336        (416)-348-9710  ext3036     | The Fields Institute for
222 College St, Toronto ON M5T 3J1 Canada   | Research in Mathematical Sciences

Signed-Of-By: Philip Spencer <pspencer at fields.utoronto.ca>

--- alsa-plugins-1.0.25.orig/pulse/pcm_pulse.c	2012-01-25 02:57:07.000000000 -0500
+++ alsa-plugins-1.0.25/pulse/pcm_pulse.c	2012-02-15 16:23:08.000000000 -0500
@@ -49,6 +49,9 @@
  	pa_sample_spec ss;
  	size_t frame_size;
  	pa_buffer_attr buffer_attr;
+	uint32_t ring_buffer_length;
+	int tlength_set_by_sw;
+
  } snd_pcm_pulse_t;

  static int check_stream(snd_pcm_pulse_t *pcm)
@@ -93,12 +96,12 @@
  		size -= pcm->offset;

  	/* Prevent accidental overrun of the fake ringbuffer */
-	if (size > pcm->buffer_attr.tlength - pcm->frame_size)
-		size = pcm->buffer_attr.tlength - pcm->frame_size;
+	if (size > pcm->ring_buffer_length - pcm->frame_size)
+		size = pcm->ring_buffer_length - pcm->frame_size;

  	if (size > pcm->last_size) {
  		pcm->ptr += size - pcm->last_size;
-		pcm->ptr %= pcm->buffer_attr.tlength;
+		pcm->ptr %= pcm->ring_buffer_length;
  	}

  	pcm->last_size = size;
@@ -830,12 +833,16 @@
  	pcm->ss.rate = io->rate;
  	pcm->ss.channels = io->channels;

+	pcm->ring_buffer_length =
+		io->buffer_size * pcm->frame_size;
  	pcm->buffer_attr.maxlength =
  		4 * 1024 * 1024;
-	pcm->buffer_attr.tlength =
-		io->buffer_size * pcm->frame_size;
-	pcm->buffer_attr.prebuf =
-	    (io->buffer_size - io->period_size) * pcm->frame_size;
+	if (! pcm->tlength_set_by_sw) {
+		pcm->buffer_attr.tlength =
+			io->buffer_size * pcm->frame_size;
+		pcm->buffer_attr.prebuf =
+			(io->buffer_size - io->period_size) * pcm->frame_size;
+	}
  	pcm->buffer_attr.minreq = io->period_size * pcm->frame_size;
  	pcm->buffer_attr.fragsize = io->period_size * pcm->frame_size;

@@ -845,6 +852,36 @@
  	return err;
  }

+static int pulse_sw_params(snd_pcm_ioplug_t * io,
+			   snd_pcm_sw_params_t * params)
+{
+	snd_pcm_pulse_t *pcm = io->private_data;
+	int err = 0;
+
+	assert(pcm);
+
+	if (!pcm->p || !pcm->p->mainloop)
+		return -EBADFD;
+
+	pa_threaded_mainloop_lock(pcm->p->mainloop);
+	snd_pcm_uframes_t start_thresh;
+	err = snd_pcm_sw_params_get_start_threshold(params, &start_thresh);
+ 	if (! err) {
+		if (start_thresh <  io->period_size)
+			start_thresh = io->period_size;
+		if (start_thresh > io->buffer_size)
+			start_thresh = io->buffer_size;
+
+		pcm->buffer_attr.tlength = start_thresh * pcm->frame_size;
+		pcm->buffer_attr.prebuf = start_thresh * pcm->frame_size;
+		pcm->tlength_set_by_sw = 1;
+	}
+
+	pa_threaded_mainloop_unlock(pcm->p->mainloop);
+	return err;
+
+}
+
  static int pulse_close(snd_pcm_ioplug_t * io)
  {
  	snd_pcm_pulse_t *pcm = io->private_data;
@@ -911,6 +948,7 @@
  	.poll_revents = pulse_pcm_poll_revents,
  	.prepare = pulse_prepare,
  	.hw_params = pulse_hw_params,
+	.sw_params = pulse_sw_params,
  	.close = pulse_close,
  	.pause = pulse_pause
  };
@@ -1080,6 +1118,7 @@
  	pcm->io.mmap_rw = 0;
  	pcm->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
  	    &pulse_playback_callback : &pulse_capture_callback;
+	pcm->tlength_set_by_sw = 0;
  	pcm->io.private_data = pcm;

  	err = snd_pcm_ioplug_create(&pcm->io, name, stream, mode);






More information about the Alsa-devel mailing list