Blame src/libout123/modules/alsa.c

Packit c32a2d
/*
Packit c32a2d
	alsa: sound output with Advanced Linux Sound Architecture 1.x API
Packit c32a2d
Packit c32a2d
	copyright 2006-2016 by the mpg123 project - free software under the terms of the LGPL 2.1
Packit c32a2d
	see COPYING and AUTHORS files in distribution or http://mpg123.org
Packit c32a2d
Packit c32a2d
	initially written by Clemens Ladisch <clemens@ladisch.de>
Packit c32a2d
*/
Packit c32a2d
Packit c32a2d
/* ALSA headers define struct timeval if no POSIX macro is set,
Packit c32a2d
   nicely in conflict with definitions in system headers. They had
Packit c32a2d
   a discussion about that a long time ago:
Packit c32a2d
     http://mailman.alsa-project.org/pipermail/alsa-devel/2007-June/001684.html
Packit c32a2d
   ... seems like the conclusion was not carried through.
Packit c32a2d
 */
Packit c32a2d
#define _POSIX_SOURCE
Packit c32a2d
/* Things are still missing if _DEFAULT_SOURCE is not defined (for recent
Packit c32a2d
   glibc, I presume. */
Packit c32a2d
#define _DEFAULT_SOURCE
Packit c32a2d
#include "out123_int.h"
Packit c32a2d
#include <errno.h>
Packit c32a2d
Packit c32a2d
/* make ALSA 0.9.x compatible to the 1.0.x API */
Packit c32a2d
#define ALSA_PCM_NEW_HW_PARAMS_API
Packit c32a2d
#define ALSA_PCM_NEW_SW_PARAMS_API
Packit c32a2d
Packit c32a2d
#include <alloca.h> /* GCC complains about missing declaration of alloca. */
Packit c32a2d
#include <alsa/asoundlib.h>
Packit c32a2d
Packit c32a2d
#include "debug.h"
Packit c32a2d
Packit c32a2d
/* Total buffer size in seconds, 0.2 is more true to what ALSA maximally uses
Packit c32a2d
   here (8192 samples). The earlier default of 0.5 was never true. */
Packit c32a2d
#define BUFFER_LENGTH (ao->device_buffer > 0. ? ao->device_buffer : 0.2)
Packit c32a2d
Packit c32a2d
static const struct {
Packit c32a2d
	snd_pcm_format_t alsa;
Packit c32a2d
	int mpg123;
Packit c32a2d
} format_map[] = {
Packit c32a2d
	{ SND_PCM_FORMAT_S16,    MPG123_ENC_SIGNED_16   },
Packit c32a2d
	{ SND_PCM_FORMAT_U16,    MPG123_ENC_UNSIGNED_16 },
Packit c32a2d
	{ SND_PCM_FORMAT_U8,     MPG123_ENC_UNSIGNED_8  },
Packit c32a2d
	{ SND_PCM_FORMAT_S8,     MPG123_ENC_SIGNED_8    },
Packit c32a2d
	{ SND_PCM_FORMAT_A_LAW,  MPG123_ENC_ALAW_8      },
Packit c32a2d
	{ SND_PCM_FORMAT_MU_LAW, MPG123_ENC_ULAW_8      },
Packit c32a2d
	{ SND_PCM_FORMAT_S32,    MPG123_ENC_SIGNED_32   },
Packit c32a2d
	{ SND_PCM_FORMAT_U32,    MPG123_ENC_UNSIGNED_32 },
Packit c32a2d
#ifdef WORDS_BIGENDIAN
Packit c32a2d
	{ SND_PCM_FORMAT_S24_3BE, MPG123_ENC_SIGNED_24   },
Packit c32a2d
	{ SND_PCM_FORMAT_U24_3BE, MPG123_ENC_UNSIGNED_24 },
Packit c32a2d
#else
Packit c32a2d
	{ SND_PCM_FORMAT_S24_3LE, MPG123_ENC_SIGNED_24   },
Packit c32a2d
	{ SND_PCM_FORMAT_U24_3LE, MPG123_ENC_UNSIGNED_24 },
Packit c32a2d
#endif
Packit c32a2d
	{ SND_PCM_FORMAT_FLOAT,  MPG123_ENC_FLOAT_32    },
Packit c32a2d
	{ SND_PCM_FORMAT_FLOAT64, MPG123_ENC_FLOAT_64   }
Packit c32a2d
};
Packit c32a2d
#define NUM_FORMATS (sizeof format_map / sizeof format_map[0])
Packit c32a2d
Packit c32a2d
Packit c32a2d
static int rates_match(long int desired, unsigned int actual)
Packit c32a2d
{
Packit c32a2d
	return actual * 100 > desired * (100 - AUDIO_RATE_TOLERANCE) &&
Packit c32a2d
	       actual * 100 < desired * (100 + AUDIO_RATE_TOLERANCE);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static int initialize_device(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	snd_pcm_hw_params_t *hw=NULL;
Packit c32a2d
	snd_pcm_sw_params_t *sw=NULL;
Packit c32a2d
	snd_pcm_uframes_t buffer_size;
Packit c32a2d
	snd_pcm_uframes_t period_size;
Packit c32a2d
	snd_pcm_format_t format;
Packit c32a2d
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
Packit c32a2d
	unsigned int rate;
Packit c32a2d
	int i;
Packit c32a2d
Packit c32a2d
	snd_pcm_hw_params_alloca(&hw;; /* Ignore GCC warning here... alsa-lib>=1.0.16 doesn't trigger that anymore, too. */
Packit c32a2d
	if (snd_pcm_hw_params_any(pcm, hw) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): no configuration available");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	if (snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): device does not support interleaved access");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	format = SND_PCM_FORMAT_UNKNOWN;
Packit c32a2d
	for (i = 0; i < NUM_FORMATS; ++i) {
Packit c32a2d
		if (ao->format == format_map[i].mpg123) {
Packit c32a2d
			format = format_map[i].alsa;
Packit c32a2d
			break;
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
	if (format == SND_PCM_FORMAT_UNKNOWN) {
Packit c32a2d
		if(!AOQUIET) error1("initialize_device(): invalid sample format %d", ao->format);
Packit c32a2d
		errno = EINVAL;
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	if (snd_pcm_hw_params_set_format(pcm, hw, format) < 0) {
Packit c32a2d
		if(!AOQUIET) error1("initialize_device(): cannot set format %s", snd_pcm_format_name(format));
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	if (snd_pcm_hw_params_set_channels(pcm, hw, ao->channels) < 0) {
Packit c32a2d
		if(!AOQUIET) error1("initialize_device(): cannot set %d channels", ao->channels);
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	rate = ao->rate;
Packit c32a2d
	if (snd_pcm_hw_params_set_rate_near(pcm, hw, &rate, NULL) < 0) {
Packit c32a2d
		if(!AOQUIET) error1("initialize_device(): cannot set rate %u", rate);
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	if (!rates_match(ao->rate, rate)) {
Packit c32a2d
		if(!AOQUIET) error2("initialize_device(): rate %ld not available, using %u", ao->rate, rate);
Packit c32a2d
		/* return -1; */
Packit c32a2d
	}
Packit c32a2d
	buffer_size = rate * BUFFER_LENGTH;
Packit c32a2d
	if (snd_pcm_hw_params_set_buffer_size_near(pcm, hw, &buffer_size) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot set buffer size");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	debug1("buffer_size=%lu", (unsigned long)buffer_size);
Packit c32a2d
	period_size = buffer_size / 3; /* 3 periods is so much more common. */
Packit c32a2d
	if (snd_pcm_hw_params_set_period_size_near(pcm, hw, &period_size, NULL) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot set period size");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	debug1("period_size=%lu", (unsigned long)period_size);
Packit c32a2d
	if (snd_pcm_hw_params(pcm, hw) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot set hw params");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
	snd_pcm_sw_params_alloca(&sw);
Packit c32a2d
	if (snd_pcm_sw_params_current(pcm, sw) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot get sw params");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	/* start playing right away */
Packit c32a2d
	if (snd_pcm_sw_params_set_start_threshold(pcm, sw, 1) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot set start threshold");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	/* wake up on every interrupt */
Packit c32a2d
	if (snd_pcm_sw_params_set_avail_min(pcm, sw, 1) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot set min available");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
#if SND_LIB_VERSION < ((1<<16)|16)
Packit c32a2d
	/* Always write as many frames as possible (deprecated since alsa-lib 1.0.16) */
Packit c32a2d
	if (snd_pcm_sw_params_set_xfer_align(pcm, sw, 1) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot set transfer alignment");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
#endif
Packit c32a2d
	if (snd_pcm_sw_params(pcm, sw) < 0) {
Packit c32a2d
		if(!AOQUIET) error("initialize_device(): cannot set sw params");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	return 0;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
#ifndef DEBUG
Packit c32a2d
static void error_ignorer(const char *file, int line, const char *function, int err, const char *fmt,...)
Packit c32a2d
{
Packit c32a2d
	/* I can make ALSA silent. */
Packit c32a2d
}
Packit c32a2d
#endif
Packit c32a2d
Packit c32a2d
static int open_alsa(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	const char *pcm_name;
Packit c32a2d
	snd_pcm_t *pcm=NULL;
Packit c32a2d
	debug1("open_alsa with %p", ao->userptr);
Packit c32a2d
Packit c32a2d
#ifndef DEBUG
Packit c32a2d
	if(AOQUIET) snd_lib_error_set_handler(error_ignorer);
Packit c32a2d
#endif
Packit c32a2d
Packit c32a2d
	pcm_name = ao->device ? ao->device : "default";
Packit c32a2d
	if (snd_pcm_open(&pcm, pcm_name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
Packit c32a2d
		if(!AOQUIET) error1("cannot open device %s", pcm_name);
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	ao->userptr = pcm;
Packit c32a2d
	if (ao->format != -1) {
Packit c32a2d
		/* we're going to play: initalize sample format */
Packit c32a2d
		return initialize_device(ao);
Packit c32a2d
	} else {
Packit c32a2d
		/* query mode; sample format will be set for each query */
Packit c32a2d
		return 0;
Packit c32a2d
	}
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
Packit c32a2d
static int get_formats_alsa(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
Packit c32a2d
	snd_pcm_hw_params_t *hw;
Packit c32a2d
	unsigned int rate;
Packit c32a2d
	int supported_formats, i;
Packit c32a2d
Packit c32a2d
	snd_pcm_hw_params_alloca(&hw;;
Packit c32a2d
	if (snd_pcm_hw_params_any(pcm, hw) < 0) {
Packit c32a2d
		if(!AOQUIET) error("get_formats_alsa(): no configuration available");
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	if (snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
Packit c32a2d
		return -1;
Packit c32a2d
	if (snd_pcm_hw_params_set_channels(pcm, hw, ao->channels) < 0)
Packit c32a2d
		return 0;
Packit c32a2d
	rate = ao->rate;
Packit c32a2d
	if (snd_pcm_hw_params_set_rate_near(pcm, hw, &rate, NULL) < 0)
Packit c32a2d
		return -1;
Packit c32a2d
	if (!rates_match(ao->rate, rate))
Packit c32a2d
		return 0;
Packit c32a2d
	supported_formats = 0;
Packit c32a2d
	for (i = 0; i < NUM_FORMATS; ++i) {
Packit c32a2d
		if (snd_pcm_hw_params_test_format(pcm, hw, format_map[i].alsa) == 0)
Packit c32a2d
			supported_formats |= format_map[i].mpg123;
Packit c32a2d
	}
Packit c32a2d
	return supported_formats;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static int write_alsa(out123_handle *ao, unsigned char *buf, int bytes)
Packit c32a2d
{
Packit c32a2d
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
Packit c32a2d
	snd_pcm_uframes_t frames;
Packit c32a2d
	snd_pcm_sframes_t written;
Packit c32a2d
Packit c32a2d
	frames = snd_pcm_bytes_to_frames(pcm, bytes);
Packit c32a2d
	while
Packit c32a2d
	( /* Try to write, recover if error, try again if recovery successful. */
Packit c32a2d
		(written = snd_pcm_writei(pcm, buf, frames)) < 0
Packit c32a2d
		&& snd_pcm_recover(pcm, (int)written, 0) == 0
Packit c32a2d
	)
Packit c32a2d
	{
Packit c32a2d
		debug2("recovered from alsa issue %i while trying to write %lu frames", (int)written, (unsigned long)frames);
Packit c32a2d
	}
Packit c32a2d
	if(written < 0)
Packit c32a2d
	{
Packit c32a2d
		error1("Fatal problem with alsa output, error %i.", (int)written);
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	else return snd_pcm_frames_to_bytes(pcm, written);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static void flush_alsa(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
Packit c32a2d
Packit c32a2d
	/* is this the optimal solution? - we should figure out what we really whant from this function */
Packit c32a2d
Packit c32a2d
debug("alsa drop");
Packit c32a2d
	snd_pcm_drop(pcm);
Packit c32a2d
debug("alsa prepare");
Packit c32a2d
	snd_pcm_prepare(pcm);
Packit c32a2d
debug("alsa flush done");
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static void drain_alsa(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
Packit c32a2d
	debug1("drain_alsa with %p", ao->userptr);
Packit c32a2d
	snd_pcm_drain(pcm);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static int close_alsa(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
Packit c32a2d
	debug1("close_alsa with %p", ao->userptr);
Packit c32a2d
	if(pcm != NULL) /* be really generous for being called without any device opening */
Packit c32a2d
	{
Packit c32a2d
		ao->userptr = NULL; /* Should alsa do this or the module wrapper? */
Packit c32a2d
		return snd_pcm_close(pcm);
Packit c32a2d
	}
Packit c32a2d
	else return 0;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static int init_alsa(out123_handle* ao)
Packit c32a2d
{
Packit c32a2d
	if (ao==NULL) return -1;
Packit c32a2d
Packit c32a2d
	/* Set callbacks */
Packit c32a2d
	ao->open = open_alsa;
Packit c32a2d
	ao->flush = flush_alsa;
Packit c32a2d
	ao->drain = drain_alsa;
Packit c32a2d
	ao->write = write_alsa;
Packit c32a2d
	ao->get_formats = get_formats_alsa;
Packit c32a2d
	ao->close = close_alsa;
Packit c32a2d
Packit c32a2d
	/* Success */
Packit c32a2d
	return 0;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
Packit c32a2d
Packit c32a2d
/* 
Packit c32a2d
	Module information data structure
Packit c32a2d
*/
Packit c32a2d
mpg123_module_t mpg123_output_module_info = {
Packit c32a2d
	/* api_version */	MPG123_MODULE_API_VERSION,
Packit c32a2d
	/* name */			"alsa",						
Packit c32a2d
	/* description */	"Output audio using Advanced Linux Sound Architecture (ALSA).",
Packit c32a2d
	/* revision */		"$Rev:$",						
Packit c32a2d
	/* handle */		NULL,
Packit c32a2d
	
Packit c32a2d
	/* init_output */	init_alsa,						
Packit c32a2d
};
Packit c32a2d