scratch team mailing list archive
-
scratch team
-
Mailing list archive
-
Message #00046
Re: PulseAudio sound plugin for Squeak
> Why don't you email me and Amos a copy of your current (working)
> source code, as a backup? Of course, it should also be recorded in
> SVN, but it's sometimes nice to have a copy of a known good version to
> avoid having to dig through old versions on SVN.
Attached.
/*
*
*
*
*
*/
/* ========== */
/* 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_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);
}
/* ================================== 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 i;
/* Not preserving buffers when play/record stopped */
/* Choosing memory conservation over speed of starting play/record */
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;
}
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) {
*/
/* 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 may not be needed... */
/* 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]");
}
/* ================================== IO INIT */
static int ioInit() {
if (initDone) return true;
initDone = true;
/* AUDIO OUT */
/* NOT USED >>> */
audioOut.dbgName = "play";
audioOut.device = "pa-simple";
/* <<< */
audioOut.open = false;
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 */
audioOut.pa_conn = null;
ioThreadStart(&audioOut);
/* AUDIO IN */
audioIn.dbgName = "rec";
/* NOT USED >>> */
audioIn.device = "pa-simple";
/* <<< */
audioIn.open = false;
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 */
audioIn.pa_conn = null;
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;
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;
audioOut.pa_spec.format = PA_SAMPLE_S16LE;
audioOut.pa_spec.rate = samplesPerSec; /* rate(SAMPLE_RATE_22_05KHZ) for Squeak */
audioOut.pa_spec.channels = stereo ? 2 : 1;
audioOut.pa_conn = NULL;
/* Create a new playback stream */
if (!(audioOut.pa_conn = pa_simple_new(NULL, "Scratch", PA_STREAM_PLAYBACK, NULL, "playback", &audioOut.pa_spec, NULL, NULL, &rc))) {
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(rc));
success(false);
return false;
}
ioAllocBuffers(&audioOut, frameCount);
audioOut.bufferCount = audioOut.maxBuffers; /* Has to be reset everytime */
audioOut.sqSemaphore = semaIndex;
audioOut.open = true;
sigSignal(&audioOut.sigRun);
/* error possibly left over from dsp-protocol.c code */
/* dsp-protocol.c in current ALSA not capturing EINTR/EAGAIN */
/* EINTR/EAGAIN from dsp-protocol.c not raised up to ALSA so not caught by ALSA */
/* Clearing errno here to see if Squeak can continue regardless */
errno = 0;
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);
if (pa_simple_drain(audioOut.pa_conn, &rc) < 0) {
fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(rc));
}
if (NULL != audioOut.pa_conn)
pa_simple_free(audioOut.pa_conn);
ioFreeBuffers(&audioOut);
audioOut.pa_conn = NULL;
audioOut.sqSemaphore = 0;
DBGMSG("<sound_Stop()");
return true;
}
/* ================================== AUDIO IN */
static sqInt sound_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex) {
int rc;
pa_buffer_attr pa_buffer_metrics; /* For recording */
DBGMSG(">sound_StartRecording()");
if (audioIn.open) return true;
audioIn.pa_spec.format = PA_SAMPLE_S16LE;
audioIn.pa_spec.rate = desiredSamplesPerSec;
audioIn.pa_spec.channels = stereo ? 2 : 1;
audioIn.pa_conn = NULL;
pa_buffer_metrics.maxlength = (uint32_t) -1;
pa_buffer_metrics.tlength = (uint32_t) -1; /* playback only */
pa_buffer_metrics.prebuf = (uint32_t) -1; /* playback only */
pa_buffer_metrics.minreq = (uint32_t) -1; /* playback only */
pa_buffer_metrics.fragsize = pa_usec_to_bytes(20*1000, &audioIn.pa_spec);
/* Create the recording stream */
if (!(audioIn.pa_conn = pa_simple_new( NULL,
"Scratch",
PA_STREAM_RECORD,
NULL,
"record",
&audioIn.pa_spec,
NULL,
&pa_buffer_metrics,
&rc)))
{
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(rc));
success(false);
return false;
}
/* Only rate supported on the N810 (atm) is 8000 */
/*
audioIn.alsaRate = 8000;
*/
/* 20Hz update freq for Squeak sounds reasonable, so... */
audioIn.maxSamples = audioIn.pa_spec.rate / 20;
/* Use a buffer large enough to hold one period (assuming 2 bytes/sample) */
/*
audioIn.alsaBufferSize = audioIn.maxSamples * 2 * audioIn.pa_spec.channels;
audioIn.alsaBuffer = (char *) malloc(audioIn.alsaBufferSize);
*/
/* Buffers will be filled before signalling Squeak. So rate & buffer size determined signalling freq... */
ioAllocBuffers(&audioIn, audioIn.pa_spec.rate / 20 ); /* for Sq signalling rate of 20Hz */
audioIn.bufferCount = 0; /* Has to be reset everytime */
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);
pa_simple_free(audioIn.pa_conn);
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, pulse);
Follow ups
References