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 } /*