Blame src/libout123/modules/sdl.c

Packit c32a2d
/*
Packit c32a2d
	sdl: audio output via SDL cross-platform 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
	initially written by Nicholas J. Humfrey
Packit c32a2d
*/
Packit c32a2d
Packit c32a2d
#include "out123_int.h"
Packit c32a2d
#include <math.h>
Packit c32a2d
Packit c32a2d
#include <SDL.h>
Packit c32a2d
Packit c32a2d
#ifdef WIN32
Packit c32a2d
#include <windows.h>
Packit c32a2d
#endif
Packit c32a2d
Packit c32a2d
/* Including the sfifo code locally, to avoid module linkage issues. */
Packit c32a2d
#define SFIFO_STATIC
Packit c32a2d
#include "sfifo.c"
Packit c32a2d
Packit c32a2d
#include "debug.h"
Packit c32a2d
Packit c32a2d
Packit c32a2d
#define SAMPLE_SIZE			(2)
Packit c32a2d
#define FRAMES_PER_BUFFER	(256)
Packit c32a2d
/* Performance of SDL with ALSA is a bit of a mystery to me. Regardless
Packit c32a2d
   of buffer size here, I just cannot avoid buffer underruns on my system.
Packit c32a2d
   SDL always chooses 1024x2 periods, which seems to be just not quite
Packit c32a2d
   enough on the Thinkpad:-/ Choosing 0.2 s as a plentiful default instead
Packit c32a2d
   of 0.5 s which is just a lie. */
Packit c32a2d
#define FIFO_DURATION		(ao->device_buffer > 0. ? ao->device_buffer : 0.2)
Packit c32a2d
#define BUFFER_SAMPLES		((FIFO_DURATION*ao->rate)/2)
Packit c32a2d
Packit c32a2d
struct handle
Packit c32a2d
{
Packit c32a2d
	int finished; /* A flag for communicating end, one-way. */
Packit c32a2d
	sfifo_t fifo;
Packit c32a2d
};
Packit c32a2d
Packit c32a2d
/* Some busy waiting. Proper stuff like semaphores might add
Packit c32a2d
   dependencies (POSIX) that the platform does not know. */
Packit c32a2d
static void ms_sleep(int milliseconds)
Packit c32a2d
{
Packit c32a2d
#ifdef WIN32
Packit c32a2d
		Sleep(milliseconds);
Packit c32a2d
#else
Packit c32a2d
		usleep(milliseconds*1000);
Packit c32a2d
#endif
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* The audio function callback takes the following parameters:
Packit c32a2d
       stream:  A pointer to the audio buffer to be filled
Packit c32a2d
       len:     The length (in bytes) of the audio buffer
Packit c32a2d
*/
Packit c32a2d
static void audio_callback_sdl(void *udata, Uint8 *stream, int len)
Packit c32a2d
{
Packit c32a2d
	out123_handle *ao = (out123_handle*)udata;
Packit c32a2d
	struct handle *sh = (struct handle*)ao->userptr;
Packit c32a2d
	sfifo_t *fifo = &sh->fifo;
Packit c32a2d
	int bytes_read;
Packit c32a2d
	int bytes_avail;
Packit c32a2d
Packit c32a2d
	/* Until the finished flag is set, we will wait for more.
Packit c32a2d
	   As the exact value does not matter and late detection of
Packit c32a2d
	   a change is kindof OK, I do not see a thread safety problem here. */
Packit c32a2d
	while((bytes_avail = sfifo_used(fifo)) < len && !sh->finished)
Packit c32a2d
	{
Packit c32a2d
		int ms = (len-bytes_avail)/ao->framesize*1000/ao->rate;
Packit c32a2d
		debug1("waiting for more input, %d ms missing", ms);
Packit c32a2d
		ms_sleep(ms/10);
Packit c32a2d
	}
Packit c32a2d
	/* Read audio from FIFO to SDL's buffer */
Packit c32a2d
	if(bytes_avail > len)
Packit c32a2d
		bytes_avail = len;
Packit c32a2d
	bytes_read = sfifo_read( fifo, stream, bytes_avail );
Packit c32a2d
	if(bytes_read != bytes_avail)
Packit c32a2d
		warning2("Error reading from the FIFO (wanted=%d, bytes_read=%d).\n"
Packit c32a2d
		,	bytes_avail, bytes_read);
Packit c32a2d
	if(bytes_read < 0)
Packit c32a2d
		bytes_read = 0;
Packit c32a2d
	/* Ensure that any remaining space is filled with zero bytes. */
Packit c32a2d
	if(bytes_read < len)
Packit c32a2d
		memset(stream+bytes_read, 0, len-bytes_read);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static int open_sdl(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	struct handle *sh = (struct handle*)ao->userptr;
Packit c32a2d
	sfifo_t *fifo = &sh->fifo;
Packit c32a2d
Packit c32a2d
	/* Open an audio I/O stream. */
Packit c32a2d
	if (ao->rate > 0 && ao->channels >0 ) {
Packit c32a2d
		size_t ringbuffer_len;
Packit c32a2d
		SDL_AudioSpec wanted;
Packit c32a2d
	
Packit c32a2d
		/* L16 uncompressed audio data, using 16-bit signed representation in twos 
Packit c32a2d
		   complement notation - system endian-ness. */
Packit c32a2d
		wanted.format = AUDIO_S16SYS;
Packit c32a2d
		/* Seems reasonable to demand a buffer size related to the device
Packit c32a2d
		   buffer. */
Packit c32a2d
		wanted.samples = BUFFER_SAMPLES;
Packit c32a2d
		wanted.callback = audio_callback_sdl; 
Packit c32a2d
		wanted.userdata = ao; 
Packit c32a2d
		wanted.channels = ao->channels; 
Packit c32a2d
		wanted.freq = ao->rate; 
Packit c32a2d
Packit c32a2d
		sh->finished = 0;
Packit c32a2d
		/* Open the audio device, forcing the desired format
Packit c32a2d
		   Actually, it is still subject to constraints by hardware.
Packit c32a2d
		   Need to have sample rate checked beforehand! SDL will
Packit c32a2d
		   happily play 22 kHz files with 44 kHz hardware rate!
Packit c32a2d
		   Same with channel count. No conversion. The manual is a bit
Packit c32a2d
		   misleading on that (only talking about sample format, I guess). */
Packit c32a2d
		if ( SDL_OpenAudio(&wanted, NULL) )
Packit c32a2d
		{
Packit c32a2d
			if(!AOQUIET)
Packit c32a2d
				error1("Couldn't open SDL audio: %s\n", SDL_GetError());
Packit c32a2d
			return -1;
Packit c32a2d
		}
Packit c32a2d
		
Packit c32a2d
		/* Initialise FIFO */
Packit c32a2d
		ringbuffer_len = ao->rate * FIFO_DURATION * SAMPLE_SIZE *ao->channels;
Packit c32a2d
		debug2( "Allocating %d byte ring-buffer (%f seconds)", (int)ringbuffer_len, (float)FIFO_DURATION);
Packit c32a2d
		if (sfifo_init( fifo, ringbuffer_len ) && !AOQUIET)
Packit c32a2d
			error1( "Failed to initialise FIFO of size %d bytes", (int)ringbuffer_len );
Packit c32a2d
	}
Packit c32a2d
	
Packit c32a2d
	return(0);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
Packit c32a2d
static int get_formats_sdl(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	/* Got no better idea than to just take 16 bit and run with it */
Packit c32a2d
	return MPG123_ENC_SIGNED_16;
Packit c32a2d
#if 0
Packit c32a2d
	/*
Packit c32a2d
		This code would "properly" test audio format support.
Packit c32a2d
		But thing is, SDL will always say yes and amen to everything, but it takes
Packit c32a2d
		an awful amount of time to get all the variants tested (about 2 seconds,
Packit c32a2d
		for example). I have seen SDL builds that do proper format conversion
Packit c32a2d
		behind your back, I have seen builds that do not. Every build seems to
Packit c32a2d
		claim that it does, though. Just hope you're lucky and your SDL works.
Packit c32a2d
		Otherwise, use a proper audio output API.
Packit c32a2d
	*/
Packit c32a2d
	SDL_AudioSpec wanted, got;
Packit c32a2d
Packit c32a2d
	/* Only implemented Signed 16-bit audio for now.
Packit c32a2d
	   The SDL manual doesn't suggest more interesting formats
Packit c32a2d
	   like S24 or S32 anyway. */
Packit c32a2d
	wanted.format = AUDIO_S16SYS;
Packit c32a2d
	wanted.samples = BUFFER_SAMPLES;
Packit c32a2d
	wanted.callback = audio_callback_sdl;
Packit c32a2d
	wanted.userdata = ao;
Packit c32a2d
	wanted.channels = ao->channels;
Packit c32a2d
	wanted.freq = ao->rate;
Packit c32a2d
Packit c32a2d
	if(SDL_OpenAudio(&wanted, &got)) return 0;
Packit c32a2d
	SDL_CloseAudio();
Packit c32a2d
fprintf(stderr, "wanted rate: %li got rate %li\n", (long)wanted.freq, (long)got.freq);
Packit c32a2d
	return (got.freq == ao->rate && got.channels == ao->channels)
Packit c32a2d
		? MPG123_ENC_SIGNED_16
Packit c32a2d
		: 0;
Packit c32a2d
#endif
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
Packit c32a2d
static int write_sdl(out123_handle *ao, unsigned char *buf, int len)
Packit c32a2d
{
Packit c32a2d
	struct handle *sh = (struct handle*)ao->userptr;
Packit c32a2d
	sfifo_t *fifo = &sh->fifo;
Packit c32a2d
	int len_remain = len;
Packit c32a2d
Packit c32a2d
	/* Some busy waiting, but feed what is possible. */
Packit c32a2d
	while(len_remain) /* Note: input len is multiple of framesize! */
Packit c32a2d
	{
Packit c32a2d
		int block = sfifo_space(fifo);
Packit c32a2d
		block -= block % ao->framesize;
Packit c32a2d
		if(block > len_remain)
Packit c32a2d
			block = len_remain;
Packit c32a2d
		if(block)
Packit c32a2d
		{
Packit c32a2d
			sfifo_write(fifo, buf, block);
Packit c32a2d
			len_remain -= block;
Packit c32a2d
			buf += block;
Packit c32a2d
			/* Unpause once the buffer is 50% full */
Packit c32a2d
			if (sfifo_used(fifo) > (sfifo_size(fifo)/2) )
Packit c32a2d
				SDL_PauseAudio(0);
Packit c32a2d
		}
Packit c32a2d
		if(len_remain)
Packit c32a2d
		{
Packit c32a2d
			debug1("Still need to write %d bytes, sleeping a bit.", len_remain);
Packit c32a2d
			ms_sleep(0.1*FIFO_DURATION*1000);
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
	return len;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static int close_sdl(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	int stuff;
Packit c32a2d
	struct handle *sh = (struct handle*)ao->userptr;
Packit c32a2d
	sfifo_t *fifo = &sh->fifo;
Packit c32a2d
Packit c32a2d
	debug1("close_sdl with %d", sfifo_used(fifo));
Packit c32a2d
	sh->finished = 1;
Packit c32a2d
	/* Wait at least until SDL emptied the FIFO. */
Packit c32a2d
	while((stuff = sfifo_used(fifo))>0)
Packit c32a2d
	{
Packit c32a2d
		int ms = stuff/ao->framesize*1000/ao->rate;
Packit c32a2d
		debug1("still stuff for about %i ms there", ms);
Packit c32a2d
		ms_sleep(ms/2);
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
	SDL_CloseAudio();
Packit c32a2d
	
Packit c32a2d
	/* Free up the memory used by the FIFO */
Packit c32a2d
	sfifo_close( fifo );
Packit c32a2d
	
Packit c32a2d
	return 0;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static void flush_sdl(out123_handle *ao)
Packit c32a2d
{
Packit c32a2d
	struct handle *sh = (struct handle*)ao->userptr;
Packit c32a2d
Packit c32a2d
	SDL_PauseAudio(1);
Packit c32a2d
	sfifo_flush(&sh->fifo);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* You can only rely on that being called after successful init_sdl()!
Packit c32a2d
   And sdl_close() should be called before to free the sfifo. */
Packit c32a2d
static int deinit_sdl(out123_handle* ao)
Packit c32a2d
{
Packit c32a2d
	/* Free up memory */
Packit c32a2d
	if (ao->userptr) {
Packit c32a2d
		free( ao->userptr );
Packit c32a2d
		ao->userptr = NULL;
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
	/* Shut down SDL */
Packit c32a2d
	SDL_Quit();
Packit c32a2d
Packit c32a2d
	/* Success */
Packit c32a2d
	return 0;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Remember: If this returns failure, no additional cleanup happens.
Packit c32a2d
   Resources must be freed here. */
Packit c32a2d
static int init_sdl(out123_handle* ao)
Packit c32a2d
{
Packit c32a2d
	struct handle *sh;
Packit c32a2d
Packit c32a2d
	if (ao==NULL) return -1;
Packit c32a2d
	
Packit c32a2d
	/* Set callbacks */
Packit c32a2d
	ao->open = open_sdl;
Packit c32a2d
	ao->flush = flush_sdl;
Packit c32a2d
	ao->write = write_sdl;
Packit c32a2d
	ao->get_formats = get_formats_sdl;
Packit c32a2d
	ao->close = close_sdl;
Packit c32a2d
	ao->deinit = deinit_sdl;
Packit c32a2d
Packit c32a2d
	/* Initialise SDL */
Packit c32a2d
	if (SDL_Init( SDL_INIT_AUDIO ) )
Packit c32a2d
	{
Packit c32a2d
		if(!AOQUIET)
Packit c32a2d
			error1("Failed to initialise SDL: %s\n", SDL_GetError());
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	/* Allocate memory _after_ checking that SDL is available, so we do not
Packit c32a2d
	   have to free after failure. */
Packit c32a2d
	ao->userptr = sh = malloc( sizeof(struct handle) );
Packit c32a2d
	if (ao->userptr==NULL)
Packit c32a2d
	{
Packit c32a2d
		if(!AOQUIET)
Packit c32a2d
			error( "Failed to allocated memory for FIFO structure" );
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	sh->finished = 0;
Packit c32a2d
	/* Not exactly necessary; only for somewhat safe sdl_close after a fake
Packit c32a2d
	   sdl_open(). */
Packit c32a2d
	memset( &sh->fifo, 0, sizeof(sfifo_t) );
Packit c32a2d
Packit c32a2d
	/* Success */
Packit c32a2d
	return 0;
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 */			"sdl",
Packit c32a2d
	/* description */	"Output audio using SDL (Simple DirectMedia Layer).",
Packit c32a2d
	/* revision */		"$Rev:$",
Packit c32a2d
	/* handle */		NULL,
Packit c32a2d
	
Packit c32a2d
	/* init_output */	init_sdl,
Packit c32a2d
};