|
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 |
};
|