text: /**
   * @file portaudio.c  Portaudio sound driver
   *
   * Copyright (C) 2010 Alfred E. Heggestad
   */
  #include <stdlib.h>
  #include <string.h>
  #include <portaudio.h>
  #include <re.h>
  #include <rem.h>
  #include <baresip.h>
  
  
  /**
   * @defgroup portaudio portaudio
   *
   * Portaudio audio driver
   *
   * (portaudio v19 is required)
   *
   *
   * References:
   *
   *    http://www.portaudio.com/
   */
  
  
  struct ausrc_st {
  	PaStream *stream_rd;
  	ausrc_read_h *rh;
  	void *arg;
  	volatile bool ready;
  	unsigned ch;
  	enum aufmt fmt;
  };
  
  struct auplay_st {
  	PaStream *stream_wr;
  	auplay_write_h *wh;
  	void *arg;
  	volatile bool ready;
  	unsigned ch;
  	enum aufmt fmt;
  };
  
  
  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;
  	struct auframe af;
  	size_t sampc;
  
  	(void)outputBuffer;
  	(void)timeInfo;
  	(void)statusFlags;
  
  	if (!st->ready)
  		return paAbort;
  
  	sampc = frameCount * st->ch;
  
  	af.fmt   = st->fmt;
  	af.sampv = (void *)inputBuffer;
  	af.sampc = sampc;
  	af.timestamp = Pa_GetStreamTime(st->stream_rd) * AUDIO_TIMEBASE;
  
  	st->rh(&af, 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;
  	struct auframe af;
  	size_t sampc;
  
  	(void)inputBuffer;
  	(void)timeInfo;
  	(void)statusFlags;
  
  	if (!st->ready)
  		return paAbort;
  
  	sampc = frameCount * st->ch;
  
  	auframe_init(&af, st->fmt, outputBuffer, sampc);
  
  	st->wh(&af, st->arg);
  
  	return paContinue;
  }
  
  
  static PaSampleFormat aufmt_to_pasampleformat(enum aufmt fmt)
  {
  	switch (fmt) {
  
  	case AUFMT_S16LE: return paInt16;
  	case AUFMT_FLOAT: return paFloat32;
  	default: return 0;
  	}
  }
  
  
  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     = aufmt_to_pasampleformat(prm->fmt);
  	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     = aufmt_to_pasampleformat(prm->fmt);
  	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);
  	}
  }
  
  
  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);
  	}
  }
  
  
  static int src_alloc(struct ausrc_st **stp, const 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();
  
  	st = mem_zalloc(sizeof(*st), ausrc_destructor);
  	if (!st)
  		return ENOMEM;
  
  	st->rh  = rh;
  	st->arg = arg;
  	st->ch  = prm->ch;
  	st->fmt = prm->fmt;
  
  	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, const 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();
  
  	st = mem_zalloc(sizeof(*st), auplay_destructor);
  	if (!st)
  		return ENOMEM;
  
  	st->wh  = wh;
  	st->arg = arg;
  	st->ch  = prm->ch;
  	st->fmt = prm->fmt;
  
  	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; i<n; i++) {
  		const PaDeviceInfo *devinfo;
  
  		devinfo = Pa_GetDeviceInfo(i);
  
  		debug("portaudio: device %d: %s\n", i, devinfo->name);
  		(void)devinfo;
  	}
  
  	if (paNoDevice != Pa_GetDefaultInputDevice())
  		err |= ausrc_register(&ausrc, baresip_ausrcl(),
  				      "portaudio", src_alloc);
  
  	if (paNoDevice != Pa_GetDefaultOutputDevice())
  		err |= auplay_register(&auplay, baresip_auplayl(),
  				       "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
  };
  