#if defined (HAVE_CONFIG_H) # include "config.h" #endif #include /* check for standard C headers */ #if STDC_HEADERS # include # include #else # ifndef HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr (), *strrchr (); #endif #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include #ifdef DMALLOC #include #endif #include #include #include "ringbuffer.h" MODULE(audio) #define MODE_READ 0x01 #define MODE_WRITE 0x02 #define MODE_RDWR 0x03 static int init_ok = 0; /* handle SIGINT and SIGTERM */ #ifdef _MSC_VER #define SIGHANDLER_RETURN(status) return #else #if RETSIGTYPE == void #define SIGHANDLER_RETURN(status) return #else #define SIGHANDLER_RETURN(status) return status #endif #endif #ifdef MUST_REINSTALL_SIGHANDLERS #define SIGHANDLER_RESTORE(sig,handler) syssignal(sig,handler) #else #define SIGHANDLER_RESTORE(sig,handler) /* nop */ #endif typedef RETSIGTYPE (*sighandler_t)(int); static sighandler_t syssignal(sig, handler) int sig; sighandler_t handler; { #ifdef HAVE_POSIX_SIGNALS struct sigaction new_action, old_action; new_action.sa_handler = handler; sigemptyset(&new_action.sa_mask); sigemptyset(&old_action.sa_mask); new_action.sa_flags = 0; sigaction(sig, &new_action, &old_action); return old_action.sa_handler; #else return signal(sig, handler); #endif } static volatile int brkflag = 0; static sighandler_t int_handler = NULL, term_handler = NULL, hup_handler = NULL; static RETSIGTYPE break_handler(int sig) { if (sig == SIGINT && int_handler) int_handler(sig); if (sig == SIGTERM && term_handler) term_handler(sig); #ifdef SIGHUP if (sig == SIGHUP && hup_handler) hup_handler(sig); #endif SIGHANDLER_RESTORE(sig, break_handler); brkflag = 1; SIGHANDLER_RETURN(0); } FUNCTION(audio,audio_vars,argc,argv) { if (argc != 0) return __FAIL; return mktuplel (13, mkint(paFloat32), mkint(paInt16), mkint(paInt32), mkint(paInt24), mkint(paPackedInt24), mkint(paInt8), mkint(paUInt8), mkint(paCustomFormat), mkint(MODE_READ), mkint(MODE_WRITE), mkint(MODE_RDWR), mkint(init_ok?Pa_GetDefaultInputDeviceID():paNoDevice), mkint(init_ok?Pa_GetDefaultOutputDeviceID():paNoDevice)); } FUNCTION(audio,audio_devices,argc,argv) { int count, dev, i, n; expr *xlist; if (!init_ok || argc != 0) return __FAIL; count = Pa_CountDevices(); if (!(xlist = (count>0)?xvalloc(count):NULL)) return __ERROR; for (dev = 0; dev < count; dev++) { expr *xv; const PaDeviceInfo *info = Pa_GetDeviceInfo(dev); int i, n = info->numSampleRates; if (n < 0) n = 2; if (!(xv = xvalloc(n))) { while (dev > 0) dispose(xlist[--dev]); xvfree(xlist); return __ERROR; } for (i = 0; i < n; i++) xv[i] = mkfloat(info->sampleRates[i]); xlist[dev] = mktuplel(5, mkstr(strdup(info->name)), mkint(info->maxInputChannels), mkint(info->maxOutputChannels), ((info->numSampleRates>=0)?mklistv:mktuplev)(n, xv), mkint(info->nativeSampleFormats)); } return mklistv(count, xlist); } typedef struct A_STREAM { PortAudioStream *as; pthread_mutex_t data_mutex, in_mutex, out_mutex; pthread_cond_t in_cond, out_cond; RingBuffer in_buf, out_buf; char *in_data, *out_data; int mode; PaDeviceID id; double time, sample_rate; int size, nbufs, channels, bps, bpf; PaSampleFormat format; struct A_STREAM *prev, *next; } a_stream; static a_stream *current = NULL; static int init_buf(RingBuffer *buf, char **data, long bufsize) { if (!(*data = malloc(bufsize))) return 0; memset(*data, 0, bufsize); if (RingBuffer_Init(buf, bufsize, *data)) { free(*data); return 0; } return 1; } static void fini_buf(RingBuffer *buf, char **data) { free(*data); *data = NULL; } static unsigned long round_pow2(unsigned long n) { /* round to the next power of 2 */ long numBits = 0; if( ((n-1) & n) == 0) return n; /* Already a power of two. */ while( n > 0 ) { n= n>>1; numBits++; } return (1<in_buf, &v->in_data, bufsize)) return 0; if (mode & MODE_WRITE) { if (!init_buf(&v->out_buf, &v->out_data, bufsize)) { if (mode & MODE_READ) fini_buf(&v->in_buf, &v->in_data); return 0; } #if 0 { /* fake a full write buffer */ long bytes = RingBuffer_GetWriteAvailable(&v->out_buf); RingBuffer_AdvanceWriteIndex(&v->out_buf, bytes); } #endif } v->mode = mode; v->time = 0.0; pthread_mutex_init(&v->data_mutex, NULL); if (mode & MODE_READ) { pthread_mutex_init(&v->in_mutex, NULL); pthread_cond_init(&v->in_cond, NULL); } if (v->mode & MODE_WRITE) { pthread_mutex_init(&v->out_mutex, NULL); pthread_cond_init(&v->out_cond, NULL); } if (current) current->prev = v; v->prev = NULL; v->next = current; current = v; return 1; } static void lock_a_stream(void *x) { a_stream *v = (a_stream*)x; pthread_mutex_lock(&v->data_mutex); if (v->mode & MODE_READ) pthread_mutex_lock(&v->in_mutex); if (v->mode & MODE_WRITE) pthread_mutex_lock(&v->out_mutex); } static void unlock_a_stream(void *x) { a_stream *v = (a_stream*)x; pthread_mutex_unlock(&v->data_mutex); if (v->mode & MODE_READ) pthread_mutex_unlock(&v->in_mutex); if (v->mode & MODE_WRITE) pthread_mutex_unlock(&v->out_mutex); } static void fini_a_stream(a_stream *v, int abort) { if (v->as) { if (abort) Pa_AbortStream(v->as); else Pa_StopStream(v->as); pthread_cleanup_push(unlock_a_stream, (void*)v); lock_a_stream(v); Pa_CloseStream(v->as); v->as = NULL; /* wake up threads waiting to read or write the stream */ if (v->mode & MODE_READ) pthread_cond_broadcast(&v->in_cond); if (v->mode & MODE_WRITE) pthread_cond_broadcast(&v->out_cond); pthread_cleanup_pop(1); } } static void destroy_a_stream(a_stream *v) { pthread_mutex_destroy(&v->data_mutex); if (v->mode & MODE_READ) { pthread_mutex_destroy(&v->in_mutex); pthread_cond_destroy(&v->in_cond); } if (v->mode & MODE_WRITE) { pthread_mutex_destroy(&v->out_mutex); pthread_cond_destroy(&v->out_cond); } if (v->mode & MODE_READ) fini_buf(&v->in_buf, &v->in_data); if (v->mode & MODE_WRITE) fini_buf(&v->out_buf, &v->out_data); if (v->prev) v->prev->next = v->next; if (v->next) v->next->prev = v->prev; if (v == current) current = v->next; } DESTRUCTOR(audio,AudioStream,v) { fini_a_stream((a_stream*)v, 0); destroy_a_stream((a_stream*)v); free(v); } FUNCTION(audio,min_buffers,argc,argv) { long size; double rate; if (argc == 2 && isint(argv[0], &size) && size > 0 && (isfloat(argv[1], &rate) || ismpz_float(argv[1], &rate)) && rate > 0.0) return mkint(Pa_GetMinNumBuffers(size, rate)); else return __FAIL; } /* Audio processing callback. Note that in difference to the pablio implementation we employ some mutex locks and condition variables here. This isn't nice but is needed to protect shared data and to wake up a thread waiting for data to be read or written. */ static int audio_cb(void *input, void *output, unsigned long nframes, PaTimestamp out_time, void *data) { a_stream *v = (a_stream*)data; long bytes = v->bpf*nframes; /* update the current time */ pthread_mutex_lock(&v->data_mutex); if (!v->as) { pthread_mutex_unlock(&v->data_mutex); return 0; } v->time = out_time; pthread_mutex_unlock(&v->data_mutex); /* process data */ if (input) { pthread_mutex_lock(&v->in_mutex); if (RingBuffer_GetWriteAvailable(&v->in_buf) == 0) RingBuffer_AdvanceReadIndex(&v->in_buf, bytes); RingBuffer_Write(&v->in_buf, input, bytes); pthread_cond_signal(&v->in_cond); pthread_mutex_unlock(&v->in_mutex); } if (output) { int i, read; pthread_mutex_lock(&v->out_mutex); read = RingBuffer_Read(&v->out_buf, output, bytes); //printf("%g output: wanted %d, got %d\n", out_time, bytes, read); //for (i = read; iout_cond); pthread_mutex_unlock(&v->out_mutex); } return 0; } FUNCTION(audio,open_audio_stream,argc,argv) { long id, mode, channels, format, size, nbufs, bps; double rate; int n; expr *xv; if (init_ok && argc == 3 && isint(argv[0], &id) && id >= 0 && id <= Pa_CountDevices() && isint(argv[1], &mode) && (mode & ~MODE_RDWR) == 0 && (mode & MODE_RDWR) != 0 && istuple(argv[2], &n, &xv) && (n == 4 || n == 5) && (isfloat(xv[0], &rate) || ismpz_float(xv[0], &rate)) && rate > 0.0 && isint(xv[1], &channels) && channels > 0 && isint(xv[2], &format) && (bps = Pa_GetSampleSize(format)) > 0 && isint(xv[3], &size) && size > 0 && (n == 4 || isint(xv[4], &nbufs) && nbufs >= 0)) { PaDeviceID in = (mode&MODE_READ)?(PaDeviceID)id:paNoDevice, out = (mode&MODE_WRITE)?(PaDeviceID)id:paNoDevice; int inchannels = (mode&MODE_READ)?channels:0, outchannels = (mode&MODE_WRITE)?channels:0; a_stream *v = malloc(sizeof(a_stream)); if (n == 4 || nbufs == 0) nbufs = Pa_GetMinNumBuffers(size, rate); if (nbufs == 0) nbufs = 1; if (!v) return __ERROR; else if (!init_a_stream(v, mode, bps*channels*nbufs*size)) { free(v); return __ERROR; } if (Pa_OpenStream(&v->as, in, inchannels, (PaSampleFormat)format, NULL, out, outchannels, (PaSampleFormat)format, NULL, rate, size, nbufs, paNoFlag, audio_cb, v) == paNoError) { #ifdef HAVE_POSIX_SIGNALS sigset_t sigset, oldset; #endif v->id = (PaDeviceID)id; v->sample_rate = rate; v->channels = channels; v->format = (PaSampleFormat)format; v->size = size; v->nbufs = nbufs; v->bps = bps; v->bpf = bps*channels; #ifdef HAVE_POSIX_SIGNALS /* temporarily block some signals to make sure that they are blocked in the PortAudio threads created by Pa_StartStream */ sigemptyset(&sigset); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGQUIT); sigaddset(&sigset, SIGTSTP); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGHUP); sigprocmask(SIG_BLOCK, &sigset, &oldset); #endif Pa_StartStream(v->as); #ifdef HAVE_POSIX_SIGNALS sigprocmask(SIG_SETMASK, &oldset, NULL); #endif return mkobj(type(AudioStream), v); } else { destroy_a_stream(v); free(v); return __FAIL; } } else return __FAIL; } FUNCTION(audio,close_audio_stream,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as) { fini_a_stream(v, 0); return mkvoid; } else return __FAIL; } FUNCTION(audio,audio_stream_id,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as) return mkint((long)v->id); else return __FAIL; } FUNCTION(audio,audio_stream_info,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as) return mktuplel(5, mkfloat(v->sample_rate), mkint((long)v->channels), mkint((long)v->format), mkint((long)v->size), mkint((long)v->nbufs)); else return __FAIL; } FUNCTION(audio,audio_stream_time,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as) { double time; pthread_mutex_lock(&v->data_mutex); time = v->time; pthread_mutex_unlock(&v->data_mutex); return mkfloat(time); } else return __FAIL; } FUNCTION(audio,bytes_per_sample,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as) return mkint(v->bps); else return __FAIL; } FUNCTION(audio,bytes_per_frame,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as) return mkint(v->bpf); else return __FAIL; } /* ByteStr data structure, see clib.c */ typedef struct bstr { long size; unsigned char *v; } bstr_t; FUNCTION(audio,read_audio_stream,argc,argv) { a_stream *v; long size; if (argc == 2 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as && (v->mode&MODE_READ) && isint(argv[1], &size) && size >= 0) { bstr_t *m = malloc(sizeof(bstr_t)); if (!m) return __FAIL; if (size > 0) { long bytes = size*v->bpf, read; unsigned char *p; if (!(m->v = malloc(bytes))) { free(m); return __ERROR; } m->size = bytes; p = m->v; release_lock(); pthread_cleanup_push((void(*)(void*))pthread_mutex_unlock, (void*)&v->in_mutex); pthread_mutex_lock(&v->in_mutex); brkflag = 0; while (!brkflag && v->as && bytes > 0) { while (!brkflag && v->as && (read = RingBuffer_Read(&v->in_buf, p, bytes)) == 0) pthread_cond_wait(&v->in_cond, &v->in_mutex); bytes -= read; p += read; } pthread_cleanup_pop(1); acquire_lock(); if (brkflag) { brkflag = 0; free(m->v); free(m); return __FAIL; } } else { m->size = 0; m->v = NULL; } return mkobj(type(ByteStr), m); } else return __FAIL; } FUNCTION(audio,write_audio_stream,argc,argv) { a_stream *v; bstr_t *m; if (argc == 2 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as && (v->mode&MODE_WRITE) && isobj(argv[1], type(ByteStr), (void**)&m)) { if (m->size > 0) { long size = m->size/v->bpf, bytes = size*v->bpf, written; unsigned char *p; p = m->v; release_lock(); pthread_cleanup_push((void(*)(void*))pthread_mutex_unlock, (void*)&v->out_mutex); pthread_mutex_lock(&v->out_mutex); brkflag = 0; while (!brkflag && v->as && bytes > 0) { while (!brkflag && v->as && (written = RingBuffer_Write(&v->out_buf, p, bytes)) == 0) pthread_cond_wait(&v->out_cond, &v->out_mutex); // printf("write: have %d, %d written\n", bytes, written); bytes -= written; p += written; } pthread_cleanup_pop(1); acquire_lock(); if (brkflag) { brkflag = 0; return __FAIL; } } return mkvoid; } else return __FAIL; } FUNCTION(audio,audio_stream_readable,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as && (v->mode & MODE_READ)) { int bytes = RingBuffer_GetReadAvailable(&v->in_buf); return mkint((long)(bytes/v->bpf)); } else return __FAIL; } FUNCTION(audio,audio_stream_writeable,argc,argv) { a_stream *v; if (argc == 1 && isobj(argv[0], type(AudioStream), (void**)&v) && v->as && (v->mode & MODE_WRITE)) { int bytes = RingBuffer_GetWriteAvailable(&v->out_buf); return mkint((long)(bytes/v->bpf)); } else return __FAIL; } INIT(audio) { init_ok = (Pa_Initialize() == paNoError); /* register signal handlers */ int_handler = syssignal(SIGINT, break_handler); term_handler = syssignal(SIGTERM, break_handler); #ifdef SIGHUP hup_handler = syssignal(SIGHUP, break_handler); #endif } FINI(audio) { if (init_ok) { a_stream *v; for (v = current; v; v = v->next) fini_a_stream(v, 1); Pa_Terminate(); } }