The attached diff has my change to aplay.c:capture_go and supporting globals, to implement a two-thread double-buffering approach to capture. I kept having overruns, due apparently to file system lock contention in RedHat 9 (2.4.20-8).
The approach is to allocate a 10-second-long buffer (rounded up to a multiple of chunk-size), start a disk-write thread to follow behind the original thread, and if the file system lock causes the write to block for a while, capturing continues unabated. 10 seconds has been sufficient so far; the longest FS lock I could cause was 9.5 seconds. No overruns (with a big enough buffer-size) since this patch has been in use!
I release the IP in this patch to the ALSA project for inclusion, should the pertinent community members find it worthwhile.
Cheers...
Eric Weaver Palo Alto, CA
*** aplay.c.orig 2003-11-28 02:24:53.000000000 -0800
--- aplay.c 2004-03-03 16:38:47.000000000 -0800
***************
*** 38,47 ****
--- 38,49 ----
#include <errno.h>
#include <alsa/asoundlib.h>
#include <assert.h>
+ #include <sched.h> /* Weav */
#include <sys/poll.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/signal.h>
+ #include <sys/wait.h>
#include "aconfig.h"
#include "formats.h"
#include "version.h"
***************
*** 78,83 ****
--- 80,88 ----
static int interleaved = 1;
static int nonblock = 0;
static char *audiobuf = NULL;
+ static char *audiobuf_writep = NULL; /* Weav */
+ static char *audiobuf_readp = NULL; /* Weav */
+ static int capture_done = 0;
static snd_pcm_uframes_t chunk_size = 0;
static unsigned period_time = 0;
static unsigned buffer_time = 0;
***************
*** 90,101 ****
--- 95,111 ----
static int buffer_pos = 0;
static size_t bits_per_sample, bits_per_frame;
static size_t chunk_bytes;
+ static size_t audiobuf_bytes; /* Weav */
static snd_output_t *log;
static int fd = -1;
static off64_t pbrec_count = (size_t)-1, fdcount;
static int vocmajor, vocminor;
+ static int thread_stack_elts = 10240;
+ static char *thread_stack[10240];
+
+
/* needed prototypes */
static void playback(char *filename);
***************
*** 524,529 ****
--- 534,540 ----
error("not enough memory");
return 1;
}
+ audiobuf_writep = audiobuf_readp = audiobuf; /* Weav */
if (mmap_flag) {
writei_func = snd_pcm_mmap_writei;
***************
*** 932,942 ****
bits_per_sample = snd_pcm_format_physical_width(hwparams.format);
bits_per_frame = bits_per_sample * hwparams.channels;
chunk_bytes = chunk_size * bits_per_frame / 8;
! audiobuf = realloc(audiobuf, chunk_bytes);
if (audiobuf == NULL) {
error("not enough memory");
exit(EXIT_FAILURE);
}
// fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n",
chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size);
}
--- 943,960 ----
bits_per_sample = snd_pcm_format_physical_width(hwparams.format);
bits_per_frame = bits_per_sample * hwparams.channels;
chunk_bytes = chunk_size * bits_per_frame / 8;
! audiobuf_bytes = 10 * rate * bits_per_frame / 8; /* Weav */
! audiobuf_bytes += chunk_bytes - (audiobuf_bytes % chunk_bytes);
! #if 0
! d_printf("audiobuf size = %d (0x%x) bytes\n",
! audiobuf_bytes, audiobuf_bytes);
! #endif
! audiobuf = realloc(audiobuf, audiobuf_bytes); /* Weav */
if (audiobuf == NULL) {
error("not enough memory");
exit(EXIT_FAILURE);
}
+ audiobuf_writep = audiobuf_readp = audiobuf; /* Weav */
// fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n",
chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size);
}
***************
*** 1817,1847 ****
/* capturing raw data, this proc handels WAVE files and .VOCs (as one block) */
void capture_go(int fd, size_t count, int rtype, char *name)
{
size_t c, cur;
ssize_t r, err;
header(rtype, name);
set_params();
!
do {
for (cur = count; cur > 0; cur -= r) {
c = cur;
if (c > chunk_bytes)
c = chunk_bytes;
c = c * 8 / bits_per_frame;
! if ((size_t)(r = pcm_read(audiobuf, c)) != c)
break;
r = r * bits_per_frame / 8;
if ((err = write(fd, audiobuf, r)) != r) {
perror(name);
exit(EXIT_FAILURE);
}
if (err > 0)
fdcount += err;
}
} while (rtype == FORMAT_RAW && !timelimit);
}
/*
--- 1835,1976 ----
/* capturing raw data, this proc handels WAVE files and .VOCs (as one block) */
+
+ int capture_writeloop (void *arg)
+ {
+ int fd = *(int*)arg;
+
+ #if 0
+ d_printf("Entering capture_writeloop; fd = %d\n", fd);
+ #endif
+ do {
+ #if 0
+ d_printf("readp = 0x%x; writep = 0x%x; cd=%d\n",
+ audiobuf_readp, audiobuf_writep, capture_done);
+ #endif
+ if (audiobuf_readp > audiobuf_writep) { /* wrapped */
+ int bytes = write(fd, audiobuf_readp, audiobuf_bytes - (audiobuf_readp -
audiobuf));
+ if (bytes < 0) {
+ perror("capture_writeloop: write");
+ return 0;
+ }
+ fdcount += bytes;
+ #if 0
+ d_printf("Wrote %d bytes from 0x%x\n", bytes, audiobuf_readp);
+ #endif
+ audiobuf_readp += bytes;
+ if (audiobuf_readp >= audiobuf + audiobuf_bytes) /* (un)wrap... */
+ audiobuf_readp = audiobuf;
+ }
+ if (audiobuf_writep > audiobuf_readp) { /* Got some ahead of us */
+ int bytes = write (fd, audiobuf_readp, audiobuf_writep - audiobuf_readp);
+ if (bytes < 0) {
+ perror("capture_writeloop: write");
+ return 0;
+ }
+ fdcount += bytes;
+ #if 0
+ d_printf("Wrote %d bytes from 0x%x\n", bytes, audiobuf_readp);
+ #endif
+ audiobuf_readp += bytes;
+ }
+ else
+ /* nanosleep(500000000); /* 1/2 sec. */
+ sleep(1);
+ } while (!capture_done || (audiobuf_readp != audiobuf_writep));
+ return 0;
+ }
+
+
void capture_go(int fd, size_t count, int rtype, char *name)
{
size_t c, cur;
ssize_t r, err;
+ int cloneid;
+ pthread_t threadid;
+ pthread_attr_t thread_attr;
+ /*
+ int writeloop_stack_elts = 1024 * 10;
+ char *writeloop_stack = malloc(writeloop_stack_elts);
+ */
+ void *clone_stack_ptr = &thread_stack[(thread_stack_elts - 8) & ~7];
header(rtype, name);
set_params();
! capture_done = 0;
! #if 0
! d_printf("thread_stack = 0x%x; &fd = 0x%x, fd = %d\n",
! thread_stack, &fd, fd);
! d_printf("clone_stack_ptr = 0x%x\n", clone_stack_ptr);
! #endif
! cloneid = clone(&capture_writeloop, clone_stack_ptr,
! CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
! CLONE_PTRACE | CLONE_VM |
! /* CLONE_THREAD | */ /* gives EINVAL */
! SIGCHLD,
! &fd);
! /* pthread_attr_init(&thread_attr);
! cloneid = pthread_create(&thread,
! */
! if (cloneid < 0) {
! perror("capture_go: clone");
! exit(1);
! }
! #if 0
! d_printf("cloneid = %d\n", cloneid);
! #endif
do {
for (cur = count; cur > 0; cur -= r) {
c = cur;
if (c > chunk_bytes)
c = chunk_bytes;
+ /* Limit to audiobuf extent... */
+ if (audiobuf_writep + c > audiobuf + audiobuf_bytes)
+ c = (audiobuf + audiobuf_bytes - audiobuf_writep) ;
c = c * 8 / bits_per_frame;
! if (c == 0) {
! struct timespec howlong = {0, 100000000};
! #if 0
! d_printf ("No room! Waiting...\n");
! #endif
! r = 0;
! nanosleep(&howlong, NULL); /* 1/10 sec. */
! continue;
! }
! if (audiobuf_writep < audiobuf_readp &&
! audiobuf_writep + c >= audiobuf_readp )
! c = (audiobuf_readp - audiobuf_writep) * 8 / bits_per_frame;
! #if 0
! d_printf("Calling pcm_read for %d frames at 0x%x\n",
! c, audiobuf_writep);
! #endif
! if ((size_t)(r = pcm_read(audiobuf_writep, c)) != c)
break;
r = r * bits_per_frame / 8;
+ #if 0
+ d_printf("Read %d (0x%x) PCM bytes out of %d requested at
0x%x\n",
+ r, r, c * bits_per_frame / 8, audiobuf_writep);
+ #endif
+ if (audiobuf_writep + r >= audiobuf + audiobuf_bytes)
+ audiobuf_writep = audiobuf;
+ else
+ audiobuf_writep += r;
+ /*
if ((err = write(fd, audiobuf, r)) != r) {
perror(name);
exit(EXIT_FAILURE);
}
if (err > 0)
fdcount += err;
+ */
}
} while (rtype == FORMAT_RAW && !timelimit);
+ capture_done = 1; /* tells writeloop thread to finish & exit */
+ waitpid(cloneid, NULL, __WALL); /* Hold on until it does so */
+ #if 0
+ d_printf("capture_go: Writeloop thread %d exited; returning\n",
+ cloneid);
+ #endif
}
/*
