The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 *  Driver for Advanced Linux Sound Architecture, http://alsa.jcu.cz
 * 
 *  Code by Anders Semb Hermansen <ahermans@vf.telia.no>
 *  Cleanups by Jaroslav Kysela <perex@jcu.cz>
 *              Ville Syrjala <syrjala@sci.fi>
 *
 *  You can use -a <soundcard #>:<device #>...
 *  For example: mpg123 -a 1:0 aaa.mpg
 *               mpg123 -a guspnp:1 aaa.mpg
 *
 * This file comes under GPL license. 
 */

#include "mpg123.h"

#include <stdlib.h>
#include <ctype.h>
#include <sys/asoundlib.h>

#ifdef SND_LITTLE_ENDIAN
#define SND_PCM_SFMT_S16_NE SND_PCM_SFMT_S16_LE
#define SND_PCM_SFMT_U16_NE SND_PCM_SFMT_U16_LE
#define SND_PCM_FMT_S16_NE SND_PCM_FMT_S16_LE
#define SND_PCM_FMT_U16_NE SND_PCM_FMT_U16_LE
#else
#define SND_PCM_SFMT_S16_NE SND_PCM_SFMT_S16_BE
#define SND_PCM_SFMT_U16_NE SND_PCM_SFMT_U16_BE
#define SND_PCM_FMT_S16_NE SND_PCM_FMT_S16_BE
#define SND_PCM_FMT_U16_NE SND_PCM_FMT_U16_BE
#endif

int audio_open(struct audio_info_struct *ai)
{
	int err;
	int card=0,device=0;
	char scard[128], sdevice[128];

	if(!ai)
		return -1;
	if(ai->device) {	/* parse ALSA device name */
		if(strchr(ai->device,':')) {	/* card with device */
			strncpy(scard, ai->device, sizeof(scard)-1);
			scard[sizeof(scard)-1] = '\0';
			if (strchr(scard,':')) *strchr(scard,':') = '\0';
			card = snd_card_name(scard);
			if (card < 0) {
				fprintf(stderr, "wrong soundcard number: %s\n", scard);
				exit(1);
			}
			strncpy(sdevice, strchr(ai->device, ':') + 1, sizeof(sdevice)-1);
		} else {
			strncpy(sdevice, ai->device, sizeof(sdevice)-1);
		}
		sdevice[sizeof(sdevice)-1] = '\0';
		device = atoi(sdevice);
		if (!isdigit(sdevice[0]) || device < 0 || device > 31) {
			fprintf(stderr, "wrong device number: %s\n", sdevice);
			exit(1);
		}
	}

	if((err=snd_pcm_open(&ai->handle, card, device, SND_PCM_OPEN_PLAYBACK)) < 0 )
	{
		fprintf(stderr, "open failed: %s\n", snd_strerror(err));
		exit(1);
	}

	if(audio_reset_parameters(ai) < 0)
	{
		audio_close(ai);
		return -1;
	}

	return 0;
}

static void audio_set_playback_params(struct audio_info_struct *ai)
{
	int err;
	snd_pcm_playback_info_t pi;
	snd_pcm_playback_params_t pp;

	if((err=snd_pcm_playback_info(ai->handle, &pi)) < 0 )
	{
		fprintf(stderr, "playback info failed: %s\n", snd_strerror(err));
		return;	/* not fatal error */
	}

	bzero(&pp, sizeof(pp));
	pp.fragment_size = pi.buffer_size/4;
	if (pp.fragment_size > pi.max_fragment_size) pp.fragment_size = pi.max_fragment_size;
	if (pp.fragment_size < pi.min_fragment_size) pp.fragment_size = pi.min_fragment_size;
	pp.fragments_max = -1;
	pp.fragments_room = 1;

	if((err=snd_pcm_playback_params(ai->handle, &pp)) < 0 )
	{
		fprintf(stderr, "playback params failed: %s\n", snd_strerror(err));
		return; /* not fatal error */
	}
}

int audio_reset_parameters(struct audio_info_struct *ai)
{
	audio_set_format(ai);
	audio_set_channels(ai);
	audio_set_rate(ai);

	return 0;
}

int audio_rate_best_match(struct audio_info_struct *ai)
{
	return 0;
}

int audio_set_rate(struct audio_info_struct *ai)
{
	int ret;

	if(!ai || ai->rate < 0)
		return -1;

	ai->alsa_format.rate=ai->rate;

	if((ret=snd_pcm_playback_format(ai->handle, &ai->alsa_format)) < 0 )
		return -1;

	audio_set_playback_params(ai);

	return 0;
}

int audio_set_channels(struct audio_info_struct *ai)
{
	int ret;

	if(ai->alsa_format.channels < 0)
		return 0;

	ai->alsa_format.channels = ai->channels;

	if((ret=snd_pcm_playback_format(ai->handle, &ai->alsa_format)) < 0 )
		return -1;

	audio_set_playback_params(ai);

	return 0;
}

int audio_set_format(struct audio_info_struct *ai)
{
	int ret;

	if(ai->format == -1)
		return 0;

	switch(ai->format)
	{
		case AUDIO_FORMAT_SIGNED_16:
		default:
			ai->alsa_format.format=SND_PCM_SFMT_S16_NE;
			break;
		case AUDIO_FORMAT_UNSIGNED_8:
			ai->alsa_format.format=SND_PCM_SFMT_U8;
			break;
		case AUDIO_FORMAT_SIGNED_8:
			ai->alsa_format.format=SND_PCM_SFMT_S8;
			break;
		case AUDIO_FORMAT_ULAW_8:
			ai->alsa_format.format=SND_PCM_SFMT_MU_LAW;
			break;
		case AUDIO_FORMAT_ALAW_8:
			ai->alsa_format.format=SND_PCM_SFMT_A_LAW;
			break;
		case AUDIO_FORMAT_UNSIGNED_16:
			ai->alsa_format.format=SND_PCM_SFMT_U16_NE;
			break;
	}

	if((ret=snd_pcm_playback_format(ai->handle, &ai->alsa_format)) < 0 )
		return -1;

	audio_set_playback_params(ai);

	return 0;
}

int audio_get_formats(struct audio_info_struct *ai)
{
	int i, err;
	int fmt = -1;
	snd_pcm_playback_info_t pi;

	static int fmts[] = {
		AUDIO_FORMAT_SIGNED_16, AUDIO_FORMAT_UNSIGNED_16,
		AUDIO_FORMAT_UNSIGNED_8, AUDIO_FORMAT_SIGNED_8,
		AUDIO_FORMAT_ULAW_8, AUDIO_FORMAT_ALAW_8
	};
	static int afmts[] = {
		SND_PCM_FMT_S16_NE, SND_PCM_FMT_U16_NE,
		SND_PCM_FMT_U8, SND_PCM_FMT_S8,
		SND_PCM_FMT_MU_LAW, SND_PCM_FMT_A_LAW
	};

	if((err=snd_pcm_playback_info(ai->handle, &pi)) < 0 )
	{
		fprintf(stderr, "playback info failed: %s\n", snd_strerror(err));
		return -1;
	}

	for (i = 0; i < 6; i++) {
		if (pi.formats & afmts[i]) {
			if (fmt == -1)
				fmt = 0;
			fmt |= fmts[i];
		}
	}

	return fmt;
}

int audio_play_samples(struct audio_info_struct *ai,unsigned char *buf,int len)
{
	ssize_t ret;

	ret=snd_pcm_write(ai->handle, buf, len);

	return ret;
}

int audio_close(struct audio_info_struct *ai)
{
	int ret;
	ret = snd_pcm_close(ai->handle);
	return ret;
}