/** * @file portaudio.c Portaudio sound driver * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include /* * portaudio v19 is required */ struct ausrc_st { struct ausrc *as; /* inheritance */ PaStream *stream_rd; ausrc_read_h *rh; void *arg; volatile bool ready; unsigned ch; }; struct auplay_st { struct auplay *ap; /* inheritance */ PaStream *stream_wr; auplay_write_h *wh; void *arg; volatile bool ready; unsigned ch; }; static struct ausrc *ausrc; static struct auplay *auplay; /* * This routine will be called by the PortAudio engine when audio is needed. * It may called at interrupt level on some machines so don't do anything * that could mess up the system like calling malloc() or free(). */ static int read_callback(const void *inputBuffer, void *outputBuffer, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { struct ausrc_st *st = userData; unsigned sampc; (void)outputBuffer; (void)timeInfo; (void)statusFlags; if (!st->ready) return paAbort; sampc = frameCount * st->ch; st->rh(inputBuffer, 2*sampc, st->arg); return paContinue; } static int write_callback(const void *inputBuffer, void *outputBuffer, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { struct auplay_st *st = userData; unsigned sampc; (void)inputBuffer; (void)timeInfo; (void)statusFlags; if (!st->ready) return paAbort; sampc = frameCount * st->ch; st->wh(outputBuffer, 2*sampc, st->arg); return paContinue; } static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm, uint32_t dev) { PaStreamParameters prm_in; PaError err; unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000; memset(&prm_in, 0, sizeof(prm_in)); prm_in.device = dev; prm_in.channelCount = prm->ch; prm_in.sampleFormat = paInt16; prm_in.suggestedLatency = 0.100; st->stream_rd = NULL; err = Pa_OpenStream(&st->stream_rd, &prm_in, NULL, prm->srate, frames_per_buffer, paNoFlag, read_callback, st); if (paNoError != err) { warning("portaudio: read: Pa_OpenStream: %s\n", Pa_GetErrorText(err)); return EINVAL; } err = Pa_StartStream(st->stream_rd); if (paNoError != err) { warning("portaudio: read: Pa_StartStream: %s\n", Pa_GetErrorText(err)); return EINVAL; } return 0; } static int write_stream_open(struct auplay_st *st, const struct auplay_prm *prm, uint32_t dev) { PaStreamParameters prm_out; PaError err; unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000; memset(&prm_out, 0, sizeof(prm_out)); prm_out.device = dev; prm_out.channelCount = prm->ch; prm_out.sampleFormat = paInt16; prm_out.suggestedLatency = 0.100; st->stream_wr = NULL; err = Pa_OpenStream(&st->stream_wr, NULL, &prm_out, prm->srate, frames_per_buffer, paNoFlag, write_callback, st); if (paNoError != err) { warning("portaudio: write: Pa_OpenStream: %s\n", Pa_GetErrorText(err)); return EINVAL; } err = Pa_StartStream(st->stream_wr); if (paNoError != err) { warning("portaudio: write: Pa_StartStream: %s\n", Pa_GetErrorText(err)); return EINVAL; } return 0; } static void ausrc_destructor(void *arg) { struct ausrc_st *st = arg; st->ready = false; if (st->stream_rd) { Pa_AbortStream(st->stream_rd); Pa_CloseStream(st->stream_rd); } mem_deref(st->as); } static void auplay_destructor(void *arg) { struct auplay_st *st = arg; st->ready = false; if (st->stream_wr) { Pa_AbortStream(st->stream_wr); Pa_CloseStream(st->stream_wr); } mem_deref(st->ap); } static int src_alloc(struct ausrc_st **stp, struct ausrc *as, struct media_ctx **ctx, struct ausrc_prm *prm, const char *device, ausrc_read_h *rh, ausrc_error_h *errh, void *arg) { struct ausrc_st *st; PaDeviceIndex dev_index; int err; (void)ctx; (void)device; (void)errh; if (!stp || !as || !prm) return EINVAL; if (str_isset(device)) dev_index = atoi(device); else dev_index = Pa_GetDefaultInputDevice(); prm->fmt = AUFMT_S16LE; st = mem_zalloc(sizeof(*st), ausrc_destructor); if (!st) return ENOMEM; st->as = mem_ref(as); st->rh = rh; st->arg = arg; st->ch = prm->ch; st->ready = true; err = read_stream_open(st, prm, dev_index); if (err) goto out; out: if (err) mem_deref(st); else *stp = st; return err; } static int play_alloc(struct auplay_st **stp, struct auplay *ap, struct auplay_prm *prm, const char *device, auplay_write_h *wh, void *arg) { struct auplay_st *st; PaDeviceIndex dev_index; int err; (void)device; if (!stp || !ap || !prm) return EINVAL; if (str_isset(device)) dev_index = atoi(device); else dev_index = Pa_GetDefaultOutputDevice(); prm->fmt = AUFMT_S16LE; st = mem_zalloc(sizeof(*st), auplay_destructor); if (!st) return ENOMEM; st->ap = mem_ref(ap); st->wh = wh; st->arg = arg; st->ch = prm->ch; st->ready = true; err = write_stream_open(st, prm, dev_index); if (err) goto out; out: if (err) mem_deref(st); else *stp = st; return err; } static int pa_init(void) { PaError paerr; int i, n, err = 0; paerr = Pa_Initialize(); if (paNoError != paerr) { warning("portaudio: init: %s\n", Pa_GetErrorText(paerr)); return ENODEV; } n = Pa_GetDeviceCount(); info("portaudio: device count is %d\n", n); for (i=0; iname); (void)devinfo; } if (paNoDevice != Pa_GetDefaultInputDevice()) err |= ausrc_register(&ausrc, "portaudio", src_alloc); if (paNoDevice != Pa_GetDefaultOutputDevice()) err |= auplay_register(&auplay, "portaudio", play_alloc); return err; } static int pa_close(void) { ausrc = mem_deref(ausrc); auplay = mem_deref(auplay); Pa_Terminate(); return 0; } EXPORT_SYM const struct mod_export DECL_EXPORTS(portaudio) = { "portaudio", "sound", pa_init, pa_close };