/**
 * @file alsa.c  ALSA sound driver
 *
 * Copyright (C) 2010 Alfred E. Heggestad
 */
#define _DEFAULT_SOURCE 1
#define _POSIX_SOURCE 1
#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <re.h>
#include <rem.h>
#include <baresip.h>
#include "alsa.h"


/**
 * @defgroup alsa alsa
 *
 * Advanced Linux Sound Architecture (ALSA) audio driver module
 *
 *
 * References:
 *
 *    http://www.alsa-project.org/main/index.php/Main_Page
 */


char alsa_dev[64] = "default";

static struct ausrc *ausrc;
static struct auplay *auplay;


int alsa_reset(snd_pcm_t *pcm, uint32_t srate, uint32_t ch,
	       uint32_t num_frames,
	       snd_pcm_format_t pcmfmt)
{
	snd_pcm_hw_params_t *hw_params = NULL;
	snd_pcm_uframes_t period = num_frames, bufsize = num_frames * 4;
	int err;

	debug("alsa: reset: srate=%u, ch=%u, num_frames=%u, pcmfmt=%s\n",
	      srate, ch, num_frames, snd_pcm_format_name(pcmfmt));

	err = snd_pcm_hw_params_malloc(&hw_params);
	if (err < 0) {
		warning("alsa: cannot allocate hw params (%s)\n",
			snd_strerror(err));
		goto out;
	}

	err = snd_pcm_hw_params_any(pcm, hw_params);
	if (err < 0) {
		warning("alsa: cannot initialize hw params (%s)\n",
			snd_strerror(err));
		goto out;
	}

	err = snd_pcm_hw_params_set_access(pcm, hw_params,
					   SND_PCM_ACCESS_RW_INTERLEAVED);
	if (err < 0) {
		warning("alsa: cannot set access type (%s)\n",
			snd_strerror(err));
		goto out;
	}

	err = snd_pcm_hw_params_set_format(pcm, hw_params, pcmfmt);
	if (err < 0) {
		warning("alsa: cannot set sample format %d (%s)\n",
			pcmfmt, snd_strerror(err));
		goto out;
	}

	err = snd_pcm_hw_params_set_rate(pcm, hw_params, srate, 0);
	if (err < 0) {
		warning("alsa: cannot set sample rate to %u Hz (%s)\n",
			srate, snd_strerror(err));
		goto out;
	}

	err = snd_pcm_hw_params_set_channels(pcm, hw_params, ch);
	if (err < 0) {
		warning("alsa: cannot set channel count to %d (%s)\n",
			ch, snd_strerror(err));
		goto out;
	}

	err = snd_pcm_hw_params_set_period_size_near(pcm, hw_params,
						     &period, 0);
	if (err < 0) {
		warning("alsa: cannot set period size to %d (%s)\n",
			period, snd_strerror(err));
	}

	err = snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, &bufsize);
	if (err < 0) {
		warning("alsa: cannot set buffer size to %d (%s)\n",
			bufsize, snd_strerror(err));
	}

	err = snd_pcm_hw_params(pcm, hw_params);
	if (err < 0) {
		warning("alsa: cannot set parameters (%s)\n",
			snd_strerror(err));
		goto out;
	}

	err = snd_pcm_prepare(pcm);
	if (err < 0) {
		warning("alsa: cannot prepare audio interface for use (%s)\n",
			snd_strerror(err));
		goto out;
	}

	err = 0;

 out:
	snd_pcm_hw_params_free(hw_params);

	if (err) {
		warning("alsa: init failed: err=%d\n", err);
	}

	return err;
}


snd_pcm_format_t aufmt_to_alsaformat(enum aufmt fmt)
{
	switch (fmt) {

	case AUFMT_S16LE:    return SND_PCM_FORMAT_S16;
	case AUFMT_FLOAT:    return SND_PCM_FORMAT_FLOAT;
	case AUFMT_S24_3LE:  return SND_PCM_FORMAT_S24_3LE;
	default:             return SND_PCM_FORMAT_UNKNOWN;
	}
}


static int alsa_init(void)
{
	int err;

	err  = ausrc_register(&ausrc, baresip_ausrcl(),
			      "alsa", alsa_src_alloc);
	err |= auplay_register(&auplay, baresip_auplayl(),
			       "alsa", alsa_play_alloc);

	return err;
}


static int alsa_close(void)
{
	ausrc  = mem_deref(ausrc);
	auplay = mem_deref(auplay);

	/* releases all resources of the global configuration tree,
	   and sets snd_config to NULL. */
	snd_config_update_free_global();

	return 0;
}


const struct mod_export DECL_EXPORTS(alsa) = {
	"alsa",
	"sound",
	alsa_init,
	alsa_close
};
