/* * Copyright (c) 2009 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "pa_util.h" #include "pa_hostapi.h" #include "pa_stream.h" #include "pa_process.h" #include "pa_allocation.h" #if 0 #define DPR(...) do { fprintf(stderr, __VA_ARGS__); } while (0) #else #define DPR(...) do {} while (0) #endif /* * per-stream data */ typedef struct PaSndioStream { PaUtilStreamRepresentation base; PaUtilBufferProcessor bufproc; /* format conversion */ struct sio_hdl *hdl; /* handle for device i/o */ struct sio_par par; /* current device parameters */ unsigned mode; /* SIO_PLAY, SIO_REC or both */ int stopped; /* stop requested or not started */ int active; /* thread is running */ unsigned long long realpos; /* frame number h/w is processing */ char *rbuf, *wbuf; /* bounce buffers for conversions */ unsigned long long rpos, wpos; /* bytes read/written */ pthread_t thread; /* thread of the callback interface */ } PaSndioStream; /* * api "class" data, common to all streams */ typedef struct PaSndioHostApiRepresentation { PaUtilHostApiRepresentation base; PaUtilStreamInterface callback; PaUtilStreamInterface blocking; /* * sndio has no device discovery mechanism and PortAudio has * no way of accepting raw device strings from users. * Normally we just expose the default device, which can be * changed via the AUDIODEVICE environment variable, but we * also allow specifying a list of up to 16 devices via the * PA_SNDIO_AUDIODEVICES environment variable. * * Example: * PA_SNDIO_AUDIODEVICES=default:snd/0.monitor:snd@remote/0 */ #define PA_SNDIO_AUDIODEVICES_MAX 16 PaDeviceInfo device_info[PA_SNDIO_AUDIODEVICES_MAX]; PaDeviceInfo *infos[PA_SNDIO_AUDIODEVICES_MAX]; char *audiodevices; } PaSndioHostApiRepresentation; /* * callback invoked when blocks are processed by the hardware */ static void sndioOnMove(void *addr, int delta) { PaSndioStream *s = (PaSndioStream *)addr; s->realpos += delta; } /* * convert PA encoding to sndio encoding, return true on success */ static int sndioSetFmt(struct sio_par *sio, PaSampleFormat fmt) { switch (fmt & ~paNonInterleaved) { case paInt32: sio->sig = 1; sio->bits = 32; break; case paInt24: sio->sig = 1; sio->bits = 24; sio->bps = 3; /* paInt24 is packed format */ break; case paInt16: case paFloat32: sio->sig = 1; sio->bits = 16; break; case paInt8: sio->sig = 1; sio->bits = 8; break; case paUInt8: sio->sig = 0; sio->bits = 8; break; default: DPR("sndioSetFmt: %x: unsupported\n", fmt); return 0; } sio->le = SIO_LE_NATIVE; return 1; } /* * convert sndio encoding to PA encoding, return true on success */ static int sndioGetFmt(struct sio_par *sio, PaSampleFormat *fmt) { if ((sio->bps * 8 != sio->bits && !sio->msb) || (sio->bps > 1 && sio->le != SIO_LE_NATIVE)) { DPR("sndioGetFmt: bits = %u, le = %u, msb = %u, bps = %u\n", sio->bits, sio->le, sio->msb, sio->bps); return 0; } switch (sio->bits) { case 32: if (!sio->sig) return 0; *fmt = paInt32; break; case 24: if (!sio->sig) return 0; *fmt = (sio->bps == 3) ? paInt24 : paInt32; break; case 16: if (!sio->sig) return 0; *fmt = paInt16; break; case 8: *fmt = sio->sig ? paInt8 : paUInt8; break; default: DPR("sndioGetFmt: %u: unsupported\n", sio->bits); return 0; } return 1; } /* * I/O loop for callback interface */ static void * sndioThread(void *arg) { PaSndioStream *s = (PaSndioStream *)arg; PaStreamCallbackTimeInfo ti; unsigned char *data; unsigned todo, rblksz, wblksz; int n, result; rblksz = s->par.round * s->par.rchan * s->par.bps; wblksz = s->par.round * s->par.pchan * s->par.bps; DPR("sndioThread: mode = %x, round = %u, rblksz = %u, wblksz = %u\n", s->mode, s->par.round, rblksz, wblksz); while (!s->stopped) { if (s->mode & SIO_REC) { todo = rblksz; data = s->rbuf; while (todo > 0) { n = sio_read(s->hdl, data, todo); if (n == 0) { DPR("sndioThread: sio_read failed\n"); goto failed; } todo -= n; data += n; } s->rpos += s->par.round; ti.inputBufferAdcTime = (double)s->realpos / s->par.rate; } if (s->mode & SIO_PLAY) { ti.outputBufferDacTime = (double)(s->realpos + s->par.bufsz) / s->par.rate; } ti.currentTime = s->realpos / (double)s->par.rate; PaUtil_BeginBufferProcessing(&s->bufproc, &ti, 0); if (s->mode & SIO_PLAY) { PaUtil_SetOutputFrameCount(&s->bufproc, s->par.round); PaUtil_SetInterleavedOutputChannels(&s->bufproc, 0, s->wbuf, s->par.pchan); } if (s->mode & SIO_REC) { PaUtil_SetInputFrameCount(&s->bufproc, s->par.round); PaUtil_SetInterleavedInputChannels(&s->bufproc, 0, s->rbuf, s->par.rchan); } result = paContinue; n = PaUtil_EndBufferProcessing(&s->bufproc, &result); if (n != s->par.round) { DPR("sndioThread: %d < %u frames, result = %d\n", n, s->par.round, result); } if (result != paContinue) { break; } if (s->mode & SIO_PLAY) { n = sio_write(s->hdl, s->wbuf, wblksz); if (n < wblksz) { DPR("sndioThread: sio_write failed\n"); goto failed; } s->wpos += s->par.round; } } failed: s->active = 0; DPR("sndioThread: done\n"); } static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, PaStream **stream, const PaStreamParameters *inputPar, const PaStreamParameters *outputPar, double sampleRate, unsigned long framesPerBuffer, PaStreamFlags streamFlags, PaStreamCallback *streamCallback, void *userData) { PaSndioHostApiRepresentation *sndioHostApi = (PaSndioHostApiRepresentation *)hostApi; PaSndioStream *s; PaError err; struct sio_hdl *hdl; struct sio_par par; unsigned mode; int inch, onch; PaSampleFormat ifmt, ofmt, siofmt; const char *dev; DPR("OpenStream:\n"); mode = 0; inch = onch = 0; ifmt = ofmt = 0; sio_initpar(&par); if (outputPar && outputPar->channelCount > 0) { if (outputPar->device >= sndioHostApi->base.info.deviceCount) { DPR("OpenStream: %d: bad output device\n", outputPar->device); return paInvalidDevice; } if (outputPar->hostApiSpecificStreamInfo) { DPR("OpenStream: output specific info\n"); return paIncompatibleHostApiSpecificStreamInfo; } if (!sndioSetFmt(&par, outputPar->sampleFormat)) { return paSampleFormatNotSupported; } ofmt = outputPar->sampleFormat; onch = par.pchan = outputPar->channelCount; mode |= SIO_PLAY; } if (inputPar && inputPar->channelCount > 0) { if (inputPar->device >= sndioHostApi->base.info.deviceCount) { DPR("OpenStream: %d: bad input device\n", inputPar->device); return paInvalidDevice; } if (inputPar->hostApiSpecificStreamInfo) { DPR("OpenStream: input specific info\n"); return paIncompatibleHostApiSpecificStreamInfo; } if (!sndioSetFmt(&par, inputPar->sampleFormat)) { return paSampleFormatNotSupported; } ifmt = inputPar->sampleFormat; inch = par.rchan = inputPar->channelCount; mode |= SIO_REC; } par.rate = sampleRate; if (framesPerBuffer != paFramesPerBufferUnspecified) par.round = framesPerBuffer; DPR("OpenStream: mode = %x, trying rate = %u\n", mode, par.rate); if (outputPar) { dev = sndioHostApi->device_info[outputPar->device].name; } else if (inputPar) { dev = sndioHostApi->device_info[inputPar->device].name; } else { return paUnanticipatedHostError; } hdl = sio_open(dev, mode, 0); if (hdl == NULL) return paUnanticipatedHostError; if (!sio_setpar(hdl, &par)) { sio_close(hdl); return paUnanticipatedHostError; } if (!sio_getpar(hdl, &par)) { sio_close(hdl); return paUnanticipatedHostError; } if (!sndioGetFmt(&par, &siofmt)) { sio_close(hdl); return paSampleFormatNotSupported; } if ((mode & SIO_REC) && par.rchan != inputPar->channelCount) { DPR("OpenStream: rchan(%u) != %d\n", par.rchan, inputPar->channelCount); sio_close(hdl); return paInvalidChannelCount; } if ((mode & SIO_PLAY) && par.pchan != outputPar->channelCount) { DPR("OpenStream: pchan(%u) != %d\n", par.pchan, outputPar->channelCount); sio_close(hdl); return paInvalidChannelCount; } if ((double)par.rate < sampleRate * 0.995 || (double)par.rate > sampleRate * 1.005) { DPR("OpenStream: rate(%u) != %g\n", par.rate, sampleRate); sio_close(hdl); return paInvalidSampleRate; } s = (PaSndioStream *)PaUtil_AllocateMemory(sizeof(PaSndioStream)); if (s == NULL) { sio_close(hdl); return paInsufficientMemory; } PaUtil_InitializeStreamRepresentation(&s->base, streamCallback ? &sndioHostApi->callback : &sndioHostApi->blocking, streamCallback, userData); DPR("inch = %d, onch = %d, ifmt = %x, ofmt = %x\n", inch, onch, ifmt, ofmt); err = PaUtil_InitializeBufferProcessor(&s->bufproc, inch, ifmt, siofmt, onch, ofmt, siofmt, sampleRate, streamFlags, framesPerBuffer, par.round, paUtilFixedHostBufferSize, streamCallback, userData); if (err) { DPR("OpenStream: PaUtil_InitializeBufferProcessor failed\n"); PaUtil_FreeMemory(s); sio_close(hdl); return err; } if (mode & SIO_REC) { s->rbuf = malloc(par.round * par.rchan * par.bps); if (s->rbuf == NULL) { DPR("OpenStream: failed to allocate rbuf\n"); PaUtil_FreeMemory(s); sio_close(hdl); return paInsufficientMemory; } } if (mode & SIO_PLAY) { s->wbuf = malloc(par.round * par.pchan * par.bps); if (s->wbuf == NULL) { DPR("OpenStream: failed to allocate wbuf\n"); free(s->rbuf); PaUtil_FreeMemory(s); sio_close(hdl); return paInsufficientMemory; } } s->base.streamInfo.inputLatency = 0; s->base.streamInfo.outputLatency = (mode & SIO_PLAY) ? (double)(par.bufsz + PaUtil_GetBufferProcessorOutputLatencyFrames(&s->bufproc)) / (double)par.rate : 0; s->base.streamInfo.sampleRate = par.rate; s->active = 0; s->stopped = 1; s->mode = mode; s->hdl = hdl; s->par = par; *stream = s; DPR("OpenStream: done\n"); return paNoError; } static PaError BlockingReadStream(PaStream *stream, void *data, unsigned long numFrames) { PaSndioStream *s = (PaSndioStream *)stream; unsigned n, res, todo; void *buf; while (numFrames > 0) { n = s->par.round; if (n > numFrames) n = numFrames; buf = s->rbuf; todo = n * s->par.rchan * s->par.bps; while (todo > 0) { res = sio_read(s->hdl, buf, todo); if (res == 0) return paUnanticipatedHostError; buf = (char *)buf + res; todo -= res; } s->rpos += n; PaUtil_SetInputFrameCount(&s->bufproc, n); PaUtil_SetInterleavedInputChannels(&s->bufproc, 0, s->rbuf, s->par.rchan); res = PaUtil_CopyInput(&s->bufproc, &data, n); if (res != n) { DPR("BlockingReadStream: copyInput: %u != %u\n"); return paUnanticipatedHostError; } numFrames -= n; } return paNoError; } static PaError BlockingWriteStream(PaStream* stream, const void *data, unsigned long numFrames) { PaSndioStream *s = (PaSndioStream *)stream; unsigned n, res; while (numFrames > 0) { n = s->par.round; if (n > numFrames) n = numFrames; PaUtil_SetOutputFrameCount(&s->bufproc, n); PaUtil_SetInterleavedOutputChannels(&s->bufproc, 0, s->wbuf, s->par.pchan); res = PaUtil_CopyOutput(&s->bufproc, &data, n); if (res != n) { DPR("BlockingWriteStream: copyOutput: %u != %u\n"); return paUnanticipatedHostError; } res = sio_write(s->hdl, s->wbuf, n * s->par.pchan * s->par.bps); if (res == 0) return paUnanticipatedHostError; s->wpos += n; numFrames -= n; } return paNoError; } static signed long BlockingGetStreamReadAvailable(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; struct pollfd pfd; int n, events; n = sio_pollfd(s->hdl, &pfd, POLLIN); while (poll(&pfd, n, 0) < 0) { if (errno == EINTR) continue; perror("poll"); abort(); } events = sio_revents(s->hdl, &pfd); if (!(events & POLLIN)) return 0; return s->realpos - s->rpos; } static signed long BlockingGetStreamWriteAvailable(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; struct pollfd pfd; int n, events; n = sio_pollfd(s->hdl, &pfd, POLLOUT); while (poll(&pfd, n, 0) < 0) { if (errno == EINTR) continue; perror("poll"); abort(); } events = sio_revents(s->hdl, &pfd); if (!(events & POLLOUT)) return 0; return s->par.bufsz - (s->wpos - s->realpos); } static PaError BlockingWaitEmpty( PaStream *stream ) { PaSndioStream *s = (PaSndioStream *)stream; /* * drain playback buffers; sndio always does it in background * and there is no way to wait for completion */ DPR("BlockingWaitEmpty: s=%d, a=%d\n", s->stopped, s->active); return paNoError; } static PaError StartStream(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; unsigned primes, wblksz; int err; DPR("StartStream: s=%d, a=%d\n", s->stopped, s->active); if (!s->stopped) { DPR("StartStream: already started\n"); return paNoError; } s->stopped = 0; s->active = 1; s->realpos = 0; s->wpos = 0; s->rpos = 0; PaUtil_ResetBufferProcessor(&s->bufproc); if (!sio_start(s->hdl)) return paUnanticipatedHostError; /* * send a complete buffer of silence */ if (s->mode & SIO_PLAY) { wblksz = s->par.round * s->par.pchan * s->par.bps; memset(s->wbuf, 0, wblksz); for (primes = s->par.bufsz / s->par.round; primes > 0; primes--) s->wpos += sio_write(s->hdl, s->wbuf, wblksz); } if (s->base.streamCallback) { err = pthread_create(&s->thread, NULL, sndioThread, s); if (err) { DPR("SndioStartStream: couldn't create thread\n"); return paUnanticipatedHostError; } DPR("StartStream: started...\n"); } return paNoError; } static PaError StopStream(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; void *ret; int err; DPR("StopStream: s=%d, a=%d\n", s->stopped, s->active); if (s->stopped) { DPR("StartStream: already started\n"); return paNoError; } s->stopped = 1; if (s->base.streamCallback) { err = pthread_join(s->thread, &ret); if (err) { DPR("SndioStop: couldn't join thread\n"); return paUnanticipatedHostError; } } if (!sio_stop(s->hdl)) return paUnanticipatedHostError; return paNoError; } static PaError CloseStream(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; DPR("CloseStream:\n"); if (!s->stopped) StopStream(stream); if (s->mode & SIO_REC) free(s->rbuf); if (s->mode & SIO_PLAY) free(s->wbuf); sio_close(s->hdl); PaUtil_TerminateStreamRepresentation(&s->base); PaUtil_TerminateBufferProcessor(&s->bufproc); PaUtil_FreeMemory(s); return paNoError; } static PaError AbortStream(PaStream *stream) { DPR("AbortStream:\n"); return StopStream(stream); } static PaError IsStreamStopped(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; //DPR("IsStreamStopped: s=%d, a=%d\n", s->stopped, s->active); return s->stopped; } static PaError IsStreamActive(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; //DPR("IsStreamActive: s=%d, a=%d\n", s->stopped, s->active); return s->active; } static PaTime GetStreamTime(PaStream *stream) { PaSndioStream *s = (PaSndioStream *)stream; return (double)s->realpos / s->base.streamInfo.sampleRate; } static PaError IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputPar, const PaStreamParameters *outputPar, double sampleRate) { return paFormatIsSupported; } static void Terminate(struct PaUtilHostApiRepresentation *hostApi) { PaSndioHostApiRepresentation *sndioHostApi; sndioHostApi = (PaSndioHostApiRepresentation *)hostApi; free(sndioHostApi->audiodevices); PaUtil_FreeMemory(hostApi); } static void InitDeviceInfo(PaDeviceInfo *info, PaHostApiIndex hostApiIndex, const char *name) { info->structVersion = 2; info->name = name; info->hostApi = hostApiIndex; info->maxInputChannels = 128; info->maxOutputChannels = 128; info->defaultLowInputLatency = 0.01; info->defaultLowOutputLatency = 0.01; info->defaultHighInputLatency = 0.5; info->defaultHighOutputLatency = 0.5; info->defaultSampleRate = 48000; } PaError PaSndio_Initialize(PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex) { PaSndioHostApiRepresentation *sndioHostApi; PaDeviceInfo *info; struct sio_hdl *hdl; char *audiodevices; char *device; size_t deviceCount; DPR("PaSndio_Initialize: initializing...\n"); /* unusable APIs should return paNoError and a NULL hostApi */ *hostApi = NULL; sndioHostApi = PaUtil_AllocateMemory(sizeof(PaSndioHostApiRepresentation)); if (sndioHostApi == NULL) return paNoError; // Add default device info = &sndioHostApi->device_info[0]; InitDeviceInfo(info, hostApiIndex, SIO_DEVANY); sndioHostApi->infos[0] = info; deviceCount = 1; // Add additional devices as specified in the PA_SNDIO_AUDIODEVICES // environment variable as a colon separated list sndioHostApi->audiodevices = NULL; audiodevices = getenv("PA_SNDIO_AUDIODEVICES"); if (audiodevices != NULL) { sndioHostApi->audiodevices = strdup(audiodevices); if (sndioHostApi->audiodevices == NULL) return paNoError; audiodevices = sndioHostApi->audiodevices; while ((device = strsep(&audiodevices, ":")) != NULL && deviceCount < PA_SNDIO_AUDIODEVICES_MAX) { if (*device == '\0') continue; info = &sndioHostApi->device_info[deviceCount]; InitDeviceInfo(info, hostApiIndex, device); sndioHostApi->infos[deviceCount] = info; deviceCount++; } } *hostApi = &sndioHostApi->base; (*hostApi)->info.structVersion = 1; (*hostApi)->info.type = paSndio; (*hostApi)->info.name = "sndio"; (*hostApi)->info.deviceCount = deviceCount; (*hostApi)->info.defaultInputDevice = 0; (*hostApi)->info.defaultOutputDevice = 0; (*hostApi)->deviceInfos = sndioHostApi->infos; (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; PaUtil_InitializeStreamInterface(&sndioHostApi->blocking, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, GetStreamTime, PaUtil_DummyGetCpuLoad, BlockingReadStream, BlockingWriteStream, BlockingGetStreamReadAvailable, BlockingGetStreamWriteAvailable); PaUtil_InitializeStreamInterface(&sndioHostApi->callback, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, GetStreamTime, PaUtil_DummyGetCpuLoad, PaUtil_DummyRead, PaUtil_DummyWrite, PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable); DPR("PaSndio_Initialize: done\n"); return paNoError; }