Good afternoon, all,
I am new to ALSA development, but experienced in C. I wrote up a little test application which simply opens one playback and one capture device and spits noise into the playback and recording it on the capture device. I cross- correlate the data streams to determine the "depth" or latency of the pipe, end-to-end.
I'm using playback and capture callbacks to handle the audio.
The program seems to work reasonably well, but at seemingly random times either the playback or capture stream will "hiccup" (xrun) -- I am handling this case, but it's fairly often that the stream does NOT resume and I'm stuck with either no playback or no capture.
I've looked at the example programs and the documentation and I do not see what it is I'm doing wrong. I'm posting my code here, hopefully someone can show me the error of my way.
Capture/playback setup (error checking removed): snd_pcm_open(handle, fname, stream, SND_PCM_NONBLOCK); snd_pcm_hw_params_malloc(hwptr); snd_pcm_hw_params_any(h, hwp); snd_pcm_hw_params_set_access(h, hwp, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(h, hwp, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_rate(h, hwp, 8000, 0); snd_pcm_hw_params_set_channels(h, hwp, 2); snd_pcm_hw_params_set_buffer_size(h, hwp, BUF_SIZE); snd_pcm_hw_params(h, hwp);
At this point I have the capture or playback device set up for 16 bit signed linear data in interleaved format and two channels. I've set up the buffers to be 160 frames, or 20ms. Each of these calls is checked for errors, and I do verify that the rate, buffer size and buffer time are what I expect. There is nothing unusual going on with these calls that I can determine.
I set up the playback and capture callbacks thusly (again checking for errors) snd_async_add_pcm_handler(&ahp, hp, playback_callback, &pb_data); snd_async_add_pcm_handler(&ahc, hc, capture_callback, &cap_data);
At this point I believe I'm ready to go, all I have to do is start playback and capture: if((fcount = snd_pcm_writei(h[PLAYBACK], pb_data.buf, BUF_SIZE)) != BUF_SIZE) { if(fcount < 0) { if((e = snd_pcm_recover(h[PLAYBACK], fcount, 0)) < 0) fprintf(stderr, "initial: can't writei: %s\n", snd_strerror(e)); } else { fprintf(stderr, "initial: short write: %d instead of %d\n", fcount, BUF_SIZE); } }
if((e = snd_pcm_start(h[CAPTURE])) < 0) { fprintf(stderr, "capture prepare error: %s\n", snd_strerror(e)); return 1; }
Now the playback and capture handlers are called by ALSA as required. The capture handler looks like this:
/* capture callback. Called by ALSA whenever a period time is up */ void capture_callback(snd_async_handler_t *ah) { snd_pcm_t *h = snd_async_handler_get_pcm(ah); struct async_data *d = snd_async_handler_get_callback_private(ah); snd_pcm_sframes_t fa; short *p; int i, fc, e, faloops; struct timeval tv; static int c=0;
/* read in whatever is available */
fa = snd_pcm_avail_update(h); faloops = 0; while(fa > 0) { if(fa > BUF_SIZE) fa = BUF_SIZE;
if((fc = snd_pcm_readi(h, d->buf, fa)) != fa) { if(fc < 0) { if((e = snd_pcm_recover(h, e, 0)) < 0) { fprintf(stderr, "callback: can't readi: %s\n", snd_strerror(e)); }
fc = 0; } else { fprintf(stderr, "callback: short read: %d instead of %ld\n", fc, fa); } }
for(i=0,p=&d->buf[0]; i<fc; i++) { *d->ptr++ = *p++; p++;
if(d->ptr > &in[MAX_BUFS * (BUF_SIZE - 1)]) { d->ptr = (short *)&in[0]; } }
c += fc; if(c >= BUF_SIZE) { gettimeofday(&tv, NULL); c -= BUF_SIZE;
in_step++; if(in_step >= MAX_BUFS) in_step = 0; }
frames_cap += fc; fa = snd_pcm_avail_update(h); faloops++; };
cap_faloops=faloops; }
The playback callback is very similar:
/* playback callback. Called by ALSA whenever a period time is up */ void playback_callback(snd_async_handler_t *ah) { snd_pcm_t *h = snd_async_handler_get_pcm(ah); struct async_data *d = snd_async_handler_get_callback_private(ah); snd_pcm_sframes_t fa; int i, fc, e, faloops; static int c=0; struct timeval tv;
fa = snd_pcm_avail_update(h); faloops = 0; while(fa > 0) { if(fa > BUF_SIZE) fa = BUF_SIZE;
for(i=0; i<fa; i++) { d->buf[i*2] = awgn(&noise_source); d->buf[i*2+1] = awgn(&noise_source); }
if((fc = snd_pcm_writei(h, d->buf, fa)) != fa) { if(fc < 0) { if((e = snd_pcm_recover(h, e, 0)) < 0) { fprintf(stderr, "callback: can't writei: error %x %s\n", e, snd_strerror(e)); }
fc = 0; } else { fprintf(stderr, "callback: short write: %d instead of %ld\n", fc, fa); } }
for(i=0; i<fc; i++) { *d->ptr++ = d->buf[i*2]; if(d->ptr > &out[MAX_BUFS * (BUF_SIZE - 1)]) { d->ptr = (short *)&out[0]; } }
c += fc; if(c >= BUF_SIZE) { gettimeofday(&tv, NULL); c -= BUF_SIZE; out_step++; if(out_step >= MAX_BUFS) out_step = 0; }
frames_pb += fc; fa = snd_pcm_avail_update(h); faloops++; };
pb_faloops = faloops; }
That's really it; the main loop keeps track of the in_step and out_step variables and does the cross-correlation as the data comes in. Now it seems that it's the capture playback that is getting xruns, but I when capture xruns occur my playback callback seems to stop, and sometimes the entire program just hangs as if it is waiting for ALSA to call one of the callbacks.
Can anyone shed any light on this? I'm certain it's something very simple and braindead, but I can't find it.
Regards, Andrew