Skip to content

Instantly share code, notes, and snippets.

@mkrautz

mkrautz/pulse.md Secret

Last active January 29, 2017 02:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mkrautz/d0994d82eb900c336d64ac971fc1847e to your computer and use it in GitHub Desktop.
Save mkrautz/d0994d82eb900c336d64ac971fc1847e to your computer and use it in GitHub Desktop.

It's possible to get excessive amounts of data from a Pulse monitor stream...

This isn't reproducible on a very old PulseAudio (0.9.14 from Ubuntu 9.04), but I've been able to reproduce this behavior on most modern versions of PulseAudio (1.1, from Ubuntu 12.04 through 9.0 in Ubuntu 16.10).

ctr is the following program:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <time.h>

static uint64_t now() {
	struct timespec ts;
	int err = clock_gettime(CLOCK_MONOTONIC, &ts);
	if (err == -1) {
		exit(2);
	}
	
	uint64_t out = 0;
	out += (ts.tv_sec * 1000000);
	out += (ts.tv_nsec / 1000);
	return out;
}

int main(int argc, char *argv) {
	uint64_t timestamp = now();
	uint64_t bytes = 0;

	while (1) {
		char buf[4096];

		int nread = read(0, &buf, sizeof(buf));
		if (nread == -1) {
			fprintf(stderr, "err = %i: %s\n", errno, strerror(errno));
			exit(1);
		}

		bytes += nread;

		uint64_t nao = now();
		uint64_t elapsed = nao - timestamp;
		if (elapsed >= 1000000) { // good enough
			fprintf(stderr, "wrote %lu shorts in 1 sec\n", bytes/2);
			bytes = 0;
			timestamp = nao;
		}
	}
}

I run:

mkrautz@ubuntu:~$ parec --format=s16le --rate=48000 --channels=1 --device="alsa_output.pci-0000_02_02.0.analog-stereo.monitor" | ./ctr 
wrote 45124 shorts in 1 sec
wrote 48424 shorts in 1 sec
wrote 48645 shorts in 1 sec
wrote 48545 shorts in 1 sec
wrote 48554 shorts in 1 sec
# I run the loop that triggers the error. (see below)
wrote 92245 shorts in 1 sec
wrote 153388 shorts in 1 sec
wrote 170318 shorts in 1 sec
wrote 151427 shorts in 1 sec
wrote 171301 shorts in 1 sec
wrote 152405 shorts in 1 sec
wrote 126881 shorts in 1 sec
# I've ended running the loop
wrote 48681 shorts in 1 sec
wrote 48354 shorts in 1 sec
wrote 47386 shorts in 1 sec
^C

Mostly I get what I expect: about 48000 samples per second. However, when I trigger underruns in the sink, I can make the monitor produce more samples than expected.

The "trigger" loop being the following snippet:

while [ 1 ]; do dd if=/dev/zero of=/dev/stdout count=1 bs=64 | aplay --rate=8000 -; done

It triggers a lot of underruns for the playback device, which seems to affect the monitor source.

... I'm quite baffled by this behavior from the monitor device. Is it expected that it will generate more samples than what its sample rate is configured at?

This is a problem for us in Mumble (see mumble-voip/mumble#1074). We use the monitor source for our own in-app echo cancellation. However, this issue causes the amount of data we read from the monitor source to be non-deterministic, causing our internal buffers to grow indefinitely.

Is this the expected behavior? Is there a way to catch it via the Pulse API, so we can work around it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment