Author: piumarta Date: 2009-09-14 14:00:51 -0700 (Mon, 14 Sep 2009) New Revision: 2131 Added: trunk/platforms/unix/vm-sound-pulse/ trunk/platforms/unix/vm-sound-pulse/config.cmake trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c Modified: trunk/platforms/unix/ChangeLog Log: add pulse audio driver Modified: trunk/platforms/unix/ChangeLog =================================================================== --- trunk/platforms/unix/ChangeLog 2009-09-10 20:04:16 UTC (rev 2130) +++ trunk/platforms/unix/ChangeLog 2009-09-14 21:00:51 UTC (rev 2131) @@ -1,3 +1,8 @@ +2009-09-14 <[hidden email]> + + * vm-sound-pulse: Pulse Audio driver added, thanks to Derek + O'Connell. + 2009-09-10 <[hidden email]> * plugins/SqueakFFIPrims/x86-sysv-asm.S (ffiCallAddressOf): Pop a Added: trunk/platforms/unix/vm-sound-pulse/config.cmake =================================================================== --- trunk/platforms/unix/vm-sound-pulse/config.cmake (rev 0) +++ trunk/platforms/unix/vm-sound-pulse/config.cmake 2009-09-14 21:00:51 UTC (rev 2131) @@ -0,0 +1 @@ +PLUGIN_REQUIRE_PACKAGE (LIBPULSE libpulse) Added: trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c =================================================================== --- trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c (rev 0) +++ trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c 2009-09-14 21:00:51 UTC (rev 2131) @@ -0,0 +1,1255 @@ +/* sqUnixSoundPulseAudio.c -- sound module for Pulse Audio + * + * Author: Derek O'Connell <[hidden email]> + * + * Copyright (C) 2009 by Derek O'Connel + * All rights reserved. + * + * This file is part of Unix Squeak. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Last edited: 2009-09-14 13:59:33 by piumarta on ubuntu.piumarta.com + */ + +/* ========== */ +/* INCLUDES */ +/* ========== */ + +#include "sq.h" +#include <errno.h> +#include <signal.h> + +#include <stdio.h> +#include <stdlib.h> +#include <sys/errno.h> +#include <sys/mman.h> +#include <string.h> +#include <unistd.h> + +/* +#include <glib.h> +*/ +#include <pthread.h> + +#include <pulse/simple.h> +#include <pulse/error.h> +/* +#include <pulse/gccmacro.h> +*/ + +/* ========== */ +/* MACROS */ +/* ========== */ + +#define FAIL(X) \ +{ \ + success(false); \ + return X; \ +} + +#define snd(expr, what) \ + if ((rc = snd_##expr) < 0) \ + { \ + fprintf(stderr, "%s: %s\n", what, snd_strerror(rc)); \ + success(false); \ + return rc; \ + } + + +/* ================================== DEBUGGING */ + +#define xDBG + +#ifdef DBG + #define DBG_MSG_MAX_LEN 128 + + char *dbg_msg[DBG_MSG_MAX_LEN]; + + #define DBGMSG(M) { \ + printf("DBG: sqUnixSoundMaemo: %s (%d, %s)\n", M, errno, strerror (errno)); \ + errno = 0; \ + } + + #define DBGERR(M, E) { \ + sprintf(*dbg_msg, M, E); \ + DBGMSG(dbg_msg); \ + } + +#else + #define DBGMSG(M) + #define DBGERR(M, E) +#endif + + +/* ================================== TYPES */ + +typedef struct { + short *buffer; + unsigned long samples; + int isFree; +} audioBuffer_t; + +typedef struct { + pthread_mutex_t *mutex; + pthread_cond_t *cond; + unsigned int count; +} gen_sig_t; + +typedef struct { + /* Left in for debugging >>> */ + const char *dbgName; + const char *device; + /* <<< */ + + int open; + + unsigned long maxSamples; + unsigned long maxWords; + unsigned long maxBytes; + + audioBuffer_t *buffer; + + int maxBuffers; + int buffersAllocated; + int bufferFree; + int bufferNext; + int bufferCount; + int bufferFull; + + pthread_mutex_t *bufferMutex; + + void * threadFunc; + pthread_t thread; + + gen_sig_t sigRun; + gen_sig_t sigStalled; + + int running; + int exit; + int stall; + int sqSemaphore; + + int rateID; + int bytesPerFrame; + + /* PULSE, Simple API parameters */ + pa_stream_direction_t dir; + const char *stream_name; + pa_simple *pa_conn; + pa_sample_spec pa_spec; + } audioIO_t; + + +/* ================================== FUNCTION PROTOTYPES */ + +static int rate(int rateID); +static int rateID(int rate); + +static inline unsigned short _swapw(unsigned short v); /* From io.h */ + +static int devInputReady(int dev_fd); + +static void sigWait(gen_sig_t *sig); +static void sigReset(gen_sig_t *sig); +static void sigSignal(gen_sig_t *sig); + +static void ioThreadWaitToRun(audioIO_t *audioIO); +static void ioThreadExit(audioIO_t *audioIO); +static int ioThreadStart(audioIO_t *audioIO); +static int ioThreadIsRunning(audioIO_t *audioIO); +static void ioThreadStall(audioIO_t *audioIO); + +static void ioZeroBuffers(audioIO_t *audioIO); +static void ioFreeBuffers(audioIO_t *audioIO); +static int ioFreeBytes(audioIO_t *audioIO); +static int ioIsFull(audioIO_t *audioIO); +static int ioAddPlayBuffer(void *buffer, int frameCount); +static int ioGetRecordBuffer(void *buffer, int bufferBytes); +static int ioAllocBuffers(audioIO_t *audioIO, int frameCount); +static int ioGetBufferData(audioIO_t *audioIO, void **buffer, int *frames); +static int ioNextBuffer(audioIO_t *audioIO); + +static void *writerThread(void *ptr); +static void *readerThread(void *ptr); + +static int ioInit(); + +/* SQUEAK INTERFACE */ + +static int trace(); + +static sqInt sound_AvailableSpace(void); +static sqInt sound_InsertSamplesFromLeadTime(int frameCount, int srcBufPtr, int samplesOfLeadTime); +static sqInt sound_PlaySamplesFromAtLength(int frameCount, int arrayIndex, int startIndex); +static sqInt sound_PlaySilence(void); +static sqInt sound_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex); +static sqInt sound_Stop(void); + +static sqInt sound_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex); +static sqInt sound_StopRecording(void); +static double sound_GetRecordingSampleRate(void); +static sqInt sound_RecordSamplesIntoAtLength(int buf, int startSliceIndex, int bufferSizeInBytes); + +static int mixer_open(char *name); +static void mixer_close(void); +static inline void mixer_getVolume(char *name, int captureFlag, double *leftLevel, double *rightLevel); +static inline void mixer_setVolume(char *name, int captureFlag, double leftLevel, double rightLevel); +static int mixer_setSwitch(char *name, int captureFlag, int parameter); +static int mixer_getSwitch(char *name, int captureFlag, int channel); +static void sound_Volume(double *left, double *right); +static void sound_SetVolume(double left, double right); +static sqInt sound_SetRecordLevel(sqInt level); +static sqInt sound_SetDevice(sqInt id, char *arg); +static sqInt sound_GetSwitch(sqInt id, sqInt captureFlag, sqInt channel); +static sqInt sound_SetSwitch(sqInt id, sqInt captureFlag, sqInt parameter); + + +/* ==================== */ +/* ==================== GLOBALS */ +/* ==================== */ + +/* Left in but not used >>> */ +#define SQ_SND_PLAY_START_THRESHOLD 7/8 +#define SQ_SND_PLAY_AVAIL_MIN 4/8 +/* <<< */ + +/* Arbitrary (apart from minmising latency) >>> */ +#define MAX_INPUT_BUFFERS 10 +#define MAX_OUTPUT_BUFFERS 2 +/* <<< */ + +audioBuffer_t iBuffer[MAX_INPUT_BUFFERS]; +audioBuffer_t oBuffer[MAX_OUTPUT_BUFFERS]; + +/* STATICALLY INITIALISED SO AUTO-DESTROYED (ON CRASHING FOR INSTANCE) >>> */ + +/* input */ + +pthread_mutex_t audioInBufferMutex = PTHREAD_MUTEX_INITIALIZER; + +pthread_mutex_t audioInRunMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t audioInRunCond = PTHREAD_COND_INITIALIZER; + +pthread_mutex_t audioInStalledMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t audioInStalledCond = PTHREAD_COND_INITIALIZER; + +/* output */ + +pthread_mutex_t audioOutBufferMutex = PTHREAD_MUTEX_INITIALIZER; + +pthread_mutex_t audioOutRunMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t audioOutRunCond = PTHREAD_COND_INITIALIZER; + +pthread_mutex_t audioOutStalledMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t audioOutStalledCond = PTHREAD_COND_INITIALIZER; + +/* <<< */ + + +audioIO_t audioIn, audioOut; + +int initDone = false; + +/* EXTRA FOR ALSA BUT UNUSED >>> */ +/* +static int output_buffer_frames_available = 1; +static double max_delay_frames = 0; +*/ +/* <<< */ + + +/* ================================== UTILS */ + +/* RATE CONVERSION: from dsp code but not used (yet). Maybe not needed at all with AlSA */ +/* RATE CONVERSION: fixed preset rates are used. TBD: choose nearest to requested */ +/* +static int rate(int rateID) { + if (SAMPLE_RATE_8KHZ == rateID) return 8000; + if (SAMPLE_RATE_16KHZ == rateID) return 16000; + if (SAMPLE_RATE_11_025KHZ == rateID) return 11025; + if (SAMPLE_RATE_22_05KHZ == rateID) return 22050; + if (SAMPLE_RATE_44_1KHZ == rateID) return 44100; + return -1; +} + +static int rateID(int rate) { + if ( 8000 == rate) return SAMPLE_RATE_8KHZ; + if ( 8192 == rate) return SAMPLE_RATE_8KHZ; + if (16000 == rate) return SAMPLE_RATE_16KHZ; + if (11025 == rate) return SAMPLE_RATE_11_025KHZ; + if (22050 == rate) return SAMPLE_RATE_22_05KHZ; + if (44100 == rate) return SAMPLE_RATE_44_1KHZ; + return -1; +} +*/ + +/* From io.h because recorded data has to be Big Endian */ +static inline unsigned short _swapw(unsigned short v) { + return ((v << 8) | (v >> 8)); +} + + +/* Not used but maybe useful */ +/* +static int devInputReady(int dev_fd) { + struct pollfd pfd; + pfd.fd = dev_fd; + pfd.events = POLLIN; + if (poll (&pfd,1,0)>0) return true; + return false; +} +*/ + +static void printPALatency() { + pa_usec_t latency; + int error; + + if ((latency = pa_simple_get_latency(audioOut.pa_conn, &error)) == (pa_usec_t) -1) + fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s\n", pa_strerror(error)); + else + fprintf(stderr, "%0.0f usec \r", (float)latency); +} + +/* +static int bytesPerChannel(audioIO_t *audioIO) { + if ( PA_SAMPLE_S16LE == audioIO->rate) return SAMPLE_RATE_8KHZ; + PA_SAMPLE_S16LE +} +*/ + +/* ================================== Signal Ops */ + +static void sigWait(gen_sig_t *sig) { + pthread_mutex_lock(sig->mutex); + while( !sig->count ) + pthread_cond_wait(sig->cond, sig->mutex); + sig->count -= 1; + pthread_mutex_unlock(sig->mutex); +} + +static void sigReset(gen_sig_t *sig) { + pthread_mutex_lock(sig->mutex); + sig->count = 0; + pthread_mutex_unlock(sig->mutex); +} + +static void sigSignal(gen_sig_t *sig) { + pthread_mutex_lock(sig->mutex); + sig->count += 1; + pthread_cond_signal(sig->cond); + pthread_mutex_unlock(sig->mutex); +} + +/* Here for debugging but direct calls would be ok >>> */ +static void signalSqueak(audioIO_t *audioIO) { +/* printf("@%d",audioIO->sqSemaphore); +*/ + if (0 < audioIO->sqSemaphore) + signalSemaphoreWithIndex(audioIO->sqSemaphore); +} +/* <<< */ + + +/* ================================== Thread Ops */ + +static void ioThreadExit(audioIO_t *audioIO) { + if (!audioIO->thread) return; + audioIO->exit = 1; + sigSignal(&audioIO->sigRun); + pthread_join(audioIO->thread, NULL); + audioIO->thread = 0; +} + +static int ioThreadStart(audioIO_t *audioIO) { + int rc; + if (audioIO->thread) return true; + rc = pthread_create(&audioIO->thread, NULL, audioIO->threadFunc, NULL); + if (0 != rc) DBGERR("ioThreadStart(): %d", rc); + return rc; +} + +static int ioThreadIsRunning(audioIO_t *audioIO) { + return audioIO->running; +} + +static void ioThreadStall(audioIO_t *audioIO) { + audioIO->stall = true; + sigSignal(&audioIO->sigRun); + sigWait(&audioIO->sigStalled); +} + +/* Don't attempt to signal Sq here as we may not have a semaphore! */ +static void ioThreadWaitToRun(audioIO_t *audioIO) { + sigSignal(&audioIO->sigStalled); + + pthread_mutex_lock(audioIO->sigRun.mutex); + audioIO->running = false; + + if (audioIO->stall) audioIO->sigRun.count = 0; + audioIO->stall = false; + + while( !audioIO->sigRun.count ) + pthread_cond_wait(audioIO->sigRun.cond, audioIO->sigRun.mutex); + audioIO->sigRun.count -= 1; + + audioIO->running = true; + pthread_mutex_unlock(audioIO->sigRun.mutex); + + sigReset(&audioIO->sigStalled); +} + +/* ================================== Buffer ops */ + +static void ioZeroBuffers(audioIO_t *audioIO) { + int i; + for(i=0; i < audioIO->maxBuffers; i++) { + audioIO->buffer[i].samples = 0; + audioIO->buffer[i].isFree = true; + } +} + +static void ioFreeBuffers(audioIO_t *audioIO) { + int i; + for(i=0; i < audioIO->maxBuffers; i++) { + free(audioIO->buffer[i].buffer); + audioIO->buffer[i].buffer = 0; + audioIO->buffer[i].samples = 0; + } + audioIO->bufferFree = audioIO->bufferNext = 0; + /* audioIO->bufferCount differs for play/record */ +} + +/* Only used for playing, not for recording */ +static int ioFreeBytes(audioIO_t *audioIO) { + int freeBytes; + pthread_mutex_lock(audioIO->bufferMutex); + freeBytes = audioIO->maxBytes * audioIO->bufferCount; + pthread_mutex_unlock(audioIO->bufferMutex); + return freeBytes; +} + +static int ioAllocBuffers(audioIO_t *audioIO, int frameCount) { + int maxBytes; + int i; + + /* NTS: should take audioIO->bytesPerFrame into account... + and that depends on stereo or not. Revisit at later date. + maxBytes = frameCount * audioIO->bytesPerFrame; + */ + + if (audioIO->buffersAllocated) + if (audioOut.maxSamples == frameCount) + return true; + else + ioFreeBuffers(audioIO); + + audioIO->maxSamples = frameCount; + audioIO->maxBytes = audioIO->maxSamples * audioIO->bytesPerFrame; + audioIO->maxWords = audioIO->maxBytes >> 1; + for(i=0; i < audioIO->maxBuffers; i++) { + audioIO->buffer[i].buffer = (short *)calloc(audioIO->maxBytes, 1); + audioIO->buffer[i].isFree = true; + } + audioIO->buffersAllocated = true; + + return true; +} + +static int ioIsFull(audioIO_t *audioIO) { + pthread_mutex_lock(audioIO->bufferMutex); + audioIO->bufferFull = (0 < audioIO->buffer[audioIO->bufferFree].samples); + pthread_mutex_unlock(audioIO->bufferMutex); + return audioIO->bufferFull; +} + +/* Could combine some of the following but makes debugging difficult */ + +static int ioAddPlayBuffer(void *buffer, int frameCount) { + long bytes; + if (ioIsFull(&audioOut)) return 0; + pthread_mutex_lock(audioOut.bufferMutex); + bytes = MIN(audioOut.maxBytes, frameCount * audioOut.bytesPerFrame); + memcpy(audioOut.buffer[audioOut.bufferFree].buffer, buffer, bytes); + audioOut.buffer[audioOut.bufferFree].samples = frameCount; + audioOut.buffer[audioOut.bufferFree].isFree = false; + audioOut.bufferFree = (audioOut.bufferFree + 1) % audioOut.maxBuffers; + audioOut.bufferCount -= 1; + pthread_mutex_unlock(audioOut.bufferMutex); + return bytes; +} + +static int ioGetRecordBuffer(void *buffer, int bufferBytes) { + long samples, sampleBytes; + + if (bufferBytes <= 0) return 0; +/* if (audioIn.buffer[audioIO->bufferNext].samples <=0) return 0; +*/ + if (audioIn.buffer[audioIn.bufferNext].isFree) return 0; + + pthread_mutex_lock(audioIn.bufferMutex); + samples = audioIn.buffer[audioIn.bufferNext].samples; + sampleBytes = MIN(2 * audioIn.pa_spec.channels * samples, bufferBytes); + memcpy(buffer, (char *)audioIn.buffer[audioIn.bufferNext].buffer, sampleBytes); + /* DMOC 090909 1800: Hmmmm, what if Squeak does not read whole buffer? ATM remaining buffer data lost since */ + /* ioGetRecordBuffer() frees the buffer after single visit. Needs more work */ + audioIn.buffer[audioIn.bufferNext].samples = 0; + audioIn.buffer[audioIn.bufferNext].isFree = true; + audioIn.bufferNext = (audioIn.bufferNext + 1) % audioIn.maxBuffers; + audioIn.bufferCount -= 1; + pthread_mutex_unlock(audioIn.bufferMutex); + return sampleBytes; +} + +static int ioGetBufferData(audioIO_t *audioIO, void **buffer, int *frames) { + if (audioIO->buffer[audioIO->bufferNext].isFree) return false; + pthread_mutex_lock(audioIO->bufferMutex); + *buffer = (void *)(audioIO->buffer[audioIO->bufferNext].buffer); + *frames = audioIO->buffer[audioIO->bufferNext].samples; + pthread_mutex_unlock(audioIO->bufferMutex); + return true; +} + +static int ioNextPlayBuffer() { + pthread_mutex_lock(audioOut.bufferMutex); + audioOut.buffer[audioOut.bufferNext].samples = 0; + audioOut.buffer[audioOut.bufferNext].isFree = true; + audioOut.bufferNext = (audioOut.bufferNext + 1) % audioOut.maxBuffers; + audioOut.stall = (audioOut.bufferNext == audioOut.bufferFree); + audioOut.bufferCount += 1; + pthread_mutex_unlock(audioOut.bufferMutex); +} + +static int ioNextRecordBuffer() { + pthread_mutex_lock(audioIn.bufferMutex); + audioIn.buffer[audioIn.bufferNext].isFree = false; + audioIn.bufferFree = (audioIn.bufferNext + 1) % audioIn.maxBuffers; + audioIn.stall = (audioIn.bufferNext == audioIn.bufferFree); + audioIn.bufferCount += 1; + pthread_mutex_unlock(audioIn.bufferMutex); +} + +/* ================================== IO THREADS */ + +static void *writerThread(void *ptr) { + struct timespec tm = {0, 1000 * 1000}; + int rc; + int nextBuffer, frames; + void *buffer; + + DBGMSG("[writerThread: started]"); + + audioOut.exit = 0; + + for (;;) { + DBGMSG("[writerThread: waiting]"); + + /* No point signalling squeak *before* running as there may not be a semaphore */ + ioThreadWaitToRun(&audioOut); + + if (audioOut.exit) break; + if (!audioOut.open || audioOut.stall) continue; + + DBGMSG("[writerThread: running]"); + + for (;;) { + if (!audioOut.open || audioOut.stall || audioOut.exit) break; + + if (!ioGetBufferData(&audioOut, &buffer, &frames)) { + signalSqueak(&audioOut); + break; + } + +/*printf("writerThread: buffer: %d, frames %d\n", audioOut.bufferNext, frames); +*/ + + while (frames > 0) { + if (!audioOut.open || audioOut.stall || audioOut.exit) break; +/* if ((rc = snd_pcm_writei(audioOut.alsaHandle, buffer, frames)) < frames) { +*/ + + /* Experiment to see if draining removes delay, result: undecided, seems slightly better */ + pa_simple_drain(audioOut.pa_conn, &rc); + + + /* PA: Have to assume for now that all frames were written */ + if (pa_simple_write(audioOut.pa_conn, buffer, (size_t) (frames * audioOut.bytesPerFrame), &rc) < 0) { + fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(rc)); +/* printf("writerThread: sent %d, actual %d\n", frames, rc); +*/ + break; + } + + /* PA: Have to assume for now that all frames were written */ +/* buffer = (short *)((char *)buffer + rc * audioOut.bytesPerFrame); + frames -= rc; +*/ + /* *** SO FOLLOWING CODE *AND* THE ENCLOSING WHILE-LOOP REDUNDANT!!! (so just break out of loop) *** */ +/* buffer = (short *)((char *)buffer + frames * audioOut.bytesPerFrame); + frames -= frames; +*/ + break; + } /* while */ + + if (!audioOut.open || audioOut.stall || audioOut.exit) break; + ioNextPlayBuffer(); + if (!audioOut.open || audioOut.stall || audioOut.exit) break; + + signalSqueak(&audioOut); + } + + if (audioOut.exit) break; + } + + DBGMSG("[writerThread: stopped]"); + +} + + +static void *readerThread(void *ptr) { + int rc; + int wc; + unsigned short *p; + + DBGMSG("[readerThread: started]"); + + audioIn.exit = 0; + + for (;;) { + DBGMSG("[readerThread: waiting]"); + + ioThreadWaitToRun(&audioIn); + + if (audioIn.exit) break; + if (!audioIn.open || audioIn.stall) continue; + + DBGMSG("[readerThread: running]"); + + for (;;) { + if (!audioIn.open || audioIn.stall || audioIn.exit) break; + + /* NB: PA Simple API does not return number of bytes/samples recorded */ + /* (so have to assume full buffer everytime (poss padded if less than requested) */ + +/* rc = snd_pcm_readi(audioIn.alsaHandle, audioIn.alsaBuffer, audioIn.alsaFrames); +*/ + if (pa_simple_read(audioIn.pa_conn, (char *)(audioIn.buffer[audioIn.bufferFree].buffer), audioIn.maxBytes, &rc) < 0) { + fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(rc)); + continue; + } + + if (!audioIn.open || audioIn.stall || audioIn.exit) break; + + /* PA: Assume max buffer frames returned... */ + rc = audioIn.maxSamples; + + /* EPIPE means overrun */ +/* + if (rc == -EPIPE) { + printf("readerThread: overrun occurred\n"); + snd_pcm_prepare(audioIn.alsaHandle); + continue; + } + + if (rc < 0) { + printf("readerThread: error from read: %s\n", snd_strerror(rc)); + continue; + } + + if (rc != (int)audioIn.alsaFrames) { + printf("readerThread: short read, read %d frames\n", rc); + continue; + } +*/ + if (!audioIn.buffer[audioIn.bufferFree].isFree) { + printf("readerThread: No free buffers!\n"); + continue; + } + + if (!audioIn.open || audioIn.stall || audioIn.exit) break; + + /* PA: Complete buffer should be returned so skipping check and moving guts to end of loop */ +/* + if ((audioIn.buffer[audioIn.bufferFree].samples + rc) > audioIn.maxSamples) { + ioNextRecordBuffer(); + if (!audioIn.open || audioIn.stall || audioIn.exit) break; + signalSqueak(&audioIn); + } +*/ + +/* Next won't get called anyway because of "stall" in ioNextRecordBuffer() */ +/* Left here in case needed for debugging */ +/* + if (!audioIn.buffer[audioIn.bufferFree].isFree) { + printf("readerThread: No free buffers!\n"); + continue; + } +*/ + + /* PA: Endian swap is N810 left-over... */ + + /* Endian Swap (rc = frames = Word Count in this case) */ +/* + p = (unsigned short *)(audioIn.buffer[audioIn.bufferFree].buffer); + wc = rc; + while (wc--) + *p = _swapw(*p); +*/ + + /* PA: No copy required since record buffer used directly... */ +/* + memcpy((char *)(audioIn.buffer[audioIn.bufferFree].buffer) + 2 * audioIn.buffer[audioIn.bufferFree].samples, audioIn.alsaBuffer, rc*2); + audioIn.buffer[audioIn.bufferFree].samples += rc; +*/ + + /* PA: No indication of actual bytes/samples read so assuming full buffer read (or padded)... */ + audioIn.buffer[audioIn.bufferFree].samples = audioIn.maxSamples; + + if (!audioIn.open || audioIn.stall || audioIn.exit) break; + + /* PA: These three lines moved from above (since assuming full buffers are read every time) */ + ioNextRecordBuffer(); + if (!audioIn.open || audioIn.stall || audioIn.exit) break; + signalSqueak(&audioIn); + + } + if (audioIn.exit) break; + } +DBGMSG("[readerThread: stopped]"); +} + + +/* ================================== OPEN/CLOSE PA */ + +static int closePulseAudio(audioIO_t *audioIO) { + int rc; + + if (!audioIO->pa_conn) + return true; + + if (PA_STREAM_PLAYBACK == audioIO->dir) + if (pa_simple_drain(audioIO->pa_conn, &rc) < 0) + fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(rc)); + + pa_simple_free(audioIO->pa_conn); + audioIO->pa_conn = NULL; + + printf("closePulseAudio((): %s\n", audioIO->dbgName); + + return true; +} + +static int openPulseAudio(audioIO_t *audioIO, int samplesPerSec, int stereo) { + int rc; + int channels; + +/* +DBGMSG(">pa_Open()"); +#ifdef DBG +printf("\tframeCount: %d, samplesPerSec: %d, stereo: %d\n", frameCount, samplesPerSec, stereo, semaIndex); +#endif +*/ + + /* DMOC 090912: + Connection for playback stream should already have been opened when module loaded so + now only open if that failed or parameters have changed. Could also avoid buffer + creation if default frameCount known/agreed. + */ + + channels = stereo ? 2 : 1; + + /* If already connected then check if same spec */ + if (audioIO->pa_conn) + if ((audioIO->pa_spec.rate == samplesPerSec) && (audioIO->pa_spec.channels == channels)) + return true; + + if (audioIO->pa_conn) + closePulseAudio(audioIO); + + audioIO->pa_spec.rate = samplesPerSec; + audioIO->pa_spec.channels = channels; + + if (!(audioIO->pa_conn = pa_simple_new(NULL, "Scratch", audioIO->dir, NULL, audioIO->stream_name, &audioIO->pa_spec, NULL, NULL, &rc))) { + fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(rc)); + return false; + } + +/* + if (PA_STREAM_PLAYBACK == audioIO->dir) + pa_simple_drain(audioIO->pa_conn, &rc); +*/ + printf("openPulseAudio() %s, rate: %i, chans: %i\n", audioIO->dbgName, samplesPerSec, channels); + + return true; +} + +/* ================================== IO INIT */ + +/* ioInit() called when module loaded... + N810: Threads pre-started and held on semaphore + PA: Connection opened for audio-out with default settings +*/ + +static int ioInit() { + int rc; + + if (initDone) return true; + initDone = true; + + /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */ + /* >>>>>>>>>>>>> AUDIO OUT >>>>>>>>>>>> */ + /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */ + +/* NOT USED >>> */ + audioOut.dbgName = "play"; + audioOut.device = "pa-simple"; +/* <<< */ + + audioOut.open = false; /* Squeak state */ + + audioOut.maxSamples = 0; + audioOut.maxWords = 0; + audioOut.maxBytes = 0; + + audioOut.maxBuffers = MAX_OUTPUT_BUFFERS; + audioOut.buffer = oBuffer; + audioOut.bufferFree = 0; + audioOut.bufferNext = 0; + audioOut.bufferCount = audioOut.maxBuffers; + audioOut.bufferFull = 0; + audioOut.bufferMutex = &audioOutBufferMutex; + audioOut.buffersAllocated = false; + + audioOut.threadFunc = writerThread; + audioOut.thread = 0; + + audioOut.sigRun.mutex = &audioOutRunMutex; + audioOut.sigRun.cond = &audioOutRunCond; + sigReset(&audioOut.sigRun); + + audioOut.sigStalled.mutex = &audioOutStalledMutex; + audioOut.sigStalled.cond = &audioOutStalledCond; + sigReset(&audioOut.sigStalled); + + audioOut.running = 0; + audioOut.exit = 0; + audioOut.stall = 0; + + audioOut.sqSemaphore = 0; + + audioOut.rateID = 0; + audioOut.bytesPerFrame = 4; /* Stereo S16LE */ + + /* PA Specific (defaults for Scratch/Squeak) */ + audioOut.dir = PA_STREAM_PLAYBACK; + audioOut.stream_name = "playback"; + audioOut.pa_spec.format = PA_SAMPLE_S16LE; /* Squeak default */ + audioOut.pa_spec.rate = 0; + audioOut.pa_spec.channels = 0; + audioOut.pa_conn = NULL; + + /* Open PA connection here to avoid delays later on */ + openPulseAudio(&audioOut, 22050, true); + + /* Allocate buffers here to avoid delays later on */ + /* Hmmm, tricky because varies... so not doing it atm */ +/* ioAllocBuffers(&audioOut, frameCount); +*/ + + ioThreadStart(&audioOut); + + + /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */ + /* >>>>>>>>>>>>> AUDIO IN >>>>>>>>>>>>> */ + /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */ + +/* NOT USED >>> */ + audioIn.dbgName = "rec"; + audioIn.device = "pa-simple"; +/* <<< */ + + audioIn.open = false; /* Squeak state */ + + audioIn.maxSamples = 0; + audioIn.maxWords = 0; + audioIn.maxBytes = 0; + + audioIn.maxBuffers = MAX_INPUT_BUFFERS; + audioIn.buffer = iBuffer; + audioIn.bufferFree = 0; + audioIn.bufferNext = 0; + audioIn.bufferCount = 0; /* No buffers yet. Was audioIn.maxBuffers; */ + audioIn.bufferFull = 0; + audioIn.bufferMutex = &audioInBufferMutex; + audioIn.buffersAllocated = false; + + audioIn.threadFunc= readerThread; + audioIn.thread = 0; + + audioIn.sigRun.mutex = &audioInRunMutex; + audioIn.sigRun.cond = &audioInRunCond; + sigReset(&audioIn.sigRun); + + audioIn.sigStalled.mutex = &audioInStalledMutex; + audioIn.sigStalled.cond = &audioInStalledCond; + sigReset(&audioIn.sigStalled); + + audioIn.running = 0; + audioIn.exit = 0; + audioIn.stall = 0; + + audioIn.sqSemaphore = 0; + + audioIn.rateID = 0; + audioIn.bytesPerFrame = 2; /* Mono S16LE */ + + /* PA Specific */ + audioIn.dir = PA_STREAM_RECORD; + audioIn.stream_name = "record"; + audioIn.pa_spec.format = PA_SAMPLE_S16LE; /* Squeak default */ + audioIn.pa_spec.rate = 0; + audioIn.pa_spec.channels = 0; + audioIn.pa_conn = NULL; + + /* DMOC 090912: Not attempting to open default PA connection for recording */ + + ioThreadStart(&audioIn); +} + +/* ============================================ */ +/* ================================== VM PLUGIN */ +/* ============================================ */ + +static int trace() { +} + +/* ================================== AUDIO OUT */ + +static sqInt sound_AvailableSpace(void) { + return ioFreeBytes(&audioOut); +} + +static sqInt sound_InsertSamplesFromLeadTime(int frameCount, int srcBufPtr, int samplesOfLeadTime) { +DBGMSG(">sound_InsertSamplesFromLeadTime()"); + return 0; /* or maxBytes? */ +} + +static sqInt sound_PlaySamplesFromAtLength(int frameCount, int arrayIndex, int startIndex) { + unsigned int bufferNext, samples, sampleBytes; + + if (0 >= frameCount) return 0; + + samples = MIN(audioOut.maxSamples, frameCount); + + if (0 == (sampleBytes = ioAddPlayBuffer((void *)(arrayIndex + startIndex * 2 * audioOut.pa_spec.channels), samples))) + DBGMSG("sound_PlaySamplesFromAtLength(): No free buffers!"); + + sigSignal(&audioOut.sigRun); + + return samples; +} + +static sqInt sound_PlaySilence(void) { +DBGMSG(">sound_PlaySilence()"); + ioThreadStall(&audioOut); + return 0; /* or maxBytes? */ +} + +static sqInt sound_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex) { + int rc; + int channels; + int reopen; + +DBGMSG(">sound_Start()"); + +#ifdef DBG +printf("\tframeCount: %d, samplesPerSec: %d, stereo: %d, semaIndex: %d\n", frameCount, samplesPerSec, stereo, semaIndex); +#endif + + if (audioOut.open) return true; + + if (!openPulseAudio(&audioOut, samplesPerSec, stereo)) { + success(false); + return false; + } + + printf("sound_Start() frameCount >> 1: %i\n", frameCount >> 1); + + ioAllocBuffers(&audioOut, frameCount >> 1); + + /* EVERY TIME: Initialise buffer count, ie, Squeak-ready buffers (max for audio-out) */ + audioOut.bufferCount = audioOut.maxBuffers; + + audioOut.sqSemaphore = semaIndex; + audioOut.open = true; + + sigSignal(&audioOut.sigRun); + +DBGMSG("<sound_Start()"); + return true; +} + + +static sqInt sound_Stop(void) { + int rc; + +DBGMSG(">sound_Stop()"); + + if (!audioOut.open) return true; + audioOut.open = false; + + if (NULL == audioOut.pa_conn) return true; + + ioThreadStall(&audioOut); + + closePulseAudio(&audioOut); + + ioFreeBuffers(&audioOut); + + audioOut.sqSemaphore = 0; + +DBGMSG("<sound_Stop()"); + return true; +} + + + +/* ================================== AUDIO IN */ + +static sqInt sound_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex) { + int rc; + +DBGMSG(">sound_StartRecording()"); + + if (audioIn.open) return true; + + /* Only rate supported on the N810 (atm) is 8000 */ +/* + desiredSamplesPerSec = 8000; +*/ + + if (!openPulseAudio(&audioIn, desiredSamplesPerSec, stereo)) { + success(false); + return false; + } + + /* Buffers will be filled before signalling Squeak. So rate & buffer size determined signalling freq... */ + /* 20Hz update freq for Squeak sounds reasonable, so... */ + audioIn.maxSamples = audioIn.pa_spec.rate / 20; + + ioAllocBuffers(&audioIn, audioIn.maxSamples); + + /* EVERY TIME: Initialise buffer count, ie, Squeak-ready buffers (ZERO for audio-in) */ + audioIn.bufferCount = 0; + + audioIn.sqSemaphore = semaIndex; + + audioIn.open = true; + + sigSignal(&audioIn.sigRun); + +DBGMSG("<sound_StartRecording()"); + return true; +} + +static sqInt sound_StopRecording(void) { +DBGMSG(">sound_StopRecording()"); + + if (!audioIn.open) return; + audioIn.open = false; + + if (NULL == audioIn.pa_conn) return; + + ioThreadStall(&audioIn); + + closePulseAudio(&audioIn); + + ioFreeBuffers(&audioIn); + + audioIn.pa_conn = NULL; + + audioIn.sqSemaphore = 0; + +DBGMSG("<sound_StopRecording()"); + return true; +} + +static double sound_GetRecordingSampleRate(void) { + return (double)audioIn.pa_spec.rate; +} + +static sqInt sound_RecordSamplesIntoAtLength(int buf, int startSliceIndex, int bufferSizeInBytes) { + unsigned int bufferNext, bufferBytes, sampleBytes; + + bufferBytes = MAX(0, bufferSizeInBytes - (startSliceIndex * 2)); + if (0 == bufferBytes) { + printf("***(%d) sound_RecordSamplesIntoAtLength(): No space in Squeak buffer!\n", startSliceIndex); + return 0; + } + + /* DMOC 090909 1800: Hmmmm, what if Squeak does not read whole buffer? ATM remaining buffer data lost since */ + /* ioGetRecordBuffer() frees the buffer after single visit. Needs more work */ + + bufferNext = audioIn.bufferNext; /* preserved for debug output */ + sampleBytes = ioGetRecordBuffer((void *)(buf + (startSliceIndex * 2)), bufferBytes); +/* + if (0 < sampleBytes) + printf(" sound_RecordSamplesIntoAtLength(%d, %d, %d) %d, %d\n", buf, startSliceIndex, bufferSizeInBytes, bufferNext, sampleBytes); + else + printf("***sound_RecordSamplesIntoAtLength(%d, %d, %d) %d, %d\n", buf, startSliceIndex, bufferSizeInBytes, bufferNext, sampleBytes); +*/ + return MAX(0, sampleBytes)/(2 * audioIn.pa_spec.channels); +} + + +/* ================================== sound mixer */ + +/* +static int sound_nomixer = 0; +static snd_mixer_t *mixer_handle = 0; +static snd_mixer_elem_t *mixer_element = 0; +*/ + +static int mixer_open(char *name) { + trace(); + return -EACCES; +} + +static void mixer_close(void) { + trace(); +} + +static inline void mixer_getVolume(char *name, int captureFlag, double *leftLevel, double *rightLevel) { + trace(); +} + +static inline void mixer_setVolume(char *name, int captureFlag, double leftLevel, double rightLevel) { + trace(); +} + +static int mixer_setSwitch(char *name, int captureFlag, int parameter) { + trace(); + return 0; +} + +static int mixer_getSwitch(char *name, int captureFlag, int channel) { + trace(); + return -1; +} + +static void sound_Volume(double *left, double *right) { + trace(); + *left= 1.0; + *right= 1.0; +} + +static void sound_SetVolume(double left, double right) { + trace(); +} + +static sqInt sound_SetRecordLevel(sqInt level) { + trace(); + return 1; + return level; +} + +static sqInt sound_SetDevice(sqInt id, char *arg) { + trace(); + return -1; +} + +static sqInt sound_GetSwitch(sqInt id, sqInt captureFlag, sqInt channel) { + trace(); + return -1; +} + +static sqInt sound_SetSwitch(sqInt id, sqInt captureFlag, sqInt parameter) { + trace(); + return -1; +} + + +/* module */ + +#include "SqSound.h" + +SqSoundDefine(PA); + +#include "SqModule.h" + +static void sound_parseEnvironment(void) { +/* + char *ev= 0; + + sound_SetDevice(0, NULL); + sound_SetDevice(1, NULL); + sound_SetDevice(2, NULL); + + if ( getenv("SQUEAK_NOMIXER" )) sound_nomixer= 1; + if ((ev= getenv("SQUEAK_SOUNDCARD"))) sound_SetDevice(0, ev); + if ((ev= getenv("SQUEAK_PLAYBACK" ))) sound_SetDevice(1, ev); + if ((ev= getenv("SQUEAK_CAPTURE" ))) sound_SetDevice(2, ev); +*/ +} + +static int sound_parseArgument(int argc, char **argv) { +/* + if (!strcmp(argv[0], "-nomixer" )) { sound_nomixer= 1; return 1; } + else if (argv[1]) + { + if (!strcmp(argv[0], "-soundcard")) { sound_SetDevice(0, argv[1]); return 2; } + if (!strcmp(argv[0], "-playback" )) { sound_SetDevice(1, argv[1]); return 2; } + if (!strcmp(argv[0], "-capture" )) { sound_SetDevice(2, argv[1]); return 2; } + } +*/ + return 0; +} + +static void sound_printUsage(void) { + printf("\nPulseAudio <option>s: <none>\n"); +/* + printf(" -nomixer disable mixer (volume) adjustment\n"); + printf(" -soundcard <name> open the named sound card (default: %s)\n", sound_device); + printf(" -playback <name> play to the named sound device (default: %s)\n", sound_playback); + printf(" -capture <name> record from the named sound device (default: %s)\n", sound_capture); +*/ +} + +static void sound_printUsageNotes(void) { +} + +static void *sound_makeInterface(void) { +//#ifdef NEWSIG +// sigalrm_save(); // DMOC: Being here assumes old handler same for run duration! Same for sigio handler. +//#else +// DMOC: Rethink: Signal captured once, preserved and restored when/where necessary? +// sigio_save(); +//#endif + +#ifdef USE_RESOURCE_MANAGER +printf("USE_RESOURCE_MANAGER\n"); +#endif + + ioInit(); + + return &sound_PA_itf; +} + +SqModuleDefine(sound, PA); + |
Free forum by Nabble | Edit this page |