/* This file is part of KDE/aRts (Noatun) - xine integration Copyright (C) 2002-2003 Ewald Snel Copyright (C) 2014 Timothy Pearson 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 #endif #include #include #include #include #include #include #include #include #if (XINE_MAJOR_VERSION >= 1) && (XINE_MINOR_VERSION >= 2) #include #include #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, 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 ); }