You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdemultimedia/xine_artsplugin/audio_fifo_out.c

523 lines
13 KiB

/*
This file is part of KDE/aRts (Noatun) - xine integration
Copyright (C) 2002-2003 Ewald Snel <ewald@rambo.its.tudelft.nl>
Copyright (C) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <xine/audio_out.h>
#if (XINE_MAJOR_VERSION >= 1) && (XINE_MINOR_VERSION >= 2)
#include <xine/xine_internal.h>
#include <xine/xine_plugin.h>
#endif
#include "audio_fifo_out.h"
#define GAP_TOLERANCE 5000
// #define XINE_DEBUG 1
typedef struct fifo_driver_s {
ao_driver_t ao_driver;
xine_arts_audio *audio;
int capabilities;
int mode;
pthread_mutex_t read_mutex;
pthread_mutex_t write_mutex;
pthread_cond_t cond;
uint32_t bytes_per_frame;
uint8_t *fifo;
int fifo_size;
int fifo_read_ptr;
int fifo_write_ptr;
int fifo_flush;
int fifo_delay;
#if (XINE_MAJOR_VERSION >= 1) && (XINE_MINOR_VERSION >= 2)
config_values_t *config;
xine_t *xine;
#endif
} fifo_driver_t;
/*
* open the audio device for writing to
*/
static int ao_fifo_open( ao_driver_t *this_gen, uint32_t bits, uint32_t rate, int mode )
{
fifo_driver_t *ao = (fifo_driver_t *)this_gen;
if ((mode & ao->capabilities) == 0)
{
fprintf( stderr, "[xine_artsplugin audio_fifo_out] unsupported mode %08x\n", mode);
return 0;
}
/* lock read buffer */
pthread_mutex_lock( &ao->read_mutex );
ao->mode = mode;
ao->audio->sample_rate = rate;
ao->audio->bits_per_sample = bits;
switch (mode)
{
case AO_CAP_MODE_MONO:
ao->audio->num_channels = 1;
break;
case AO_CAP_MODE_STEREO:
ao->audio->num_channels = 2;
break;
}
ao->bytes_per_frame = (ao->audio->bits_per_sample * ao->audio->num_channels) / 8;
ao->fifo_size = ao->audio->sample_rate * ao->bytes_per_frame;
ao->fifo = malloc( 2*ao->fifo_size );
ao->fifo_read_ptr = 0;
ao->fifo_write_ptr = 0;
ao->fifo_flush = 0;
ao->fifo_delay = 0;
/* unlock and enable read buffer for aRts sound server */
pthread_mutex_unlock( &ao->read_mutex );
return ao->audio->sample_rate;
}
static int ao_fifo_num_channels( ao_driver_t *this_gen )
{
return ((fifo_driver_t *)this_gen)->audio->num_channels;
}
static int ao_fifo_bytes_per_frame( ao_driver_t *this_gen )
{
return ((fifo_driver_t *)this_gen)->bytes_per_frame;
}
static int ao_fifo_get_gap_tolerance( ao_driver_t *this_gen )
{
return GAP_TOLERANCE;
}
static int ao_fifo_bytes_in_buffer( fifo_driver_t *ao )
{
int bytes_in_buffer = (ao->fifo_write_ptr - ao->fifo_read_ptr);
if (bytes_in_buffer < 0)
{
bytes_in_buffer += ao->fifo_size;
}
return bytes_in_buffer;
}
static int ao_fifo_write( ao_driver_t *this_gen, int16_t *data, uint32_t num_frames )
{
fifo_driver_t *ao = (fifo_driver_t *)this_gen;
uint8_t *src = (uint8_t *)data;
int bytes_in_buffer, bytes_to_write, written;
bytes_to_write = (num_frames * ao->bytes_per_frame);
pthread_mutex_lock( &ao->write_mutex );
while (!ao->fifo_flush && bytes_to_write > 0)
{
bytes_in_buffer = ao_fifo_bytes_in_buffer( ao );
written = bytes_to_write;
if ((bytes_in_buffer + written) >= ao->fifo_size)
{
written = (ao->fifo_size - bytes_in_buffer - 1);
written -= (written % ao->bytes_per_frame);
if (written == 0)
{
struct timespec ts;
struct timeval tv;
int delay;
gettimeofday( &tv, 0 );
delay = ao_fifo_arts_delay();
delay += (1000 * num_frames) / ao->audio->sample_rate;
delay = (delay < 20) ? 20 : ((delay >= 250) ? 250 : delay + 1);
ts.tv_sec = tv.tv_sec + (delay / 1000);
ts.tv_nsec = (1000 * tv.tv_usec) + (1000000 * (delay % 1000));
if (ts.tv_nsec >= 1000000000)
{
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
if (pthread_cond_timedwait( &ao->cond, &ao->write_mutex, &ts ) != 0)
{
fprintf( stderr, "[xine_artsplugin audio_fifo_out] blocked for more than %d ms,\n", delay);
fprintf( stderr, "[xine_artsplugin audio_fifo_out] %d sample(s) discarded.\n", num_frames);
pthread_mutex_unlock( &ao->write_mutex );
return 0;
}
}
}
if (!ao->fifo_flush && written > 0)
{
int new_write_ptr = (ao->fifo_write_ptr + written);
if (new_write_ptr >= ao->fifo_size)
{
new_write_ptr -= ao->fifo_size;
memcpy( &ao->fifo[ao->fifo_write_ptr], src, (written - new_write_ptr) );
memcpy( ao->fifo, &src[written - new_write_ptr], new_write_ptr );
}
else
{
memcpy( &ao->fifo[ao->fifo_write_ptr], src, written );
}
/* update audio buffer pointer */
ao->fifo_write_ptr = new_write_ptr;
bytes_to_write -= written;
src += written;
}
}
/* audio has stopped */
ao->fifo_delay += bytes_to_write;
pthread_mutex_unlock( &ao->write_mutex );
return 1;
}
static int ao_fifo_delay( ao_driver_t *this_gen )
{
fifo_driver_t *ao = (fifo_driver_t *)this_gen;
return (ao_fifo_arts_delay() * ao->audio->sample_rate / 1000)
+ ((ao_fifo_bytes_in_buffer( ao ) + ao->fifo_delay) / ao->bytes_per_frame);
}
static void ao_fifo_close( ao_driver_t *this_gen )
{
fifo_driver_t *ao = (fifo_driver_t *)this_gen;
/* lock read buffer */
pthread_mutex_lock( &ao->read_mutex );
/* disable audio driver */
ao->fifo_flush = 2;
ao->fifo_delay = 0;
/* free audio FIFO */
if (ao->fifo)
{
free( ao->fifo );
ao->fifo = NULL;
}
pthread_mutex_unlock( &ao->read_mutex );
}
static uint32_t ao_fifo_get_capabilities( ao_driver_t *this_gen )
{
return ((fifo_driver_t *)this_gen)->capabilities;
}
static void ao_fifo_exit( ao_driver_t *this_gen )
{
fifo_driver_t *ao = (fifo_driver_t *)this_gen;
ao_fifo_close( this_gen );
pthread_cond_destroy( &ao->cond );
pthread_mutex_destroy( &ao->read_mutex );
pthread_mutex_destroy( &ao->write_mutex );
free( ao );
}
static int ao_fifo_get_property( ao_driver_t *this_gen, int property )
{
return 0;
}
static int ao_fifo_set_property( ao_driver_t *this_gen, int property, int value )
{
return ~value;
}
static int ao_fifo_control( ao_driver_t *this_gen, int cmd, ... )
{
fifo_driver_t *ao = (fifo_driver_t *)this_gen;
switch (cmd)
{
case AO_CTRL_FLUSH_BUFFERS:
case AO_CTRL_PLAY_PAUSE:
/* flush audio FIFO */
pthread_mutex_lock( &ao->read_mutex );
ao->fifo_read_ptr = ao->fifo_write_ptr;
if (ao->fifo_flush == 1)
{
ao->fifo_flush = 0;
ao->fifo_delay = 0;
}
pthread_mutex_unlock( &ao->read_mutex );
break;
case AO_CTRL_PLAY_RESUME:
break;
}
return 0;
}
#if (XINE_MAJOR_VERSION >= 1) && (XINE_MINOR_VERSION >= 2)
static fifo_driver_t * _ao_driver = NULL;
typedef struct fifo_class_s {
audio_driver_class_t driver_class;
config_values_t *config;
xine_t *xine;
} fifo_class_t;
static void _arts_class_dispose(audio_driver_class_t *driver_class) {
fifo_class_t *cl;
cl = (fifo_class_t *)driver_class;
free(cl);
}
static char *_arts_class_identifier_get(video_driver_class_t *driver_class) {
return "arts";
}
static char *_arts_class_description_get(video_driver_class_t *driver_class) {
return "aRts xine video output plugin";
}
static ao_driver_t * _arts_open(audio_driver_class_t *driver_class, const void *data) {
fifo_class_t *cl;
cl = (fifo_class_t *)driver_class;
_ao_driver = (fifo_driver_t *)malloc(sizeof(fifo_driver_t));
if (!_ao_driver) return NULL;
_ao_driver->config = cl->config;
_ao_driver->xine = cl->xine;
_ao_driver->audio = data;
_ao_driver->fifo = NULL;
_ao_driver->fifo_read_ptr = 0;
_ao_driver->fifo_write_ptr = 0;
_ao_driver->fifo_flush = 2;
_ao_driver->fifo_delay = 0;
_ao_driver->capabilities = (AO_CAP_MODE_MONO | AO_CAP_MODE_STEREO);
_ao_driver->ao_driver.get_capabilities = ao_fifo_get_capabilities;
_ao_driver->ao_driver.get_property = ao_fifo_get_property;
_ao_driver->ao_driver.set_property = ao_fifo_set_property;
_ao_driver->ao_driver.open = ao_fifo_open;
_ao_driver->ao_driver.num_channels = ao_fifo_num_channels;
_ao_driver->ao_driver.bytes_per_frame = ao_fifo_bytes_per_frame;
_ao_driver->ao_driver.delay = ao_fifo_delay;
_ao_driver->ao_driver.write = ao_fifo_write;
_ao_driver->ao_driver.close = ao_fifo_close;
_ao_driver->ao_driver.exit = ao_fifo_exit;
_ao_driver->ao_driver.get_gap_tolerance = ao_fifo_get_gap_tolerance;
_ao_driver->ao_driver.control = ao_fifo_control;
pthread_cond_init( &_ao_driver->cond, NULL );
pthread_mutex_init( &_ao_driver->read_mutex, NULL );
pthread_mutex_init( &_ao_driver->write_mutex, NULL );
return &_ao_driver->ao_driver;
}
static void *_arts_plugin_class_init(xine_t *xine, const void *data) {
fifo_class_t *cl;
cl = (fifo_class_t *) malloc(sizeof(fifo_class_t));
if (!cl) return NULL;
cl->driver_class.open_plugin = _arts_open;
cl->driver_class.identifier = _arts_class_identifier_get(NULL);
cl->driver_class.description = _arts_class_description_get(NULL);
cl->driver_class.dispose = _arts_class_dispose;
cl->config = xine->config;
cl->xine = xine;
return cl;
}
static ao_info_t _arts_info =
{
1 /* priority */
};
plugin_info_t arts_xine_plugin_info[] =
{
{ PLUGIN_AUDIO_OUT, AUDIO_OUT_IFACE_VERSION, "arts", XINE_VERSION_CODE, &_arts_info, &_arts_plugin_class_init },
{ PLUGIN_NONE, 0, "", 0, NULL, NULL }
};
#endif
xine_audio_port_t *init_audio_out_plugin( xine_t *xine, xine_arts_audio *audio,
void **ao_driver )
{
#ifdef XINE_DEBUG
xine->verbosity = 1;
#endif
#if (XINE_MAJOR_VERSION >= 1) && (XINE_MINOR_VERSION >= 2)
xine_audio_port_t *ret;
xine_register_plugins(xine, arts_xine_plugin_info);
ret = xine_open_audio_driver( xine, "arts", audio );
if (ret) {
*ao_driver = (void *)_ao_driver;
}
return ret;
#else
fifo_driver_t *ao = (fifo_driver_t *)malloc( sizeof(fifo_driver_t) );
ao->audio = audio;
ao->fifo = NULL;
ao->fifo_read_ptr = 0;
ao->fifo_write_ptr = 0;
ao->fifo_flush = 2;
ao->fifo_delay = 0;
ao->capabilities = (AO_CAP_MODE_MONO | AO_CAP_MODE_STEREO);
ao->ao_driver.get_capabilities = ao_fifo_get_capabilities;
ao->ao_driver.get_property = ao_fifo_get_property;
ao->ao_driver.set_property = ao_fifo_set_property;
ao->ao_driver.open = ao_fifo_open;
ao->ao_driver.num_channels = ao_fifo_num_channels;
ao->ao_driver.bytes_per_frame = ao_fifo_bytes_per_frame;
ao->ao_driver.delay = ao_fifo_delay;
ao->ao_driver.write = ao_fifo_write;
ao->ao_driver.close = ao_fifo_close;
ao->ao_driver.exit = ao_fifo_exit;
ao->ao_driver.get_gap_tolerance = ao_fifo_get_gap_tolerance;
ao->ao_driver.control = ao_fifo_control;
pthread_cond_init( &ao->cond, NULL );
pthread_mutex_init( &ao->read_mutex, NULL );
pthread_mutex_init( &ao->write_mutex, NULL );
*ao_driver = (void *)ao;
return ao_new_port( xine, (ao_driver_t *)ao, 0 );
#endif
}
unsigned long ao_fifo_read( void *ao_driver, unsigned char **buffer,
unsigned long samples )
{
fifo_driver_t *ao = (fifo_driver_t *)ao_driver;
int bytes_in_buffer, bytes_to_read;
/* lock read buffer */
pthread_mutex_lock( &ao->read_mutex );
bytes_in_buffer = ao_fifo_bytes_in_buffer( ao );
bytes_to_read = (samples * ao->bytes_per_frame);
if (ao->fifo_flush || bytes_in_buffer == 0)
{
/* unlock read buffer */
pthread_mutex_unlock( &ao->read_mutex );
/* signal blocked writes */
pthread_mutex_lock( &ao->write_mutex );
pthread_cond_signal( &ao->cond );
pthread_mutex_unlock( &ao->write_mutex );
/* audio FIFO empty or disabled, return */
return 0;
}
if (bytes_to_read > bytes_in_buffer)
{
fprintf( stderr, "[xine_artsplugin audio_fifo_out] audio buffer underflow!\n" );
bytes_to_read = bytes_in_buffer - (bytes_in_buffer % ao->bytes_per_frame);
}
if ((ao->fifo_read_ptr + bytes_to_read) > ao->fifo_size)
{
/* copy samples from front to end of buffer */
memcpy( &ao->fifo[ao->fifo_size], ao->fifo,
((ao->fifo_read_ptr + bytes_to_read) - ao->fifo_size) );
}
/* return pointer to audio samples */
*buffer = &ao->fifo[ao->fifo_read_ptr];
return bytes_to_read;
}
void ao_fifo_flush( void *ao_driver, unsigned long samples )
{
fifo_driver_t *ao = (fifo_driver_t *)ao_driver;
int bytes_in_buffer, bytes_to_flush;
/* flush audio data */
bytes_in_buffer = ao_fifo_bytes_in_buffer( ao );
bytes_to_flush = (samples * ao->bytes_per_frame);
if (bytes_to_flush <= bytes_in_buffer)
{
int new_read_ptr = (ao->fifo_read_ptr + bytes_to_flush);
if (new_read_ptr >= ao->fifo_size)
{
new_read_ptr -= ao->fifo_size;
}
ao->fifo_read_ptr = new_read_ptr;
}
/* unlock read buffer */
pthread_mutex_unlock( &ao->read_mutex );
/* signal blocked writes */
pthread_mutex_lock( &ao->write_mutex );
pthread_cond_signal( &ao->cond );
pthread_mutex_unlock( &ao->write_mutex );
}
void ao_fifo_clear( void *ao_driver, int clear )
{
fifo_driver_t *ao = (fifo_driver_t *)ao_driver;
pthread_mutex_lock( &ao->write_mutex );
/* enable/disable audio driver */
ao->fifo_flush = clear;
ao->fifo_delay = 0;
if (clear)
{
/* signal blocked writes */
pthread_cond_signal( &ao->cond );
}
pthread_mutex_unlock( &ao->write_mutex );
}