Blob Blame History Raw
/*
	pulse: audio output using PulseAudio server

	copyright 2006-2016 by the mpg123 project - free software under the terms of the LGPL 2.1
	see COPYING and AUTHORS files in distribution or http://mpg123.org
	initially written by Nicholas J. Humfrey
*/

#include "out123_int.h"

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <pulse/simple.h>
#include <pulse/error.h>

#include "debug.h"

static int open_pulse(out123_handle *ao)
{
	int err;
	pa_simple* pas = NULL;
	pa_sample_spec ss;
	/* Check if already open ? */
	if (ao->userptr) {
		if(!AOQUIET)
			error("Pulse audio output is already open.");
		return -1;
	}

	/* Open an audio I/O stream. */
	/* When they are < 0, I shall set some default. */
	if(ao->rate < 0 || ao->format < 0 || ao->channels < 0)
	{
		ao->rate     = 44100;
		ao->channels = 2;
		ao->format   = MPG123_ENC_SIGNED_16;
	}

	/* Fill out pulse audio's data structure */
	ss.channels = ao->channels;
	ss.rate = ao->rate;

	switch(ao->format) {
		case MPG123_ENC_SIGNED_16:
#ifdef WORDS_BIGENDIAN
			ss.format=PA_SAMPLE_S16BE;
#else
			ss.format=PA_SAMPLE_S16LE;
#endif
		break;
		case MPG123_ENC_SIGNED_24:
#ifdef WORDS_BIGENDIAN
			ss.format=PA_SAMPLE_S24BE;
#else
			ss.format=PA_SAMPLE_S24LE;
#endif
		break;
		case MPG123_ENC_SIGNED_32:
#ifdef WORDS_BIGENDIAN
			ss.format=PA_SAMPLE_S32BE;
#else
			ss.format=PA_SAMPLE_S32LE;
#endif
		break;
		case MPG123_ENC_FLOAT_32:
#ifdef WORDS_BIGENDIAN
			ss.format=PA_SAMPLE_FLOAT32BE;
#else
			ss.format=PA_SAMPLE_FLOAT32LE;
#endif
		break;
		case MPG123_ENC_ALAW_8:
			ss.format=PA_SAMPLE_ALAW;
		break;
		case MPG123_ENC_ULAW_8:
			ss.format=PA_SAMPLE_ULAW;
		break;
		case MPG123_ENC_UNSIGNED_8:
			ss.format=PA_SAMPLE_U8;
		break;
		default:
			if(!AOQUIET)
				error1("Unsupported audio format: 0x%x", ao->format);
			return -1;
		break;
	}


	/* Perform the open */
	pas = pa_simple_new(
			NULL,				/* Use the default server */
			ao->name,		/* Our application's name */
			PA_STREAM_PLAYBACK,
			ao->device,			/* Use the default device if NULL */
			"via out123",		/* Description of our stream */
			&ss,				/* Our sample format */
			NULL,				/* Use default channel map */
			NULL,				/* Use default buffering attributes */
			&err				/* Error result code */
	);

	if(pas == NULL)
	{
		if(!AOQUIET)
			error1("Failed to open pulse audio output: %s", pa_strerror(err));
		return -1;
	}

	/* Store the pointer */
	ao->userptr = (void*)pas;
	return 0;
}


static int get_formats_pulse(out123_handle *ao)
{
	/* Only implemented Signed 16-bit audio for now */
	return MPG123_ENC_SIGNED_16;
}


static int write_pulse(out123_handle *ao, unsigned char *buf, int len)
{
	pa_simple *pas = (pa_simple*)ao->userptr;
	int ret, err;
	/* Doesn't return number of bytes but just success or not. */
	ret = pa_simple_write( pas, buf, len, &err );
	if(ret<0)
	{
		if(!AOQUIET)
			error1("Failed to write audio: %s", pa_strerror(err));
		return -1;
	}
	return len; /* If successful, everything has been written. */
}

static int close_pulse(out123_handle *ao)
{
	pa_simple *pas = (pa_simple*)ao->userptr;

	if (pas) {
		int err; /* Do we really want to handle errors here? End is the end. */
		pa_simple_drain(pas, &err);
		pa_simple_free(pas);
		ao->userptr = NULL;
	}
	
	return 0;
}

static void flush_pulse(out123_handle *ao)
{
	pa_simple *pas = (pa_simple*)ao->userptr;
	
	if (pas) {
		int err;
		pa_simple_flush( pas, &err );	
		if(err && !AOQUIET)
			error1("Failed to flush audio: %s", pa_strerror(err));
	}
}


static int init_pulse(out123_handle* ao)
{
	if (ao==NULL) return -1;

	/* Set callbacks */
	ao->open = open_pulse;
	ao->flush = flush_pulse;
	ao->write = write_pulse;
	ao->get_formats = get_formats_pulse;
	ao->close = close_pulse;

	/* Success */
	return 0;
}


/* 
	Module information data structure
*/
mpg123_module_t mpg123_output_module_info = {
	/* api_version */	MPG123_MODULE_API_VERSION,
	/* name */			"pulse",						
	/* description */	"Output audio using PulseAudio Server",
	/* revision */		"$Rev:$",						
	/* handle */		NULL,
	
	/* init_output */	init_pulse,						
};