/* Test program to compare latency of different flushing methods
* By Magnus Hjorth
*
* To compile:
* gcc pa_flush_test.c -o pa_flush_test -Wall -O2 -lm \
* `pkg-config --cflags --libs libpulse`
*
* History:
* 2013-07-16, MH, First version
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <math.h>
#include <pulse/pulseaudio.h>
static void check1(void *p)
{
if (p == NULL) { fputs("Unexpected NULL pointer\n",stderr); exit(1); }
}
static void check2(int i)
{
if (i < 0) { fprintf(stderr,"Error: %s\n",pa_strerror(i)); exit(1); }
}
static struct termios origattr;
static void setup_term(void)
{
int i;
struct termios attr;
if (isatty(0)) {
i = tcgetattr(0,&attr);
if (i < 0) { perror("tcgetattr"); exit(1); }
memcpy(&origattr,&attr,sizeof(origattr));
attr.c_lflag &= ~(ICANON|ECHO);
i = tcsetattr(0,TCSANOW,&attr);
if (i < 0) { perror("tcsetattr"); exit(1); }
}
}
static void restore_term(void)
{
if (isatty(0))
tcsetattr(0,TCSANOW,&origattr);
}
static void cx_state_cb(pa_context *c, void *userdata)
{
pa_context_state_t *csp = (pa_context_state_t *)userdata;
pa_context_state_t cs;
cs = pa_context_get_state(c);
if (!PA_CONTEXT_IS_GOOD(cs)) {
fprintf(stderr,"Bad context state: %d\n",cs);
exit(1);
}
*csp = cs;
}
static void sm_state_cb(pa_stream *p, void *userdata)
{
pa_stream_state_t smst;
smst = pa_stream_get_state(p);
if (!PA_STREAM_IS_GOOD(smst)) {
fprintf(stderr,"Bad stream state: %d\n",smst);
exit(1);
}
}
static long soundpos=0;
static float ang = 0.0;
static void write_sound(pa_stream *p, size_t nbytes, int relread)
{
void *buf;
float *fbuf;
size_t s,nf;
int i;
while (nbytes > 0) {
s = nbytes;
i = pa_stream_begin_write(p,&buf,&s);
check2(i);
fbuf = buf;
nf = s / 4;
for (i=0; i<nf; i++,soundpos++) {
ang += (220.0/44100.0) * 2.0 * M_PI;
fbuf[i] = sinf( ang );
if (soundpos > 2205) fbuf[i] *= 0.005;
}
i = pa_stream_write(p,fbuf,s,NULL,0,
relread ? PA_SEEK_RELATIVE_ON_READ :
PA_SEEK_RELATIVE);
check2(i);
nbytes -= s;
}
}
static void sm_write_cb(pa_stream *p, size_t nbytes, void *userdata)
{
write_sound(p,nbytes,0);
}
static pa_stream *create_stream(pa_context *cx)
{
pa_sample_spec ss={PA_SAMPLE_FLOAT32NE,44100,1};
pa_stream *sm;
sm = pa_stream_new(cx, "test", &ss, NULL);
check1(sm);
pa_stream_set_state_callback(sm,sm_state_cb,NULL);
pa_stream_set_write_callback(sm,sm_write_cb,NULL);
pa_stream_connect_playback(sm,NULL,NULL,0,NULL,NULL);
return sm;
}
static void keyboard_cb(pa_mainloop_api *ea, pa_io_event *e, int fd,
pa_io_event_flags_t events, void *userdata)
{
char c;
pa_operation *op;
pa_stream **sm = (pa_stream **)userdata;
pa_context *ctx;
int i;
read(0,&c,1);
/* printf("keyboard_cb: %c\n",c); */
switch (c) {
case '0':
soundpos = 0;
break;
case '1':
op = pa_stream_flush(*sm, NULL, NULL);
pa_operation_unref(op);
soundpos = 0;
break;
case '2':
soundpos = 0;
write_sound(*sm, 256, 1);
break;
case '3':
ctx = pa_stream_get_context(*sm);
pa_stream_set_state_callback(*sm, NULL, NULL);
i = pa_stream_disconnect(*sm);
check2(i);
pa_stream_unref(*sm);
*sm = create_stream(ctx);
soundpos = 0;
break;
};
}
int main(int argc, char **argv)
{
pa_mainloop *ml;
pa_mainloop_api *mlapi;
pa_context *cx;
int i;
pa_context_state_t cs=PA_CONTEXT_UNCONNECTED;
pa_stream *sm;
setup_term();
atexit(restore_term);
ml = pa_mainloop_new();
check1(ml);
mlapi = pa_mainloop_get_api(ml);
cx = pa_context_new(mlapi,"flushtest");
check1(cx);
pa_context_set_state_callback(cx, cx_state_cb, &cs);
i = pa_context_connect(cx, NULL, 0, NULL);
check2(i);
while (cs != PA_CONTEXT_READY)
pa_mainloop_iterate(ml,1,NULL);
sm = create_stream(cx);
puts(" PulseAudio flush latency test");
puts(" Keys:");
puts(" 0 = No flushing (full latency)");
puts(" 1 = pa_stream_flush");
puts(" 2 = pa_stream_write with PA_SEEK_RELATIVE_ON_READ");
puts(" 3 = disconnect/reconnect stream");
puts(" Ctrl-C = exit");
mlapi->io_new(mlapi,0,PA_IO_EVENT_INPUT,keyboard_cb,&sm);
pa_mainloop_run(ml,NULL);
return 0;
}