Mercurial > audlegacy
diff Plugins/Output/crossfade/crossfade.c @ 630:5b81b0f310e5 trunk
[svn] Update codingstyle to be more consistent with last upstream release. Stop exploding at shutdown.
author | chainsaw |
---|---|
date | Sat, 11 Feb 2006 08:25:14 -0800 |
parents | 55dc40ff1aff |
children |
line wrap: on
line diff
--- a/Plugins/Output/crossfade/crossfade.c Wed Feb 08 16:08:04 2006 -0800 +++ b/Plugins/Output/crossfade/crossfade.c Sat Feb 11 08:25:14 2006 -0800 @@ -1,6 +1,6 @@ /* * XMMS Crossfade Plugin - * Copyright (C) 2000-2004 Peter Eisenlohr <peter@eisenlohr.org> + * Copyright (C) 2000-2005 Peter Eisenlohr <peter@eisenlohr.org> * * based on the original OSS Output Plugin * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies @@ -55,89 +55,81 @@ #undef DEBUG_HARDCORE /* output plugin callback prototypes */ -static void xfade_init (); -static void xfade_set_volume (int l, int r); -static void xfade_get_volume (int *l, int *r); -static gint xfade_open_audio (AFormat fmt, int rate, int nch); -static void xfade_write_audio (void *ptr, int length); -static void xfade_close_audio (); -static void xfade_flush (int time); -static void xfade_pause (short paused); -static gint xfade_buffer_free (); +static void xfade_init(); +static void xfade_cleanup(); +static void xfade_set_volume(int l, int r); +static void xfade_get_volume(int *l, int *r); +static gint xfade_open_audio(AFormat fmt, int rate, int nch); +static void xfade_write_audio(void *ptr, int length); +static void xfade_close_audio(); +static void xfade_flush(int time); +static void xfade_pause(short paused); +static gint xfade_buffer_free(); static gint xfade_buffer_playing(); -static gint xfade_written_time (); -static gint xfade_output_time (); +static gint xfade_written_time(); +static gint xfade_output_time(); /* output plugin callback table */ -static OutputPlugin xfade_op = -{ - NULL, - NULL, - "Crossfade Plugin " VERSION, - xfade_init, - NULL, - xfade_about, - xfade_configure, - xfade_get_volume, - xfade_set_volume, - xfade_open_audio, - xfade_write_audio, - xfade_close_audio, - xfade_flush, - xfade_pause, - xfade_buffer_free, - xfade_buffer_playing, - xfade_output_time, - xfade_written_time, - NULL /* we do not support effects on crossfade, too fragile */ +static OutputPlugin xfade_op = { + NULL, + NULL, + "Crossfade Plugin " VERSION, + xfade_init, + xfade_cleanup, + xfade_about, + xfade_configure, + xfade_get_volume, + xfade_set_volume, + xfade_open_audio, + xfade_write_audio, + xfade_close_audio, + xfade_flush, + xfade_pause, + xfade_buffer_free, + xfade_buffer_playing, + xfade_output_time, + xfade_written_time, + NULL /* we do not support effects on crossfade, too fragile */ }; /* internal prototypes */ -static void load_symbols (); -static void output_list_hack(); -static gint open_output (); -static void buffer_reset (buffer_t *buf, config_t *cfg); -static void *buffer_thread_f (void *arg); -static void sync_output (); +static void output_list_hack(); +static gint open_output(); +static void buffer_reset(buffer_t * buf, config_t * cfg); +static void *buffer_thread_f(void *arg); +static void sync_output(); -/* special XMMS symbols (dynamically looked up, see xfade_init) */ -static gboolean *xmms_playlist_get_info_going = NULL; /* XMMS */ -static gboolean *xmms_is_quitting = NULL; /* XMMS */ -static gboolean (*input_stopped_for_restart)() = NULL; /* XMMS */ - -void fini() __attribute__((destructor)); +void fini() __attribute__ ((destructor)); /* local variables */ -static gint session_id; -static gboolean realtime; -static gboolean is_http; +static gint session_id; +static gboolean realtime; +static gboolean is_http; -static gint64 streampos; /* position within current song (input bps) */ -static gboolean playing; - gboolean opened; /* TRUE between open_ and close_audio() */ -static gboolean paused; /* TRUE: no playback (but still filling buffer) */ -static gboolean stopped; /* TRUE: stop buffer thread ASAP */ -static gboolean eop; /* TRUE: wait until buffer is empty then sync() */ +static gint64 streampos; /* position within current song (input bps) */ +static gboolean playing; +gboolean opened; /* TRUE between open_audio and close_audio() */ +static gboolean paused; /* TRUE: no playback (but still filling buffer) */ +static gboolean stopped; /* TRUE: stop buffer thread ASAP */ +static gboolean eop; /* TRUE: wait until buffer is empty then sync() */ -static plugin_config_t the_op_config = DEFAULT_OP_CONFIG; - OutputPlugin *the_op = NULL; - gint the_rate = 44100; +static plugin_config_t the_op_config = DEFAULT_OP_CONFIG; +OutputPlugin *the_op = NULL; +gint the_rate = 44100; static gboolean input_playing = FALSE; - gboolean output_opened = FALSE; -static gint output_flush_time = 0; - gint output_offset = 0; -static gint64 output_written = 0; - gint64 output_streampos = 0; - -static gboolean finishing = FALSE; /* TRUE after fini() has been called */ +gboolean output_opened = FALSE; +static gint output_flush_time = 0; +gint output_offset = 0; +static gint64 output_written = 0; +gint64 output_streampos = 0; static gchar zero_4k[4096]; #ifdef VOLUME_NORMALIZER static quantaudio_t qa, last_qa; -static gboolean have_qa = FALSE, have_last_qa = FALSE; +static gboolean have_qa = FALSE, have_last_qa = FALSE; #endif @@ -185,2060 +177,2147 @@ static format_t in_format; static format_t out_format; -static buffer_t the_buffer; - buffer_t *buffer = &the_buffer; +static buffer_t the_buffer; +buffer_t *buffer = &the_buffer; GStaticMutex buffer_mutex = G_STATIC_MUTEX_INIT; -static GThread *buffer_thread; +static GThread *buffer_thread; -static effect_context_t effect_context; -static convert_context_t convert_context; -static rate_context_t rate_context; -static volume_context_t volume_context; +static effect_context_t effect_context; +static convert_context_t convert_context; +static rate_context_t rate_context; +static volume_context_t volume_context; -static config_t the_config; - config_t *config = &the_config; - config_t config_default = CONFIG_DEFAULT; +static config_t the_config; +config_t *config = &the_config; +config_t config_default = CONFIG_DEFAULT; static fade_config_t *fade_config = NULL; /* this is the entry point for XMMS */ -OutputPlugin *get_oplugin_info() +OutputPlugin * +get_oplugin_info() { - return &xfade_op; + return &xfade_op; } -OutputPlugin *get_crossfade_oplugin_info() +OutputPlugin * +get_crossfade_oplugin_info() { - return &xfade_op; + return &xfade_op; } static gint effect_list_f(gconstpointer a, gconstpointer b) { - EffectPlugin *ep = (EffectPlugin *)a; - gchar *name = (gchar *)b; + EffectPlugin *ep = (EffectPlugin *) a; + gchar *name = (gchar *) b; - return strcmp(g_basename(ep->filename), name); + return strcmp(g_basename(ep->filename), name); } static gboolean open_output_f(gpointer data) { - DEBUG(("[crossfade] open_output_f: pid=%d\n", getpid())); - open_output(); - return FALSE; /* FALSE = 'do not call me again' */ + DEBUG(("[crossfade] open_output_f: pid=%d\n", getpid())); + open_output(); + return FALSE; /* FALSE = 'do not call me again' */ } void xfade_realize_ep_config() { - GList *list, *element; - EffectPlugin *ep = NULL; + GList *list, *element; + EffectPlugin *ep = NULL; - /* find effect plugin */ - if(config->ep_enable && config->ep_name && (list = get_effect_list())) /* XMMS */ - if((element = g_list_find_custom(list, config->ep_name, effect_list_f))) - ep = element->data; + /* find effect plugin */ + if (config->ep_enable && config->ep_name && (list = get_effect_list())) /* XMMS */ + if ((element = g_list_find_custom(list, config->ep_name, effect_list_f))) + ep = element->data; - effect_set_plugin(&effect_context, ep); + effect_set_plugin(&effect_context, ep); } void -xfade_realize_config() /* also called by xfade_init() */ +xfade_realize_config() /* also called by xfade_init() */ { - /* realize effect plugin config */ - xfade_realize_ep_config(); + /* realize effect plugin config */ + xfade_realize_ep_config(); #ifdef VOLUME_NORMALIZER - /* update volume normalizer target rms */ - volume_set_target_rms(&volume_context, config->volnorm_target); + /* update volume normalizer target rms */ + volume_set_target_rms(&volume_context, config->volnorm_target); #endif - /* 0.3.0: keep device opened */ - if(config->output_keep_opened && !output_opened) { - DEBUG(("[crossfade] realize_config: keeping output opened...\n")); + /* 0.3.0: keep device opened */ + if (config->output_keep_opened && !output_opened) + { + DEBUG(("[crossfade] realize_config: keeping output opened...\n")); - /* HACK: avoid rate converter complaing about illegal input rate */ - /* if(!rate_context.valid || !rate_context.in_rate) - rate_config(&rate_context, out_format.rate, out_format.rate); */ + /* HACK: avoid rate converter complaing about illegal input rate */ + /* if (!rate_context.valid || !rate_context.in_rate) + rate_config(&rate_context, out_format.rate, out_format.rate); */ - /* 0.3.1: HACK: this will make sure that we start playing silence after startup */ - gettimeofday(&last_close, NULL); + /* 0.3.1: HACK: this will make sure that we start playing silence after startup */ + gettimeofday(&last_close, NULL); - /* 0.3.1: HACK: Somehow, if we open output here at XMMS startup, there - will be leftover filedescriptors later when closing output again. - Opening output in a timeout function seems to work around this... */ - DEBUG(("[crossfade] realize_config: adding timeout (pid=%d)\n", getpid())); - g_timeout_add(0, open_output_f, NULL); - } + /* 0.3.1: HACK: Somehow, if we open output here at XMMS startup, there + will be leftover filedescriptors later when closing output again. + Opening output in a timeout function seems to work around this... */ + DEBUG(("[crossfade] realize_config: adding timeout (pid=%d)\n", getpid())); + g_timeout_add(0, open_output_f, NULL); + } } static gint output_list_f(gconstpointer a, gconstpointer b) { - OutputPlugin *op = (OutputPlugin *)a; - gchar *name = (gchar *)b; + OutputPlugin *op = (OutputPlugin *) a; + gchar *name = (gchar *) b; - return strcmp(g_basename(op->filename), name); + return strcmp(g_basename(op->filename), name); } static OutputPlugin * find_output() { - GList *list, *element; - OutputPlugin *op = NULL; + GList *list, *element; + OutputPlugin *op = NULL; + + /* find output plugin */ + if (config->op_name && (list = get_output_list())) /* XMMS */ + if ((element = g_list_find_custom(list, config->op_name, output_list_f))) + op = element->data; - /* find output plugin */ - if(config->op_name && (list = get_output_list())) /* XMMS */ - if((element = g_list_find_custom(list, config->op_name, output_list_f))) - op = element->data; - - if(op == &xfade_op) { - DEBUG(("[crossfade] find_output: can't use myself as output plugin!\n")); - op = NULL; - } - else if(!op) { - DEBUG(("[crossfade] find_output: could not find output plugin \"%s\"\n", - config->op_name ? config->op_name : "#NULL#")); - } - else /* ok, we have a plugin. last, get its compatibility options */ - xfade_load_plugin_config(config->op_config_string, config->op_name, - &the_op_config); + if (op == &xfade_op) + { + DEBUG(("[crossfade] find_output: can't use myself as output plugin!\n")); + op = NULL; + } + else if (!op) + { + DEBUG(("[crossfade] find_output: could not find output plugin \"%s\"\n", + config->op_name ? config->op_name : "#NULL#")); + } + else /* ok, we have a plugin. last, get its compatibility options */ + xfade_load_plugin_config(config->op_config_string, config->op_name, &the_op_config); - return op; + return op; } static gint open_output() { - /* sanity check */ - if(output_opened) - DEBUG(("[crossfade] open_output: WARNING: output_opened=TRUE!\n")); + /* sanity check */ + if (output_opened) + DEBUG(("[crossfade] open_output: WARNING: output_opened=TRUE!\n")); - /* reset output_* */ - output_opened = FALSE; - output_flush_time = 0; - output_offset = 0; - output_written = 0; - output_streampos = 0; + /* reset output_* */ + output_opened = FALSE; + output_flush_time = 0; + output_offset = 0; + output_written = 0; + output_streampos = 0; - /* get output plugin (this will also init the_op_config) */ - if(!(the_op = find_output())) { - DEBUG(("[crossfade] open_output: could not find any output!\n")); - return -1; - } + /* get output plugin (this will also init the_op_config) */ + if (!(the_op = find_output())) + { + DEBUG(("[crossfade] open_output: could not find any output!\n")); + return -1; + } - /* print output plugin info */ - DEBUG(("[crossfade] open_output: using \"%s\" for output", - the_op->description ? the_op->description : "#NULL#")); + /* print output plugin info */ + DEBUG(("[crossfade] open_output: using \"%s\" for output", the_op->description ? the_op->description : "#NULL#")); - if(realtime) - DEBUG((" (RT)")); + if (realtime) + DEBUG((" (RT)")); - if(the_op_config.throttle_enable) - DEBUG((realtime - ? " (throttled (disabled with RT))" - : " (throttled)")); + if (the_op_config.throttle_enable) + DEBUG((realtime ? " (throttled (disabled with RT))" : " (throttled)")); - if(the_op_config.max_write_enable) - DEBUG((" (max_write=%d)", the_op_config.max_write_len)); + if (the_op_config.max_write_enable) + DEBUG((" (max_write=%d)", the_op_config.max_write_len)); + + DEBUG(("\n")); - DEBUG(("\n")); + /* setup sample rate (note that OUTPUT_RATE is #defined as the_rate) */ + the_rate = config->output_rate; + + /* setup out_format. use host byte order for easy math */ + setup_format(FMT_S16_NE, OUTPUT_RATE, OUTPUT_NCH, &out_format); - /* setup sample rate (note that OUTPUT_RATE is #defined as the_rate) */ - the_rate = config->output_rate; - - /* setup out_format. use host byte order for easy math */ - setup_format(FMT_S16_NE, OUTPUT_RATE, OUTPUT_NCH, &out_format); - - /* force re-config of rate converter */ - if(in_format.rate == 0) /* this happens with keep_output_opened */ - rate_config(&rate_context, out_format.rate, out_format.rate, config->output_quality); - else - rate_config(&rate_context, in_format.rate, out_format.rate, config->output_quality); + /* force re-config of rate converter */ + if (in_format.rate == 0) /* this happens with keep_output_opened */ + rate_config(&rate_context, out_format.rate, out_format.rate, config->output_quality); + else + rate_config(&rate_context, in_format.rate, out_format.rate, config->output_quality); - /* open plugin */ - if(!the_op->open_audio(out_format.fmt, out_format.rate, out_format.nch)) { - DEBUG(("[crossfade] open_output: open_audio() failed!\n")); - the_op = NULL; - return -1; - } + /* open plugin */ + if (!the_op->open_audio(out_format.fmt, out_format.rate, out_format.nch)) + { + DEBUG(("[crossfade] open_output: open_audio() failed!\n")); + the_op = NULL; + return -1; + } + + /* clear buffer struct */ + memset(buffer, 0, sizeof(*buffer)); - /* clear buffer struct */ - memset(buffer, 0, sizeof(*buffer)); - - /* calculate buffer size */ - buffer->mix_size = MS2B(xfade_mix_size_ms(config)) & -4; - buffer->sync_size = MS2B(config->sync_size_ms) & -4; - buffer->preload_size = MS2B(config->preload_size_ms) & -4; + /* calculate buffer size */ + buffer->mix_size = MS2B(xfade_mix_size_ms(config)) & -4; + buffer->sync_size = MS2B(config->sync_size_ms) & -4; + buffer->preload_size = MS2B(config->preload_size_ms) & -4; - buffer->size = (buffer->mix_size + /* mixing area */ - buffer->sync_size + /* additional sync */ - buffer->preload_size); /* preload */ + buffer->size = (buffer->mix_size + /* mixing area */ + buffer->sync_size + /* additional sync */ + buffer->preload_size); /* preload */ - DEBUG(("[crossfade] open_output: buffer: size=%d (%d+%d+%d=%d ms) (%d Hz)\n", - buffer->size, - B2MS(buffer->mix_size), - B2MS(buffer->preload_size), - B2MS(buffer->sync_size), - B2MS(buffer->size), - the_rate)); + DEBUG(("[crossfade] open_output: buffer: size=%d (%d+%d+%d=%d ms) (%d Hz)\n", + buffer->size, + B2MS(buffer->mix_size), B2MS(buffer->preload_size), B2MS(buffer->sync_size), B2MS(buffer->size), the_rate)); - /* allocate buffer */ - if(!(buffer->data = g_malloc0(buffer->size))) { - DEBUG(("[crossfade] open_output: error allocating buffer!\n")); - the_op->close_audio(); - the_op = NULL; - return -1; - } - - /* reset buffer */ - buffer_reset(buffer, config); - - /* make sure stopped is TRUE -- otherwise the buffer thread would - * stop again immediatelly after it has been started. */ - stopped = FALSE; - - /* create and run buffer thread */ - buffer_thread = g_thread_create((GThreadFunc)buffer_thread_f, NULL, FALSE, NULL); - if (buffer_thread == NULL) { - PERROR("[crossfade] open_output: g_thread_create()"); - g_free(buffer->data); - the_op->close_audio(); - the_op = NULL; - return -1; - } + /* allocate buffer */ + if (!(buffer->data = g_malloc0(buffer->size))) + { + DEBUG(("[crossfade] open_output: error allocating buffer!\n")); + the_op->close_audio(); + the_op = NULL; + return -1; + } + + /* reset buffer */ + buffer_reset(buffer, config); + + /* make sure stopped is TRUE -- otherwise the buffer thread would + * stop again immediatelly after it has been started. */ + stopped = FALSE; - /* start updating monitor */ - xfade_start_monitor(); + /* create and run buffer thread */ + buffer_thread = g_thread_create((GThreadFunc) buffer_thread_f, NULL, TRUE, NULL); + if (buffer_thread == NULL) + { + PERROR("[crossfade] open_output: g_thread_create()"); + g_free(buffer->data); + the_op->close_audio(); + the_op = NULL; + return -1; + } - /* done */ - output_opened = TRUE; - return 0; + /* start updating monitor */ + xfade_start_monitor(); + + /* done */ + output_opened = TRUE; + return 0; } static void xfade_init() { - /* load config */ - memset(config, 0, sizeof(*config)); - *config = config_default; - xfade_load_config(); - - /* set default strings if there is no existing config */ - if(!config->op_config_string) - config->op_config_string = g_strdup(DEFAULT_OP_CONFIG_STRING); - if(!config->op_name) - config->op_name = g_strdup(DEFAULT_OP_NAME); - - /* check for realtime priority, it needs some special attention */ - realtime = xmms_check_realtime_priority(); - - /* show monitor win if enabled in config */ - xfade_check_monitor_win(); + /* load config */ + memset(config, 0, sizeof(*config)); + *config = config_default; + xfade_load_config(); - /* init contexts */ - effect_init(&effect_context, NULL); - convert_init(&convert_context); - rate_init(&rate_context); - volume_init(&volume_context); + /* set default strings if there is no existing config */ + if (!config->op_config_string) + config->op_config_string = g_strdup(DEFAULT_OP_CONFIG_STRING); + if (!config->op_name) + config->op_name = g_strdup(DEFAULT_OP_NAME); - /* reset */ - stopped = FALSE; + /* check for realtime priority, it needs some special attention */ + realtime = xmms_check_realtime_priority(); - /* find current output plugin early so that volume control works - * even if playback has not started yet. */ - if(!(the_op = find_output())) - DEBUG(("[crossfade] init: could not find any output!\n")); - - /* get xmms controlsocket session id */ - session_id = ctrlsocket_get_session_id(); + /* show monitor win if enabled in config */ + xfade_check_monitor_win(); - /* load any dynamic linked symbols */ - load_symbols(); - - /* HACK: make sure we are at the beginning of XMMS' output plugin list */ - output_list_hack(); + /* init contexts */ + effect_init(&effect_context, NULL); + convert_init(&convert_context); + rate_init(&rate_context); + volume_init(&volume_context); - /* realize config -- will also setup the pre-mixing effect plugin */ - xfade_realize_config(); -} - -static void load_symbols() -{ -#ifdef HAVE_DLFCN_H - void *handle; - char *error; + /* reset */ + stopped = FALSE; - /* open ourselves (that is, the XMMS binary) */ - handle = dlopen(NULL, RTLD_NOW); - if(!handle) { - DEBUG(("[crossfade] init: dlopen(NULL) failed!\n")); - return; - } + /* find current output plugin early so that volume control works + * even if playback has not started yet. */ + if (!(the_op = find_output())) + DEBUG(("[crossfade] init: could not find any output!\n")); - /* check for XMMS patches */ - DEBUG(("[crossfade] load_symbols: input_stopped_for_restart:")); - input_stopped_for_restart = dlsym(handle, "input_stopped_for_restart"); - DEBUG((!(error = dlerror()) ? " found\n" : " not found\n")); + /* get xmms controlsocket session id */ + session_id = ctrlsocket_get_session_id(); - DEBUG(("[crossfade] load_symbols: playlist_get_info_going:")); - xmms_playlist_get_info_going = dlsym(handle, "playlist_get_info_going"); - DEBUG((!(error = dlerror()) ? " found\n" : " not found\n")); - - DEBUG(("[crossfade] load_symbols: is_quitting:")); - xmms_is_quitting = dlsym(handle, "is_quitting"); - DEBUG((!(error = dlerror()) ? " found\n" : " not found\n")); + /* HACK: make sure we are at the beginning of XMMS' output plugin list */ + output_list_hack(); - dlclose(handle); -#endif + /* realize config -- will also setup the pre-mixing effect plugin */ + xfade_realize_config(); } /* - HACK: Try to move ourselves to the beginning of XMMS output plugin list, - so that we will be freed first when XMMS is quitting. This way, we - avoid the segfault when using ALSA as the output plugin. + HACK: Try to move ourselves to the beginning of XMMS output plugin list, + so that we will be freed first when XMMS is quitting. This way, we + avoid the segfault when using ALSA as the output plugin. */ -static void output_list_hack() +static void +output_list_hack() { - GList *output_list = get_output_list(); + GList *output_list = get_output_list(); - int i0 = g_list_index(output_list, &xfade_op), i1; + int i0 = g_list_index(output_list, &xfade_op), i1; - GList *first = g_list_first(output_list); - GList *xfade = g_list_find (output_list, &xfade_op); - xfade->data = first->data; - first->data = &xfade_op; + GList *first = g_list_first(output_list); + GList *xfade = g_list_find(output_list, &xfade_op); + xfade->data = first->data; + first->data = &xfade_op; - i1 = g_list_index(output_list, &xfade_op); - if (i0 != i1) - DEBUG(("[crossfade] output_list_hack: crossfade moved from index %d to %d\n", i0, i1)); + i1 = g_list_index(output_list, &xfade_op); + if (i0 != i1) + DEBUG(("[crossfade] output_list_hack: crossfade moved from index %d to %d\n", i0, i1)); } -void fini() +void +fini() { - DEBUG(("[crossfade]\n")); - DEBUG(("[crossfade] fini: cleanup:\n")); - - /* wait for buffer_thread to stop */ - g_static_mutex_lock(&buffer_mutex); - - /* reset last_close so that the buffer thread will - * not unnecessarily wait for a timeout. */ - memset(&last_close, 0, sizeof(last_close)); + DEBUG(("[crossfade]\n")); + DEBUG(("[crossfade] fini: cleanup:\n")); - /* HACK: tell buffer thread not to close output by itself */ - finishing = TRUE; - paused = FALSE; + /* wait for buffer_thread to stop */ + g_static_mutex_lock(&buffer_mutex); - /* wait for buffer thread to clean up and terminate */ - DEBUG(("[crossfade] fini: cleanup: waiting for buffer thread...\n")); - while(output_opened && finishing) { - g_static_mutex_unlock(&buffer_mutex); - xmms_usleep(10000); - g_static_mutex_lock(&buffer_mutex); - } - DEBUG(("[crossfade] fini: cleanup: waiting for buffer thread... done\n")); + /* reset last_close so that the buffer thread will + * not unnecessarily wait for a timeout. */ + memset(&last_close, 0, sizeof(last_close)); + + /* make sure we are not paused */ + paused = FALSE; - /* HACK: 0.3.5: close output plugin from THIS thread */ -#if 0 - if(output_opened) { - DEBUG(("[crossfade] fini: cleanup: closing audio...\n")); - if(the_op->close_audio) the_op->close_audio(); - DEBUG(("[crossfade] fini: cleanup: closing audio... done\n")); - g_free(buffer->data); - output_opened = FALSE; - } -#endif - DEBUG(("[crossfade] fini: cleanup: done\n")); + /* wait for buffer thread to clean up and terminate */ + DEBUG(("[crossfade] fini: cleanup: waiting for buffer thread...\n")); + while (output_opened) + { + g_static_mutex_unlock(&buffer_mutex); + xmms_usleep(10000); + g_static_mutex_lock(&buffer_mutex); + } + DEBUG(("[crossfade] fini: cleanup: waiting for buffer thread... done\n")); - g_static_mutex_unlock (&buffer_mutex); + g_static_mutex_unlock(&buffer_mutex); - /* free contexts */ - volume_free(&volume_context); - rate_free(&rate_context); - convert_free(&convert_context); - effect_free(&effect_context); + /* free contexts */ + volume_free(&volume_context); + rate_free(&rate_context); + convert_free(&convert_context); + effect_free(&effect_context); + + /* save configuration (required only for mixer_vol_*) */ + xfade_save_config(); - /* save configuration (required only for mixer_vol_*) */ - xfade_save_config(); + /* free resources */ + if (config->op_name) + g_free(config->op_name); + xfade_free_config(); + if (last_filename) + g_free(last_filename); - /* free resources */ - if(config->op_name) g_free(config->op_name); - xfade_free_config(); - if(last_filename) g_free(last_filename); - - DEBUG(("[crossfade] fini: done.\n")); + DEBUG(("[crossfade] fini: done.\n")); } void xfade_get_volume(int *l, int *r) { - if(config->mixer_software) { - if(config->mixer_reverse) { - *l = config->mixer_vol_right; - *r = config->mixer_vol_left; - } else { - *l = config->mixer_vol_left; - *r = config->mixer_vol_right; - } - } - else { - if(the_op && the_op->get_volume) { - if(config->mixer_reverse) - the_op->get_volume(r, l); - else - the_op->get_volume(l, r); - } - } + if (config->mixer_software) + { + if (config->mixer_reverse) + { + *l = config->mixer_vol_right; + *r = config->mixer_vol_left; + } + else + { + *l = config->mixer_vol_left; + *r = config->mixer_vol_right; + } + } + else + { + if (the_op && the_op->get_volume) + { + if (config->mixer_reverse) + the_op->get_volume(r, l); + else + the_op->get_volume(l, r); + } + } } void xfade_set_volume(int l, int r) { - if(!config->enable_mixer) return; - if(config->mixer_software) { - if(config->mixer_reverse) { - config->mixer_vol_right = l; - config->mixer_vol_left = r; - } - else { - config->mixer_vol_right = r; - config->mixer_vol_left = l; - } - } - else { - if(the_op && the_op->set_volume) { - if(config->mixer_reverse) - the_op->set_volume(r, l); - else - the_op->set_volume(l, r); - } - } + if (!config->enable_mixer) + return; + if (config->mixer_software) + { + if (config->mixer_reverse) + { + config->mixer_vol_right = l; + config->mixer_vol_left = r; + } + else + { + config->mixer_vol_right = r; + config->mixer_vol_left = l; + } + } + else + { + if (the_op && the_op->set_volume) + { + if (config->mixer_reverse) + the_op->set_volume(r, l); + else + the_op->set_volume(l, r); + } + } } /*** buffer stuff ***********************************************************/ static void -buffer_mfg_reset(buffer_t *buf, config_t *cfg) +buffer_mfg_reset(buffer_t * buf, config_t * cfg) { - buf->mix = 0; - buf->fade = 0; - buf->gap = (cfg->gap_lead_enable ? MS2B(cfg->gap_lead_len_ms) & -4 : 0); - buf->gap_len = buf->gap; - buf->gap_level = cfg->gap_lead_level; - buf->gap_killed = 0; + buf->mix = 0; + buf->fade = 0; + buf->gap = (cfg->gap_lead_enable ? MS2B(cfg->gap_lead_len_ms) & -4 : 0); + buf->gap_len = buf->gap; + buf->gap_level = cfg->gap_lead_level; + buf->gap_killed = 0; } static void -buffer_reset(buffer_t *buf, config_t *cfg) +buffer_reset(buffer_t * buf, config_t * cfg) { - buffer_mfg_reset(buf, cfg); + buffer_mfg_reset(buf, cfg); - buf->rd_index = 0; - buf->used = 0; - buf->preload = buf->preload_size; + buf->rd_index = 0; + buf->used = 0; + buf->preload = buf->preload_size; - buf->silence = 0; - buf->silence_len = 0; - buf->reopen = -1; - buf->pause = -1; + buf->silence = 0; + buf->silence_len = 0; + buf->reopen = -1; + buf->pause = -1; } /****************************************************************************/ static void -xfade_apply_fade_config(fade_config_t *fc) +xfade_apply_fade_config(fade_config_t * fc) { - gint avail, out_len, in_len, offset, skip; - gint index, length, fade, n; - gfloat out_scale, in_scale; - gboolean out_len_clipped = FALSE, offset_clipped = FALSE; + gint avail, out_len, in_len, offset, skip; + gint index, length, fade, n; + gfloat out_scale, in_scale; + gboolean out_len_clipped = FALSE, offset_clipped = FALSE; + + /* Overwrites mix and fade; may add silence */ - /* Overwrites mix and fade; may add silence */ + /* + * Example 1: offset < 0 --> mix streams together + * Example 2: offset > 0 --> insert pause between streams + * + * |----- out_len -----| * |out_len| + * | | * | | + * ~~~~~-_ /T~~~~~~~T~~ * ~~~~~\ | /T~~ + * ~-_ / | | * \ | / | + * ~-_/ | | * \ | / | + * /~-_| | * \ | / | + * / T-_ | * \ | / | + * / | ~-_ | * \ | / | + * _________/______|_____~-|__ * ___________\__________/______|__ + * |in_len| | * | |in_len| + * |<-- offset ---| * |offset-->| + * + * a) avail: max(0, used - preload) + * b) out_len: 0 .. avail + * c) in_len: 0 .. # + * d) offset: -avail .. buffer->mix_size - out_size + * e) skip: min(used, preload) + * + */ + + out_scale = 1.0f - (gfloat) xfade_cfg_fadeout_volume(fc) / 100.0f; + in_scale = 1.0f - (gfloat) xfade_cfg_fadein_volume(fc) / 100.0f; - /* - * Example 1: offset < 0 --> mix streams together - * Example 2: offset > 0 --> insert pause between streams - * - * |----- out_len -----| * |out_len| - * | | * | | - * ~~~~~-_ /T~~~~~~~T~~ * ~~~~~\ | /T~~ - * ~-_ / | | * \ | / | - * ~-_/ | | * \ | / | - * /~-_| | * \ | / | - * / T-_ | * \ | / | - * / | ~-_ | * \ | / | - * _________/______|_____~-|__ * ___________\__________/______|__ - * |in_len| | * | |in_len| - * |<-- offset ---| * |offset-->| - * - * a) avail: max(0, used - preload) - * b) out_len: 0 .. avail - * c) in_len: 0 .. # - * d) offset: -avail .. buffer->mix_size - out_size - * e) skip: min(used, preload) - * - */ + /* rules (see above) */ + /* a */ + avail = buffer->used - buffer->preload_size; + if (avail < 0) + avail = 0; + + /* b */ + out_len = MS2B(xfade_cfg_fadeout_len(fc)) & -4; + if (out_len < 0) + out_len = 0; + if (out_len > avail) + { + DEBUG(("[crossfade] apply_fade_config: WARNING: clipping out_len (%d -> %d)!\n", B2MS(out_len), B2MS(avail))); + out_len = avail; + out_len_clipped = TRUE; + } - out_scale = 1.0f - (gfloat)xfade_cfg_fadeout_volume(fc) / 100.0f; - in_scale = 1.0f - (gfloat)xfade_cfg_fadein_volume (fc) / 100.0f; + /* c */ + in_len = MS2B(xfade_cfg_fadein_len(fc)) & -4; + if (in_len < 0) + in_len = 0; - /* rules (see above) */ - /* a */ - avail = buffer->used - buffer->preload_size; - if(avail < 0) avail = 0; - - /* b */ - out_len = MS2B(xfade_cfg_fadeout_len(fc)) & -4; - if(out_len < 0) out_len = 0; - if(out_len > avail) { - DEBUG(("[crossfade] apply_fade_config: WARNING: clipping out_len (%d -> %d)!\n", - B2MS(out_len), B2MS(avail))); - out_len = avail; - out_len_clipped = TRUE; - } + /* d */ + offset = MS2B(xfade_cfg_offset(fc)) & -4; + if (offset < -avail) + { + DEBUG(("[crossfade] apply_fade_config: WARNING: clipping offset (%d -> %d)!\n", B2MS(offset), -B2MS(avail))); + offset = -avail; + offset_clipped = TRUE; + } + if (offset > (buffer->mix_size - out_len)) + offset = buffer->mix_size - out_len; + + /* e */ + skip = buffer->preload_size; + if (skip > buffer->used) + skip = buffer->used; - /* c */ - in_len = MS2B(xfade_cfg_fadein_len(fc)) & -4; - if(in_len < 0) in_len = 0; - - /* d */ - offset = MS2B(xfade_cfg_offset(fc)) & -4; - if(offset < -avail) { - DEBUG(("[crossfade] apply_fade_config: WARNING: clipping offset (%d -> %d)!\n", - B2MS(offset), -B2MS(avail))); - offset = -avail; - offset_clipped = TRUE; - } - if(offset > (buffer->mix_size - out_len)) - offset = buffer->mix_size - out_len; - - /* e */ - skip = buffer->preload_size; - if(skip > buffer->used) skip = buffer->used; - - /* cut off rest of stream (decreases latency on manual songchange) */ - if(fc->flush) { - gint cutoff = avail - MAX(out_len, -offset); /* MAX() -> glib.h */ - if(cutoff > 0) { - DEBUG(("[crossfade] apply_fade_config: %d ms flushed\n", B2MS(cutoff))); - buffer->used -= cutoff; - avail -= cutoff; - } + /* cut off rest of stream (decreases latency on manual songchange) */ + if (fc->flush) + { + gint cutoff = avail - MAX(out_len, -offset); /* MAX() -> glib.h */ + if (cutoff > 0) + { + DEBUG(("[crossfade] apply_fade_config: %d ms flushed\n", B2MS(cutoff))); + buffer->used -= cutoff; + avail -= cutoff; + } + + /* make sure there is no pending silence */ + buffer->silence = 0; + buffer->silence_len = 0; + } + + /* begin modifying buffer at index */ + index = (buffer->rd_index + buffer->used - out_len) % buffer->size; - /* make sure there is no pending silence */ - buffer->silence = 0; - buffer->silence_len = 0; - } + /* fade out (modifies buffer directly) */ + fade = 0; + length = out_len; + while (length > 0) + { + gint16 *p = buffer->data + index; + gint blen = buffer->size - index; + if (blen > length) + blen = length; + + for (n = blen / 4; n > 0; n--) + { + gfloat factor = 1.0f - (((gfloat) fade / out_len) * out_scale); + *p = (gfloat) * p * factor; + p++; + *p = (gfloat) * p * factor; + p++; + fade += 4; + } - /* begin modifying buffer at index */ - index = (buffer->rd_index + buffer->used - out_len) % buffer->size; - - /* fade out (modifies buffer directly) */ - fade = 0; - length = out_len; - while(length > 0) { - gint16 *p = buffer->data + index; - gint blen = buffer->size - index; - if(blen > length) blen = length; - - for(n = blen/4; n > 0; n--) { - gfloat factor = 1.0f - (((gfloat)fade / out_len) * out_scale); - *p = (gfloat)*p * factor; p++; - *p = (gfloat)*p * factor; p++; - fade += 4; - } - - index = (index + blen) % buffer->size; - length -= blen; - } + index = (index + blen) % buffer->size; + length -= blen; + } + + /* Initialize fadein. Note that the actual fading / mixing will be done + * on-the-fly when audio data is received by xfade_write_audio() */ - /* Initialize fadein. Note that the actual fading / mixing will be done - * on-the-fly when audio data is received by xfade_write_audio() */ + /* start fading in */ + if (in_len > 0) + { + buffer->fade = in_len; + buffer->fade_len = in_len; + buffer->fade_scale = in_scale; + } + else + buffer->fade = 0; - /* start fading in */ - if(in_len > 0) { - buffer->fade = in_len; - buffer->fade_len = in_len; - buffer->fade_scale = in_scale; - } - else - buffer->fade = 0; - - /* start mixing */ - if(offset < 0) { - length = -offset; - buffer->mix = length; - buffer->used -= length; - } - else - buffer->mix = 0; - - /* start silence if applicable (will be applied in buffer_thread_f) */ - if(offset > 0) { - if((buffer->silence > 0) || (buffer->silence_len > 0)) - DEBUG(("[crossfade] apply_config: WARNING: silence in progress (%d/%d ms)\n", - B2MS(buffer->silence), B2MS(buffer->silence_len))); - - buffer->silence = buffer->used; - buffer->silence_len = offset; - } - - /* done */ - DEBUG(("[crossfade] apply_fade_config: avail=%d out=%d in=%d offset=%d skip=%d\n", - B2MS(avail), B2MS(out_len), B2MS(in_len), B2MS(offset), B2MS(skip))); + /* start mixing */ + if (offset < 0) + { + length = -offset; + buffer->mix = length; + buffer->used -= length; + } + else + buffer->mix = 0; + + /* start silence if applicable (will be applied in buffer_thread_f) */ + if (offset > 0) + { + if ((buffer->silence > 0) || (buffer->silence_len > 0)) + DEBUG(("[crossfade] apply_config: WARNING: silence in progress (%d/%d ms)\n", + B2MS(buffer->silence), B2MS(buffer->silence_len))); + + buffer->silence = buffer->used; + buffer->silence_len = offset; + } + + /* done */ + DEBUG(("[crossfade] apply_fade_config: avail=%d out=%d in=%d offset=%d skip=%d\n", + B2MS(avail), B2MS(out_len), B2MS(in_len), B2MS(offset), B2MS(skip))); } static gint -extract_track(gchar *name) +extract_track(gchar * name) { - /* Remove all but numbers. - * Will not work if a filename has number in the title, like "track-03-U2.mp3" - * Ideally, should look into id3 track entry and fallback to filename - * */ + /* Remove all but numbers. + * Will not work if a filename has number in the title, like "track-03-U2.mp3" + * Ideally, should look into id3 track entry and fallback to filename + * */ + + gchar temp[8]; + int t = 0; - gchar temp[8]; - int t = 0 ; - - memset(temp,0,sizeof(temp)); - while(*name != '\0' && t < sizeof(temp)) { - if(strcmp(name,"mp3") == 0) - break; - if(isdigit(*name)) - temp[t++] = *name; - name++; - } - return atoi(temp); + memset(temp, 0, sizeof(temp)); + while (*name != '\0' && t < sizeof(temp)) + { + if (strcmp(name, "mp3") == 0) + break; + if (isdigit(*name)) + temp[t++] = *name; + name++; + } + return atoi(temp); } static gint -album_match(gchar *old, gchar *new) +album_match(gchar * old, gchar * new) { - gchar *old_dir, *new_dir; - gboolean same_dir; - gint old_track = 0, new_track = 0; - - if(!old || !new) return 0; + gchar *old_dir, *new_dir; + gboolean same_dir; + gint old_track = 0, new_track = 0; - old_dir = g_dirname(old); - new_dir = g_dirname(new); - same_dir = !strcmp(old_dir, new_dir); - g_free(old_dir); - g_free(new_dir); + if (!old || !new) + return 0; - if(!same_dir) { - DEBUG(("[crossfade] album_match: " - "no match (different dirs)\n")); - return 0; - } + old_dir = g_dirname(old); + new_dir = g_dirname(new); + same_dir = !strcmp(old_dir, new_dir); + g_free(old_dir); + g_free(new_dir); - old_track = extract_track(g_path_get_basename(old)); - new_track = extract_track(g_path_get_basename(new)); - - if(new_track <= 0) { - DEBUG(("[crossfade] album_match: can't parse track number:\n")); - DEBUG(("[crossfade] album_match: ... \"%s\"\n", g_basename(new))); - return 0; - } + if (!same_dir) + { + DEBUG(("[crossfade] album_match: " "no match (different dirs)\n")); + return 0; + } + + old_track = extract_track(g_path_get_basename(old)); + new_track = extract_track(g_path_get_basename(new)); - if((old_track < 0) || (old_track+1 != new_track)) { - DEBUG(("[crossfade] album_match: " - "no match (same dir, but non-successive (%d, %d))\n", - old_track, new_track)); - return 0; - } - - DEBUG(("[crossfade] album_match: " - "match detected (same dir, successive tracks (%d, %d))\n", - old_track, new_track)); - - return old_track; + if (new_track <= 0) + { + DEBUG(("[crossfade] album_match: can't parse track number:\n")); + DEBUG(("[crossfade] album_match: ... \"%s\"\n", g_basename(new))); + return 0; + } + + if ((old_track < 0) || (old_track + 1 != new_track)) + { + DEBUG(("[crossfade] album_match: " "no match (same dir, but non-successive (%d, %d))\n", old_track, new_track)); + return 0; + } + + DEBUG(("[crossfade] album_match: " "match detected (same dir, successive tracks (%d, %d))\n", old_track, new_track)); + + return old_track; } static gint xfade_open_audio(AFormat fmt, int rate, int nch) { - gint pos; - gchar *file; + gint pos; + gchar *file; - struct timeval tv; - glong dt; + struct timeval tv; + glong dt; - DEBUG(("[crossfade]\n")); - DEBUG(("[crossfade] open_audio: XMMS-crossfade "VERSION"\n")); + DEBUG(("[crossfade]\n")); + DEBUG(("[crossfade] open_audio: XMMS-crossfade " VERSION "\n")); #if 0 - DEBUG(("[crossfade] open_audio: pid=%d\n", getpid())); + DEBUG(("[crossfade] open_audio: pid=%d\n", getpid())); #endif - /* sanity... don't do anything about it */ - if(opened) - DEBUG(("[crossfade] open_audio: WARNING: already opened!\n")); + /* sanity... don't do anything about it */ + if (opened) + DEBUG(("[crossfade] open_audio: WARNING: already opened!\n")); - /* get filename */ + /* get filename */ #ifdef HAVE_PLAYLIST_GET_FILENAME - pos = get_playlist_position(); /* XMMS */ - file = playlist_get_filename(pos); /* XMMS */ + pos = get_playlist_position(); /* XMMS */ + file = playlist_get_filename(pos); /* XMMS */ #else - pos = xmms_remote_get_playlist_pos (session_id); - file = xmms_remote_get_playlist_file(session_id, pos); + pos = xmms_remote_get_playlist_pos(session_id); + file = xmms_remote_get_playlist_file(session_id, pos); #endif - DEBUG(("[crossfade] open_audio: bname=\"%s\"\n", g_basename(file))); + DEBUG(("[crossfade] open_audio: bname=\"%s\"\n", g_basename(file))); - /* is this an automatic crossfade? */ - if(last_filename && (fade_config == &config->fc[FADE_CONFIG_XFADE])) { - /* check if next song is the same as the current one */ - if(config->no_xfade_if_same_file && !strcmp(last_filename, file)) { - DEBUG(("[crossfade] open_audio: same file, disabling crossfade\n")); - fade_config = &config->fc[FADE_CONFIG_ALBUM]; - } + /* is this an automatic crossfade? */ + if (last_filename && (fade_config == &config->fc[FADE_CONFIG_XFADE])) + { + /* check if next song is the same as the current one */ + if (config->no_xfade_if_same_file && !strcmp(last_filename, file)) + { + DEBUG(("[crossfade] open_audio: same file, disabling crossfade\n")); + fade_config = &config->fc[FADE_CONFIG_ALBUM]; + } - /* check if next song is the next song from the same album */ - else if(config->album_detection && album_match(last_filename, file)) { - gboolean use_fc_album = FALSE; + /* check if next song is the next song from the same album */ + else if (config->album_detection && album_match(last_filename, file)) + { + gboolean use_fc_album = FALSE; - if(xfade_cfg_gap_trail_enable(config)) { - DEBUG(("[crossfade] album_match: " - "trailing gap: length=%d/%d ms\n", - B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); - - if(buffer->gap_killed < buffer->gap_len) { - DEBUG(("[crossfade] album_match: " - "trailing gap: -> no silence, probably pre-faded\n")); - use_fc_album = TRUE; - } - else { - DEBUG(("[crossfade] album_match: " - "trailing gap: -> silence, sticking to XFADE\n")); + if (xfade_cfg_gap_trail_enable(config)) + { + DEBUG(("[crossfade] album_match: " + "trailing gap: length=%d/%d ms\n", B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); + + if (buffer->gap_killed < buffer->gap_len) + { + DEBUG(("[crossfade] album_match: " + "trailing gap: -> no silence, probably pre-faded\n")); + use_fc_album = TRUE; + } + else + { + DEBUG(("[crossfade] album_match: " "trailing gap: -> silence, sticking to XFADE\n")); + } + } + else + { + DEBUG(("[crossfade] album_match: " "trailing gap killer disabled\n")); + use_fc_album = TRUE; + } + + if (use_fc_album) + { + DEBUG(("[crossfade] album_match: " "-> using FADE_CONFIG_ALBUM\n")); + fade_config = &config->fc[FADE_CONFIG_ALBUM]; + } + } + g_free(last_filename); } - } - else { - DEBUG(("[crossfade] album_match: " - "trailing gap killer disabled\n")); - use_fc_album = TRUE; - } - - if(use_fc_album) { - DEBUG(("[crossfade] album_match: " - "-> using FADE_CONFIG_ALBUM\n")); - fade_config = &config->fc[FADE_CONFIG_ALBUM]; - } - } - g_free(last_filename); - } - last_filename = g_strdup(file); + last_filename = g_strdup(file); #if 0 - /* FIXME: finish this */ - /* Check if this is a short song. */ - if(fade_config == &config->fc[FADE_CONFIG_XFADE]) { - DEBUG(("*** XFADE:\n")); - int current_length = playlist_get_current_length(); - DEBUG(("*** length=%d\n", current_length)); - if (current_length < 30*1000) - fade_config = &config->fc[FADE_CONFIG_ALBUM]; - } + /* FIXME: finish this */ + /* Check if this is a short song. */ + if (fade_config == &config->fc[FADE_CONFIG_XFADE]) + { + DEBUG(("*** XFADE:\n")); + int current_length = playlist_get_current_length(); + DEBUG(("*** length=%d\n", current_length)); + if (current_length < 30 * 1000) + fade_config = &config->fc[FADE_CONFIG_ALBUM]; + } #endif #ifdef VOLUME_NORMALIZER - /* turn off volume normalizing per default, until we get a target RMS for this song */ - volume_set_active(&volume_context, FALSE); + /* turn off volume normalizing per default, until we get a target RMS for this song */ + volume_set_active(&volume_context, FALSE); - /* thomas: add support for quantaudio per-song mixing info */ - if(config->volnorm_use_qa) { - /* remember timing info from last song */ - last_qa = qa; - have_last_qa = have_qa; + /* thomas: add support for quantaudio per-song mixing info */ + if (config->volnorm_use_qa) + { + /* remember timing info from last song */ + last_qa = qa; + have_last_qa = have_qa; - /* try to read quantaudio comment from ID3v1 tag */ - have_qa = get_timing_comment(file, &qa); - if(have_qa) { - DEBUG(("[crossfade] open_audio: quantaudio comment found:\n")); - DEBUG(("[crossfade] open_audio: ... RMS=%d, mix_in=%.2f, mix_out=%.2f, length=%.2f\n", - qa.RMS, qa.mix_in, qa.mix_out, qa.length)); - volume_set_song_rms(&volume_context, qa.RMS); - volume_set_active (&volume_context, TRUE); - } - else - DEBUG(("[crossfade] open_audio: no quantaudio comment found\n")); - + /* try to read quantaudio comment from ID3v1 tag */ + have_qa = get_timing_comment(file, &qa); + if (have_qa) + { + DEBUG(("[crossfade] open_audio: quantaudio comment found:\n")); + DEBUG(("[crossfade] open_audio: ... RMS=%d, mix_in=%.2f, mix_out=%.2f, length=%.2f\n", + qa.RMS, qa.mix_in, qa.mix_out, qa.length)); + volume_set_song_rms(&volume_context, qa.RMS); + volume_set_active(&volume_context, TRUE); + } + else + DEBUG(("[crossfade] open_audio: no quantaudio comment found\n")); + #if 0 - /* configure fade_config with fadeout from last song and fadein from new song */ - if(fade_config) { - config->fc[FADE_CONFIG_TIMING].out_enable = fade_config->out_enable; - config->fc[FADE_CONFIG_TIMING].out_volume = fade_config->out_volume; - config->fc[FADE_CONFIG_TIMING].out_len_ms = fade_config->out_len_ms; - config->fc[FADE_CONFIG_TIMING].ofs_custom_ms = 0; - config->fc[FADE_CONFIG_TIMING].in_enable = fade_config->in_enable; - config->fc[FADE_CONFIG_TIMING].in_volume = fade_config->in_volume; - config->fc[FADE_CONFIG_TIMING].in_len_ms = fade_config->in_len_ms; - - if(have_last_qa && ((fade_config->config == FADE_CONFIG_XFADE) || - (fade_config->config == FADE_CONFIG_ALBUM))) { - fade_config = &config->fc[FADE_CONFIG_TIMING]; - fade_config->out_enable = TRUE; - fade_config->out_volume = 0; - fade_config->out_len_ms = (gint)((last_qa.length - last_qa.mix_out) * 1000); - fade_config->ofs_custom_ms = - fade_config->out_len_ms; /* FIXME: */ - DEBUG(("[crossfade] open_audio: CONFIG_TIMING: using last song's fadeout parameter\n")); - } - - if(have_qa && ((fade_config->config == FADE_CONFIG_XFADE) || - (fade_config->config == FADE_CONFIG_ALBUM) || - (fade_config->config == FADE_CONFIG_MANUAL))) { - fade_config = &config->fc[FADE_CONFIG_TIMING]; - fade_config->in_enable = TRUE; - fade_config->in_volume = 0; - fade_config->in_len_ms = (gint)(qa.mix_in * 1000); - DEBUG(("[crossfade] open_audio: CONFIG_TIMING: using new song's fadein parameter\n")); - } - } + /* configure fade_config with fadeout from last song and fadein from new song */ + if (fade_config) + { + config->fc[FADE_CONFIG_TIMING].out_enable = fade_config->out_enable; + config->fc[FADE_CONFIG_TIMING].out_volume = fade_config->out_volume; + config->fc[FADE_CONFIG_TIMING].out_len_ms = fade_config->out_len_ms; + config->fc[FADE_CONFIG_TIMING].ofs_custom_ms = 0; + config->fc[FADE_CONFIG_TIMING].in_enable = fade_config->in_enable; + config->fc[FADE_CONFIG_TIMING].in_volume = fade_config->in_volume; + config->fc[FADE_CONFIG_TIMING].in_len_ms = fade_config->in_len_ms; + + if (have_last_qa && ((fade_config->config == FADE_CONFIG_XFADE) || + (fade_config->config == FADE_CONFIG_ALBUM))) + { + fade_config = &config->fc[FADE_CONFIG_TIMING]; + fade_config->out_enable = TRUE; + fade_config->out_volume = 0; + fade_config->out_len_ms = (gint) ((last_qa.length - last_qa.mix_out) * 1000); + fade_config->ofs_custom_ms = -fade_config->out_len_ms; /* FIXME: */ + DEBUG(("[crossfade] open_audio: CONFIG_TIMING: using last song's fadeout parameter\n")); + } + + if (have_qa && ((fade_config->config == FADE_CONFIG_XFADE) || + (fade_config->config == FADE_CONFIG_ALBUM) || + (fade_config->config == FADE_CONFIG_MANUAL))) + { + fade_config = &config->fc[FADE_CONFIG_TIMING]; + fade_config->in_enable = TRUE; + fade_config->in_volume = 0; + fade_config->in_len_ms = (gint) (qa.mix_in * 1000); + DEBUG(("[crossfade] open_audio: CONFIG_TIMING: using new song's fadein parameter\n")); + } + } #endif - } - else have_qa = FALSE; - /* thomas: end */ - - if(volume_context.active && config->volnorm_enable) - DEBUG(("[crossfade] open_audio: volume normalizer: factor=%.3f\n", - volume_context.factor)); + } + else + have_qa = FALSE; + /* thomas: end */ + + if (volume_context.active && config->volnorm_enable) + DEBUG(("[crossfade] open_audio: volume normalizer: factor=%.3f\n", volume_context.factor)); #endif - - /* check for HTTP streaming */ - if(config->enable_http_workaround && (0 == strncasecmp(file, "http://", 7))) { - DEBUG(("[crossfade] open_audio: HTTP underrun workaround enabled.\n")); - is_http = TRUE; - } - else - is_http = FALSE; - - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + + /* check for HTTP streaming */ + if (config->enable_http_workaround && (0 == strncasecmp(file, "http://", 7))) + { + DEBUG(("[crossfade] open_audio: HTTP underrun workaround enabled.\n")); + is_http = TRUE; + } + else + is_http = FALSE; + + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); - /* reset writer timeout */ - gettimeofday(&last_write, NULL); + /* reset writer timeout */ + gettimeofday(&last_write, NULL); - /* calculate time since last close() (don't care about overflows at 24h) */ - if(output_opened) { - gettimeofday(&tv, NULL); - dt = (tv.tv_sec - last_close.tv_sec) * 1000 - + (tv.tv_usec - last_close.tv_usec) / 1000; - } - else - dt = 0; + /* calculate time since last close() (don't care about overflows at 24h) */ + if (output_opened) + { + gettimeofday(&tv, NULL); + dt = (tv.tv_sec - last_close.tv_sec) * 1000 + (tv.tv_usec - last_close.tv_usec) / 1000; + } + else + dt = 0; - DEBUG(("[crossfade] open_audio: fmt=%s rate=%d nch=%d dt=%ld ms\n", - format_name(fmt), rate, nch, dt)); - - /* check format */ - if(setup_format(fmt, rate, nch, &in_format) < 0) { - DEBUG(("[crossfade] open_audio: format not supported!\n")); - return 0; - } + DEBUG(("[crossfade] open_audio: fmt=%s rate=%d nch=%d dt=%ld ms\n", format_name(fmt), rate, nch, dt)); + + /* check format */ + if (setup_format(fmt, rate, nch, &in_format) < 0) + { + DEBUG(("[crossfade] open_audio: format not supported!\n")); + return 0; + } - /* (re)open the device if necessary */ - if(!output_opened) { - if(open_output()) { - DEBUG(("[crossfade] open_audio: error opening/configuring output!\n")); - g_static_mutex_unlock(&buffer_mutex); - return 0; - } - fade_config = &config->fc[FADE_CONFIG_START]; - } + /* (re)open the device if necessary */ + if (!output_opened) + { + if (open_output()) + { + DEBUG(("[crossfade] open_audio: error opening/configuring output!\n")); + g_static_mutex_unlock(&buffer_mutex); + return 0; + } + fade_config = &config->fc[FADE_CONFIG_START]; + } - /* reset */ - streampos = 0; - playing = TRUE; - opened = TRUE; - paused = FALSE; + /* reset */ + streampos = 0; + playing = TRUE; + opened = TRUE; + paused = FALSE; - /* reset mix/fade/gap */ - buffer_mfg_reset(buffer, config); + /* reset mix/fade/gap */ + buffer_mfg_reset(buffer, config); - /* enable gap killer / zero crossing only for automatic/album songchange */ - switch(fade_config->config) { - case FADE_CONFIG_XFADE: - case FADE_CONFIG_ALBUM: - break; - default:; - buffer->gap = GAP_SKIPPING_DONE; - } + /* enable gap killer / zero crossing only for automatic/album songchange */ + switch (fade_config->config) + { + case FADE_CONFIG_XFADE: + case FADE_CONFIG_ALBUM: + break; + default:; + buffer->gap = GAP_SKIPPING_DONE; + } - /* restart realtime throttling */ - output_written = 0; + /* restart realtime throttling */ + output_written = 0; - /* start mixing */ - switch(fade_config ? fade_config->type : -1) { - case FADE_TYPE_FLUSH: - DEBUG(("[crossfade] open_audio: FLUSH:\n")); + /* start mixing */ + switch (fade_config ? fade_config->type : -1) + { + case FADE_TYPE_FLUSH: + DEBUG(("[crossfade] open_audio: FLUSH:\n")); - /* flush output plugin */ - the_op->flush(0); - output_streampos = 0; + /* flush output plugin */ + the_op->flush(0); + output_streampos = 0; - /* flush buffer */ - buffer_reset(buffer, config); + /* flush buffer */ + buffer_reset(buffer, config); + + /* apply fade config (pause/fadein after flush) */ + xfade_apply_fade_config(fade_config); - /* apply fade config (pause/fadein after flush) */ - xfade_apply_fade_config(fade_config); - - /* also repopen device (if configured so in the plugin compat. options) */ - if(the_op_config.force_reopen) { - buffer->reopen = 0; - buffer->reopen_sync = FALSE; - } - break; + /* also repopen device (if configured so in the plugin compat. options) */ + if (the_op_config.force_reopen) + { + buffer->reopen = 0; + buffer->reopen_sync = FALSE; + } + break; - case FADE_TYPE_REOPEN: - DEBUG(("[crossfade] open_audio: REOPEN:\n")); + case FADE_TYPE_REOPEN: + DEBUG(("[crossfade] open_audio: REOPEN:\n")); - /* flush buffer if applicable */ - if(fade_config->flush) - buffer_reset(buffer, config); + /* flush buffer if applicable */ + if (fade_config->flush) + buffer_reset(buffer, config); - if(buffer->reopen >= 0) - DEBUG(("[crossfade] open_audio: REOPEN: WARNING: reopen in progress (%d ms)\n", - B2MS(buffer->reopen))); - - /* start reopen countdown (will be executed in buffer_thread_f) */ - buffer->reopen = buffer->used; /* may be 0 */ - buffer->reopen_sync = FALSE; - break; + if (buffer->reopen >= 0) + DEBUG(("[crossfade] open_audio: REOPEN: WARNING: reopen in progress (%d ms)\n", + B2MS(buffer->reopen))); + + /* start reopen countdown (will be executed in buffer_thread_f) */ + buffer->reopen = buffer->used; /* may be 0 */ + buffer->reopen_sync = FALSE; + break; - case FADE_TYPE_NONE: - case FADE_TYPE_PAUSE: - case FADE_TYPE_SIMPLE_XF: - case FADE_TYPE_ADVANCED_XF: - case FADE_TYPE_FADEIN: - case FADE_TYPE_FADEOUT: - DEBUG(("[crossfade] open_audio: XFADE:\n")); - - /* apply fade config (do fadeout, init mix/fade/gap, add silence) */ - xfade_apply_fade_config(fade_config); - - /* set reopen countdown. after buffer_thread_f has written - * buffer->reopen bytes, it will close/reopen the output plugin. */ - if(the_op_config.force_reopen && !(fade_config->config == FADE_CONFIG_START)) { - if(buffer->reopen >= 0) - DEBUG(("[crossfade] open_audio: XFADE: WARNING: reopen in progress (%d ms)\n", - B2MS(buffer->reopen))); - buffer->reopen = buffer->used; - buffer->reopen_sync = TRUE; - } - break; - } + case FADE_TYPE_NONE: + case FADE_TYPE_PAUSE: + case FADE_TYPE_SIMPLE_XF: + case FADE_TYPE_ADVANCED_XF: + case FADE_TYPE_FADEIN: + case FADE_TYPE_FADEOUT: + DEBUG(("[crossfade] open_audio: XFADE:\n")); + + /* apply fade config (do fadeout, init mix/fade/gap, add silence) */ + xfade_apply_fade_config(fade_config); - /* calculate offset of the output plugin */ - output_offset = the_op->written_time() + B2MS(buffer->used) + B2MS(buffer->silence_len); + /* set reopen countdown. after buffer_thread_f has written + * buffer->reopen bytes, it will close/reopen the output plugin. */ + if (the_op_config.force_reopen && !(fade_config->config == FADE_CONFIG_START)) + { + if (buffer->reopen >= 0) + DEBUG(("[crossfade] open_audio: XFADE: WARNING: reopen in progress (%d ms)\n", + B2MS(buffer->reopen))); + buffer->reopen = buffer->used; + buffer->reopen_sync = TRUE; + } + break; + } - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); + /* calculate offset of the output plugin */ + output_offset = the_op->written_time() + B2MS(buffer->used) + B2MS(buffer->silence_len); - /* done */ - return 1; + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); + + /* done */ + return 1; } void xfade_write_audio(void *ptr, int length) { - gint free; - gint ofs = 0; - format_t format; + gint free; + gint ofs = 0; + format_t format; #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] write_audio: ptr=0x%08lx, length=%d\n", (long)ptr, length)); + DEBUG(("[crossfade] write_audio: ptr=0x%08lx, length=%d\n", (long) ptr, length)); #endif - /* sanity */ - if(length <= 0) return; - if(length & 3) { - DEBUG(("[crossfade] write_audio: truncating %d bytes!\n", length & 3)); - length &= -4; - } + /* sanity */ + if (length <= 0) + return; + if (length & 3) + { + DEBUG(("[crossfade] write_audio: truncating %d bytes!\n", length & 3)); + length &= -4; + } - /* update input accumulator (using input format size) */ - streampos += length; + /* update input accumulator (using input format size) */ + streampos += length; - /* apply pre-mixing effect (NOTE: format might change!) */ - format_copy(&format, &in_format); - length = effect_flow(&effect_context, (gpointer*)&ptr, length, &format, TRUE); - - /* convert sample format (signed-16bit-ne 44100hz stereo) */ - length = convert_flow(&convert_context, (gpointer*)&ptr, length, &format); + /* apply pre-mixing effect (NOTE: format might change!) */ + format_copy(&format, &in_format); + length = effect_flow(&effect_context, (gpointer *) & ptr, length, &format, TRUE); - /* convert rate */ - if(!rate_context.valid || (rate_context.in_rate != format.rate)) - rate_config(&rate_context, format.rate, out_format.rate, config->output_quality); - - length = rate_flow(&rate_context, (gpointer*)&ptr, length); + /* convert sample format (signed-16bit-ne 44100hz stereo) */ + length = convert_flow(&convert_context, (gpointer *) & ptr, length, &format); + + /* convert rate */ + if (!rate_context.valid || (rate_context.in_rate != format.rate)) + rate_config(&rate_context, format.rate, out_format.rate, config->output_quality); + + length = rate_flow(&rate_context, (gpointer *) & ptr, length); #ifdef VOLUME_NORMALIZER - /* normalize volume */ - if(config->volnorm_enable) - volume_flow(&volume_context, (gpointer*)&ptr, length); + /* normalize volume */ + if (config->volnorm_enable) + volume_flow(&volume_context, (gpointer *) & ptr, length); #endif - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); - /* check if device has been closed, reopen if necessary */ - if(!output_opened) { - if(open_output()) { - DEBUG(("[crossfade] write_audio: reopening failed!\n")); - g_static_mutex_unlock(&buffer_mutex); - return; - } - } + /* check if device has been closed, reopen if necessary */ + if (!output_opened) + { + if (open_output()) + { + DEBUG(("[crossfade] write_audio: reopening failed!\n")); + g_static_mutex_unlock(&buffer_mutex); + return; + } + } - /* reset timeout */ - gettimeofday(&last_write, NULL); + /* reset timeout */ + gettimeofday(&last_write, NULL); - /* calculate free buffer space, check for overflow (should never happen :) */ - free = buffer->size - buffer->used; - if(length > free) { - DEBUG(("[crossfade] write_audio: %d bytes truncated!\n", length-free)); - length = free; - } + /* calculate free buffer space, check for overflow (should never happen :) */ + free = buffer->size - buffer->used; + if (length > free) + { + DEBUG(("[crossfade] write_audio: %d bytes truncated!\n", length - free)); + length = free; + } + + /* kill leading gap */ + if ((length > 0) && (buffer->gap > 0)) + { + gint blen = MIN(length, buffer->gap); + gint16 *p = ptr; + gint index = 0; - /* kill leading gap */ - if((length > 0) && (buffer->gap > 0)) { - gint blen = MIN(length, buffer->gap); - gint16 *p = ptr; - gint index = 0; + gint16 left, right; + while (index < blen) + { + left = *p++, right = *p++; + if (ABS(left) >= buffer->gap_level) + break; + if (ABS(right) >= buffer->gap_level) + break; + index += 4; + } - gint16 left, right; - while(index < blen) { - left = *p++, right = *p++; - if(ABS(left) >= buffer->gap_level) break; - if(ABS(right) >= buffer->gap_level) break; - index += 4; - } - - buffer->gap -= index; - length -= index; - ptr += index; + buffer->gap -= index; + length -= index; + ptr += index; - if((index < blen) || (buffer->gap <= 0)) { - buffer->gap_killed = buffer->gap_len - buffer->gap; - buffer->gap = 0; + if ((index < blen) || (buffer->gap <= 0)) + { + buffer->gap_killed = buffer->gap_len - buffer->gap; + buffer->gap = 0; - DEBUG(("[crossfade] write_audio: leading gap size: %d/%d ms\n", - B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); + DEBUG(("[crossfade] write_audio: leading gap size: %d/%d ms\n", + B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); - /* fix streampos */ - streampos -= (gint64)buffer->gap_killed * in_format.bps / out_format.bps; - } - } + /* fix streampos */ + streampos -= (gint64) buffer->gap_killed * in_format.bps / out_format.bps; + } + } - /* start skipping to next crossing (if enabled) */ - if(buffer->gap == 0) { - if(config->gap_crossing) { - buffer->gap = GAP_SKIPPING_POSITIVE; - buffer->gap_skipped = 0; - } - else - buffer->gap = GAP_SKIPPING_DONE; - } + /* start skipping to next crossing (if enabled) */ + if (buffer->gap == 0) + { + if (config->gap_crossing) + { + buffer->gap = GAP_SKIPPING_POSITIVE; + buffer->gap_skipped = 0; + } + else + buffer->gap = GAP_SKIPPING_DONE; + } - /* skip until next zero crossing (pos -> neg) */ - if((length > 0) && (buffer->gap == GAP_SKIPPING_POSITIVE)) { - gint16 *p = ptr; - gint index = 0; + /* skip until next zero crossing (pos -> neg) */ + if ((length > 0) && (buffer->gap == GAP_SKIPPING_POSITIVE)) + { + gint16 *p = ptr; + gint index = 0; - gint16 left; - while(index < length) { - left = *p++; p++; - if(left < 0) break; - index += 4; - } + gint16 left; + while (index < length) + { + left = *p++; + p++; + if (left < 0) + break; + index += 4; + } - buffer->gap_skipped += index; - length -= index; - ptr += index; + buffer->gap_skipped += index; + length -= index; + ptr += index; - if(index < length) - buffer->gap = GAP_SKIPPING_NEGATIVE; - } + if (index < length) + buffer->gap = GAP_SKIPPING_NEGATIVE; + } - /* skip until next zero crossing (neg -> pos) */ - if((length > 0) && (buffer->gap == GAP_SKIPPING_NEGATIVE)) { - gint16 *p = ptr; - gint index = 0; + /* skip until next zero crossing (neg -> pos) */ + if ((length > 0) && (buffer->gap == GAP_SKIPPING_NEGATIVE)) + { + gint16 *p = ptr; + gint index = 0; - gint16 left; - while(index < length) { - left = *p++; p++; - if(left >= 0) break; - index += 4; - } + gint16 left; + while (index < length) + { + left = *p++; + p++; + if (left >= 0) + break; + index += 4; + } - buffer->gap_skipped += index; - length -= index; - ptr += index; + buffer->gap_skipped += index; + length -= index; + ptr += index; - if(index < length) { - DEBUG(("[crossfade] write_audio: %d samples to next crossing\n", - buffer->gap_skipped)); - buffer->gap = GAP_SKIPPING_DONE; - } - } + if (index < length) + { + DEBUG(("[crossfade] write_audio: %d samples to next crossing\n", buffer->gap_skipped)); + buffer->gap = GAP_SKIPPING_DONE; + } + } + + /* update preload. the buffer thread will not write any + * data to the device before preload is decreased below 1. */ + if ((length > 0) && (buffer->preload > 0)) + buffer->preload -= length; - /* update preload. the buffer thread will not write any - * data to the device before preload is decreased below 1. */ - if((length > 0) && (buffer->preload > 0)) - buffer->preload -= length; + /* fadein -- FIXME: is modifying the input/effect buffer safe? */ + if ((length > 0) && (buffer->fade > 0)) + { + gint16 *p = ptr; + gint blen = MIN(length, buffer->fade); + gint n; - /* fadein -- FIXME: is modifying the input/effect buffer safe? */ - if((length > 0) && (buffer->fade > 0)) { - gint16 *p = ptr; - gint blen = MIN(length, buffer->fade); - gint n; + for (n = blen / 4; n > 0; n--) + { + gfloat factor = 1.0f - (((gfloat) buffer->fade / buffer->fade_len) * buffer->fade_scale); + *p = (gfloat) * p * factor; + p++; + *p = (gfloat) * p * factor; + p++; + buffer->fade -= 4; + } + } - for(n = blen/4; n > 0; n--) { - gfloat factor = 1.0f - (((gfloat)buffer->fade / buffer->fade_len) * buffer->fade_scale); - *p = (gfloat)*p * factor; p++; - *p = (gfloat)*p * factor; p++; - buffer->fade -= 4; - } - } - - /* mix */ - while((length > 0) && (buffer->mix > 0)) { - gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; - gint blen = buffer->size - wr_index; - gint16 *p1 = buffer->data + wr_index; - gint16 *p2 = ptr + ofs; - gint n; + /* mix */ + while ((length > 0) && (buffer->mix > 0)) + { + gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; + gint blen = buffer->size - wr_index; + gint16 *p1 = buffer->data + wr_index; + gint16 *p2 = ptr + ofs; + gint n; - if(blen > length) blen = length; - if(blen > buffer->mix) blen = buffer->mix; + if (blen > length) + blen = length; + if (blen > buffer->mix) + blen = buffer->mix; + + for (n = blen / 2; n > 0; n--) + { + gint out = (gint) * p1 + *p2++; /* add */ + if (out > 32767) /* clamp */ + *p1++ = 32767; + else if (out < -32768) + *p1++ = -32768; + else + *p1++ = out; + } - for(n = blen/2; n > 0; n--) { - gint out = (gint)*p1 + *p2++; /* add */ - if(out > 32767) /* clamp */ - *p1++ = 32767; - else if(out < -32768) - *p1++ = -32768; - else - *p1++ = out; - } - - buffer->used += blen; - buffer->mix -= blen; - length -= blen; - ofs += blen; - } + buffer->used += blen; + buffer->mix -= blen; + length -= blen; + ofs += blen; + } + + /* normal write */ + while (length > 0) + { + gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; + gint blen = buffer->size - wr_index; - /* normal write */ - while(length > 0) { - gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; - gint blen = buffer->size - wr_index; + if (blen > length) + blen = length; - if(blen > length) blen = length; + memcpy(buffer->data + wr_index, ptr + ofs, blen); - memcpy(buffer->data + wr_index, ptr + ofs, blen); - - buffer->used += blen; - length -= blen; - ofs += blen; - } + buffer->used += blen; + length -= blen; + ofs += blen; + } - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] write_audio: done.\n")); + DEBUG(("[crossfade] write_audio: done.\n")); #endif } /* sync_output: wait for output plugin to finish playback */ /* is only called from within buffer_thread_f */ -static void sync_output() +static void +sync_output() { - glong dt, total; - gint opt, opt_last; - struct timeval tv, tv_start, tv_last_change; - gboolean was_closed = !opened; - - if(!the_op->buffer_playing || !the_op->buffer_playing()) { - DEBUG(("[crossfade] sync_output: nothing to do\n")); - return; - } + glong dt, total; + gint opt, opt_last; + struct timeval tv, tv_start, tv_last_change; + gboolean was_closed = !opened; - DEBUG(("[crossfade] sync_output: waiting for plugin...\n")); + if (!the_op->buffer_playing || !the_op->buffer_playing()) + { + DEBUG(("[crossfade] sync_output: nothing to do\n")); + return; + } + + DEBUG(("[crossfade] sync_output: waiting for plugin...\n")); - dt = 0; - opt_last = 0; - gettimeofday(&tv_start, NULL); - gettimeofday(&tv_last_change, NULL); + dt = 0; + opt_last = 0; + gettimeofday(&tv_start, NULL); + gettimeofday(&tv_last_change, NULL); - while((dt < SYNC_OUTPUT_TIMEOUT) - && !stopped - && output_opened - && !(was_closed && opened) - && the_op && the_op->buffer_playing()) { + while ((dt < SYNC_OUTPUT_TIMEOUT) + && !stopped && output_opened && !(was_closed && opened) && the_op && the_op->buffer_playing()) + { - /* use output_time() to check if the output plugin is still active */ - if(the_op->output_time) { - opt = the_op->output_time(); - if(opt != opt_last) { - /* output_time has changed */ - opt_last = opt; - gettimeofday(&tv_last_change, NULL); - } - else { - /* calculate time since last change of the_op->output_time() */ + /* use output_time() to check if the output plugin is still active */ + if (the_op->output_time) + { + opt = the_op->output_time(); + if (opt != opt_last) + { + /* output_time has changed */ + opt_last = opt; + gettimeofday(&tv_last_change, NULL); + } + else + { + /* calculate time since last change of the_op->output_time() */ + gettimeofday(&tv, NULL); + dt = (tv.tv_sec - tv_last_change.tv_sec) * 1000 + (tv.tv_usec - tv_last_change.tv_usec) / 1000; + } + } + + /* yield */ + g_static_mutex_unlock(&buffer_mutex); + xmms_usleep(10000); + g_static_mutex_lock(&buffer_mutex); + } + + /* calculate total time we spent in here */ gettimeofday(&tv, NULL); - dt = (tv.tv_sec - tv_last_change.tv_sec) * 1000 - + (tv.tv_usec - tv_last_change.tv_usec) / 1000; - } - } - - /* yield */ - g_static_mutex_unlock(&buffer_mutex); - xmms_usleep(10000); - g_static_mutex_lock(&buffer_mutex); - } + total = (tv.tv_sec - tv_start.tv_sec) * 1000 + (tv.tv_usec - tv_start.tv_usec) / 1000; - /* calculate total time we spent in here */ - gettimeofday(&tv, NULL); - total = (tv.tv_sec - tv_start.tv_sec) * 1000 - + (tv.tv_usec - tv_start.tv_usec) / 1000; - - /* print some debug info */ - if(stopped) - DEBUG(("[crossfade] sync_output: ... stopped\n")) - else if(was_closed && opened) - DEBUG(("[crossfade] sync_output: ... reopened\n")) - else if(dt >= SYNC_OUTPUT_TIMEOUT) - DEBUG(("[crossfade] sync_output: ... TIMEOUT! (%ld ms)\n", total)) - else - DEBUG(("[crossfade] sync_output: ... done (%ld ms)\n", total)); + /* print some debug info */ + if (stopped) + DEBUG(("[crossfade] sync_output: ... stopped\n")) + else + if (was_closed && opened) + DEBUG(("[crossfade] sync_output: ... reopened\n")) + else + if (dt >= SYNC_OUTPUT_TIMEOUT) + DEBUG(("[crossfade] sync_output: ... TIMEOUT! (%ld ms)\n", total)) + else + DEBUG(("[crossfade] sync_output: ... done (%ld ms)\n", total)); } void * buffer_thread_f(void *arg) { - gpointer data; - gint sync; - gint op_free; - gint length_bak, length, blen; - glong timeout, dt; - gboolean stopping; + gpointer data; + gint sync; + gint op_free; + gint length_bak, length, blen; + glong timeout, dt; + gboolean stopping; - struct timeval tv; - struct timeval mark; + struct timeval tv; + struct timeval mark; - DEBUG(("[crossfade] buffer_thread_f: thread started (pid=%d)\n", - (int)getpid())); - - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + DEBUG(("[crossfade] buffer_thread_f: thread started (pid=%d)\n", (int) getpid())); + + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); - while(!stopped) { - /* yield */ + while (!stopped) + { + /* yield */ #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] buffer_thread_f: yielding...\n")); + DEBUG(("[crossfade] buffer_thread_f: yielding...\n")); #endif - g_static_mutex_unlock(&buffer_mutex); - xmms_usleep(10000); - g_static_mutex_lock(&buffer_mutex); + g_static_mutex_unlock(&buffer_mutex); + xmms_usleep(10000); + g_static_mutex_lock(&buffer_mutex); + + /* --------------------------------------------------------------------- */ + + stopping = FALSE; + + /* V0.3.0: New timeout detection */ + if (!opened) + { + gboolean current = bmp_playback_get_playing(); /* BEEP */ + + /* also see fini() */ + if (last_close.tv_sec || last_close.tv_usec) + { + gettimeofday(&tv, NULL); + timeout = (tv.tv_sec - last_close.tv_sec) * 1000 + (tv.tv_usec - last_close.tv_usec) / 1000; + } + else + timeout = -1; + + if (current != input_playing) + { + input_playing = current; + if (current) + DEBUG(("[crossfade] buffer_thread_f:" " input restarted after %ld ms\n", timeout)) + else + DEBUG(("[crossfade] buffer_thread_f:" " input stopped after + %ld ms\n", timeout)); + } + + /* 0.3.0: HACK: output_keep_opened: play silence during prebuffering */ + if (input_playing && config->output_keep_opened && (buffer->used == 0)) + { + buffer->silence = 0; + buffer->silence_len = MS2B(100); + } - /* --------------------------------------------------------------------- */ + /* FIXME: make timeout configureable */ + if (((timeout < 0) || (timeout >= 100)) && !input_playing) + { + if (playing) + DEBUG(("[crossfade] buffer_thread_f: timeout:" + " input did not restart after %ld ms (playing=%d)\n", timeout, playing)); + stopping = TRUE; + } + } + + /* V0.2.4: Moved the timeout checks in front of the buffer_free() check + * below. Before, buffer_thread_f could (theoretically) loop + * endlessly if buffer_free() returned 0 all the time. */ + + /* calculate time since last write to the buffer (ignore overflows) */ + gettimeofday(&tv, NULL); + timeout = (tv.tv_sec - last_write.tv_sec) * 1000 + (tv.tv_usec - last_write.tv_usec) / 1000; + + /* check for timeout/eop (note this is the only way out of this loop) */ + if (stopping) + { + if (playing) + { + DEBUG(("[crossfade] buffer_thread_f: timeout: manual stop\n")); + + /* if CONFIG_STOP is of TYPE_NONE, immediatelly close the device... */ + if ((config->fc[FADE_CONFIG_STOP].type == FADE_TYPE_NONE) && !config->output_keep_opened) + break; + + /* special handling for pause */ + if (paused) + { + DEBUG(("[crossfade] buffer_thread_f: timeout: paused, closing now...\n")); + paused = FALSE; + if (config->output_keep_opened) + the_op->pause(0); + else + break; + } + else if (buffer->pause >= 0) + { + DEBUG(("[crossfade] buffer_thread_f: timeout: cancelling pause countdown\n")); + buffer->pause = -1; + } + + /* ...otherwise, do the fadeout first */ + xfade_apply_fade_config(&config->fc[FADE_CONFIG_STOP]); - stopping = FALSE; - - /* V0.3.0: New timeout detection */ - if(!opened) { - gboolean current = bmp_playback_get_playing(); /* BEEP */ + /* force CONFIG_START in case the user restarts playback during fadeout */ + fade_config = &config->fc[FADE_CONFIG_START]; + playing = FALSE; + eop = TRUE; + } + else + { + if (!eop) + { + DEBUG(("[crossfade] buffer_thread_f: timeout: end of playback\n")); + + /* 0.3.3: undo trailing gap killer at end of playlist */ + if (buffer->gap_killed) + { + buffer->used += buffer->gap_killed; + DEBUG(("[crossfade] buffer_thread_f: timeout:" + " undoing trailing gap (%d ms)\n", B2MS(buffer->gap_killed))); + } + + /* do the fadeout if applicable */ + if (config->fc[FADE_CONFIG_EOP].type != FADE_TYPE_NONE) + xfade_apply_fade_config(&config->fc[FADE_CONFIG_EOP]); + + fade_config = &config->fc[FADE_CONFIG_START]; /* see above */ + eop = TRUE; + } - /* also see fini() */ - if(last_close.tv_sec || last_close.tv_usec) { - gettimeofday(&tv, NULL); - timeout = (tv.tv_sec - last_close.tv_sec) * 1000 - + (tv.tv_usec - last_close.tv_usec) / 1000; - } - else - timeout = -1; + if (buffer->used == 0) + { + if (config->output_keep_opened) + { + /* 0.3.0: play silence while keeping the output opened */ + buffer->silence = 0; + buffer->silence_len = MS2B(100); + } + else if (buffer->silence_len <= 0) + { + sync_output(); + if (opened) + { + DEBUG(("[crossfade] buffer_thread_f: timeout, eop: device has been reopened\n")); + DEBUG(("[crossfade] buffer_thread_f: timeout, eop: -> continuing playback\n")); + eop = FALSE; + } + else + { + DEBUG(("[crossfade] buffer_thread_f: timeout, eop: closing output...\n")); + break; + } + } + } + } + } + else + eop = FALSE; + + /* --------------------------------------------------------------------- */ + + /* get free space in device output buffer + * NOTE: disk_writer always returns <big int> here */ + op_free = the_op->buffer_free() & -4; + + /* continue waiting if there is no room in the device buffer */ + if (op_free == 0) + continue; + + /* --- Limit OP buffer use (decreases latency) ------------------------- */ + + /* HACK: limit output plugin buffer usage to decrease latency */ + if (config->enable_op_max_used) + { + gint output_time = the_op->output_time(); + gint output_used = the_op->written_time() - output_time; + gint output_limit = MS2B(config->op_max_used_ms - MIN(output_used, config->op_max_used_ms)); + + if (output_flush_time != output_time) + { +#ifdef SONGCHANGE_TIMEOUT + if (timeout < config->songchange_timeout) + { + /* slow down output, but always write _some_ data */ + if (output_limit < in_format.bps / 100 / 2) + output_limit = in_format.bps / 100 / 2; + + if (op_free > output_limit) + op_free = output_limit; + } + else + { + if ((output_used < config->op_max_used_ms) && (op_free > output_limit)) + op_free = output_limit; + else + op_free = MIN(op_free, 2304); /* avoid 0 */ + } +#else + /* slow down output, but always write _some_ data */ + if (output_limit < in_format.bps / 100 / 2) + output_limit = in_format.bps / 100 / 2; + + if (op_free > output_limit) + op_free = output_limit; +#endif + } + } + + /* --- write silence --------------------------------------------------- */ - if(current != input_playing) { - input_playing = current; - if(current) - DEBUG(("[crossfade] buffer_thread_f:" - " input restarted after %ld ms\n", timeout)) - else - DEBUG(("[crossfade] buffer_thread_f:" - " input stopped after + %ld ms\n", timeout)); - } + if (!paused && (buffer->silence <= 0) && (buffer->silence_len >= 4)) + { + /* write as much silence as a) there is left and b) the device can take */ + length = buffer->silence_len; + if (length > op_free) + length = op_free; + + /* make sure we always operate on stereo sample boundary */ + length &= -4; + + /* HACK: don't stay in here too long when in realtime mode (see below) */ + if (realtime) + gettimeofday(&mark, NULL); + + /* write length bytes to the device */ + length_bak = length; + while (length > 0) + { + data = zero_4k; + blen = sizeof(zero_4k); + if (blen > length) + blen = length; + + /* make sure zero_4k is cleared. The effect plugin within + * the output plugin may have modified this buffer! (0.2.8) */ + memset(zero_4k, 0, blen); + + /* HACK: the original OSS plugin hangs when writing large + * blocks (greater than device buffer size) in realtime mode */ + if (the_op_config.max_write_enable && (blen > the_op_config.max_write_len)) + blen = the_op_config.max_write_len; + + /* finally, write data */ + the_op->write_audio(data, blen); + length -= blen; + + /* HACK: don't stay in here too long (force yielding every 10 ms) */ + if (realtime) + { + gettimeofday(&tv, NULL); + dt = (tv.tv_sec - mark.tv_sec) * 1000 + (tv.tv_usec - mark.tv_usec) / 1000; + if (dt >= 10) + break; + } + } + + /* calculate how many bytes actually have been written */ + length = length_bak - length; + } - /* 0.3.0: HACK: output_keep_opened: play silence during prebuffering */ - if(input_playing && config->output_keep_opened && (buffer->used == 0)) { - buffer->silence = 0; - buffer->silence_len = MS2B(100); - } + else + /* --- write data ------------------------------------------------- */ if (!paused + && (buffer->preload <= 0) + && (buffer->used >= 4)) + { + /* write as much data as a) is available and b) the device can take */ + length = buffer->used; + if (length > op_free) + length = op_free; + + /* HACK: throttle output (used with fast output plugins) */ + if (the_op_config.throttle_enable && !realtime && opened +#ifdef SONGCHANGE_TIMEOUT + && (timeout < config->songchange_timeout) +#endif + ) + { + sync = buffer->sync_size - (buffer->size - buffer->used); + if (sync < 0) + length = 0; + else if (sync < length) + length = sync; + } + + /* clip length to silence countdown (if applicable) */ + if ((buffer->silence >= 4) && (length > buffer->silence)) + length = buffer->silence; - /* FIXME: make timeout configureable */ - if(((timeout < 0) || (timeout >= 100)) && !input_playing) { - if(playing) - DEBUG(("[crossfade] buffer_thread_f: timeout:" - " input did not restart after %ld ms (playing=%d)\n", timeout, playing)); - stopping = TRUE; - } - } + /* clip length to reopen countdown (if applicable) */ + if ((buffer->reopen >= 0) && (length > buffer->reopen)) + length = buffer->reopen; + + /* clip length to pause countdown (if applicable) */ + if ((buffer->pause >= 0) && (length > buffer->pause)) + length = buffer->pause; + + /* make sure we always operate on stereo sample boundary */ + length &= -4; + + /* HACK: don't stay in here too long when in realtime mode (see below) */ + if (realtime) + gettimeofday(&mark, NULL); + + /* write length bytes to the device */ + length_bak = length; + while (length > 0) + { + data = buffer->data + buffer->rd_index; + blen = buffer->size - buffer->rd_index; + if (blen > length) + blen = length; + + /* HACK: the original OSS plugin hangs when writing large + * blocks (greater than device buffer size) in realtime mode */ + if (the_op_config.max_write_enable && (blen > the_op_config.max_write_len)) + blen = the_op_config.max_write_len; - /* V0.2.4: Moved the timeout checks in front of the buffer_free() check - * below. Before, buffer_thread_f could (theoretically) loop - * endlessly if buffer_free() returned 0 all the time. */ + /* finally, write data */ + the_op->write_audio(data, blen); + + buffer->rd_index = (buffer->rd_index + blen) % buffer->size; + buffer->used -= blen; + length -= blen; + + /* HACK: don't stay in here too long (force yielding every 10 ms) */ + if (realtime) + { + gettimeofday(&tv, NULL); + dt = (tv.tv_sec - mark.tv_sec) * 1000 + (tv.tv_usec - mark.tv_usec) / 1000; + if (dt >= 10) + break; + } + } + + /* calculate how many bytes actually have been written */ + length = length_bak - length; + } + else + length = 0; - /* calculate time since last write to the buffer (ignore overflows) */ - gettimeofday(&tv, NULL); - timeout = (tv.tv_sec - last_write.tv_sec) * 1000 - + (tv.tv_usec - last_write.tv_usec) / 1000; + /* update realtime throttling */ + output_written += length; + output_streampos += length; + + /* --- check countdowns ------------------------------------------------ */ - /* check for timeout/eop (note this is the only way out of this loop) */ - if(stopping) { - if(playing) { - DEBUG(("[crossfade] buffer_thread_f: timeout: manual stop\n")); + if (buffer->silence > 0) + { + buffer->silence -= length; + if (buffer->silence < 0) + DEBUG(("[crossfade] buffer_thread_f: WARNING: silence overrun: %d\n", buffer->silence)); + } + else if (buffer->silence_len > 0) + { + buffer->silence_len -= length; + if (buffer->silence_len <= 0) + { + if (buffer->silence_len < 0) + DEBUG(("[crossfade] buffer_thread_f: WARNING: silence_len overrun: %d\n", + buffer->silence_len)); + } + } - /* if CONFIG_STOP is of TYPE_NONE, immediatelly close the device... */ - if((config->fc[FADE_CONFIG_STOP].type == FADE_TYPE_NONE) - && !config->output_keep_opened) break; + if ((buffer->reopen >= 0) && !((buffer->silence <= 0) && (buffer->silence_len > 0))) + { + buffer->reopen -= length; + if (buffer->reopen <= 0) + { + if (buffer->reopen < 0) + DEBUG(("[crossfade] buffer_thread_f: WARNING: reopen overrun: %d\n", buffer->reopen)); + + DEBUG(("[crossfade] buffer_thread_f: closing/reopening device\n")); + if (buffer->reopen_sync) + sync_output(); + + if (the_op->close_audio) + the_op->close_audio(); + if (!the_op->open_audio(out_format.fmt, out_format.rate, out_format.nch)) + { + DEBUG(("[crossfade] buffer_thread_f: reopening output plugin failed!\n")); + g_free(buffer->data); + output_opened = FALSE; + g_static_mutex_unlock(&buffer_mutex); + g_thread_exit(0); + } + + output_flush_time = 0; + output_written = 0; + output_streampos = 0; - /* special handling for pause */ - if(paused) { - DEBUG(("[crossfade] buffer_thread_f: timeout: paused, closing now...\n")); - paused = FALSE; - if(config->output_keep_opened) - the_op->pause(0); - else - break; - } - else if(buffer->pause >= 0) { - DEBUG(("[crossfade] buffer_thread_f: timeout: cancelling pause countdown\n")); - buffer->pause = -1; + /* We need to take the leading gap killer into account here: + * It will fix streampos only after gapkilling has finished. + * So, if gapkilling is still in progress at this point, we + * have to fix it ourselves. */ + output_offset = buffer->used; + if ((buffer->gap_len > 0) && (buffer->gap > 0)) + output_offset += buffer->gap_len - buffer->gap; + output_offset = B2MS(output_offset) - xfade_written_time(); + + /* make sure reopen is not 0 */ + buffer->reopen = -1; + } + } + + if (buffer->pause >= 0) + { + buffer->pause -= length; + if (buffer->pause <= 0) + { + if (buffer->pause < 0) + DEBUG(("[crossfade] buffer_thread_f: WARNING: pause overrun: %d\n", buffer->pause)); + + DEBUG(("[crossfade] buffer_thread_f: pausing output\n")); + + paused = TRUE; + sync_output(); + + if (paused) + the_op->pause(1); + else + DEBUG(("[crossfade] buffer_thread_f: unpause during sync\n")) buffer->pause = -1; + } + } } - /* ...otherwise, do the fadeout first */ - xfade_apply_fade_config(&config->fc[FADE_CONFIG_STOP]); + /* ----------------------------------------------------------------------- */ - /* force CONFIG_START in case the user restarts playback during fadeout */ - fade_config = &config->fc[FADE_CONFIG_START]; - playing = FALSE; - eop = TRUE; - } - else { - if(!eop) { - DEBUG(("[crossfade] buffer_thread_f: timeout: end of playback\n")); - - /* 0.3.3: undo trailing gap killer at end of playlist */ - if(buffer->gap_killed) { - buffer->used += buffer->gap_killed; - DEBUG(("[crossfade] buffer_thread_f: timeout:" - " undoing trailing gap (%d ms)\n", B2MS(buffer->gap_killed))); - } - - /* do the fadeout if applicable */ - if(config->fc[FADE_CONFIG_EOP].type != FADE_TYPE_NONE) - xfade_apply_fade_config(&config->fc[FADE_CONFIG_EOP]); + /* cleanup: close output */ + if (output_opened) + { + xfade_stop_monitor(); - fade_config = &config->fc[FADE_CONFIG_START]; /* see above */ - eop = TRUE; - } - - if(buffer->used == 0) { - if(config->output_keep_opened) { - /* 0.3.0: play silence while keeping the output opened */ - buffer->silence = 0; - buffer->silence_len = MS2B(100); - } - else if(buffer->silence_len <= 0) { - sync_output(); - if(opened) { - DEBUG(("[crossfade] buffer_thread_f: timeout, eop: device has been reopened\n")); - DEBUG(("[crossfade] buffer_thread_f: timeout, eop: -> continuing playback\n")); - eop = FALSE; - } - else { - DEBUG(("[crossfade] buffer_thread_f: timeout, eop: closing output...\n")); - break; - } - } - } - } - } - else - eop = FALSE; + DEBUG(("[crossfade] buffer_thread_f: closing output...\n")); + + if (the_op->close_audio) + the_op->close_audio(); - /* --------------------------------------------------------------------- */ - - /* get free space in device output buffer - * NOTE: disk_writer always returns <big int> here */ - op_free = the_op->buffer_free() & -4; - - /* continue waiting if there is no room in the device buffer */ - if(op_free == 0) continue; - - /* --- Limit OP buffer use (decreases latency) ------------------------- */ + DEBUG(("[crossfade] buffer_thread_f: closing output... done\n")); - /* HACK: limit output plugin buffer usage to decrease latency */ - if(config->enable_op_max_used) { - gint output_time = the_op->output_time(); - gint output_used = the_op->written_time() - output_time; - gint output_limit = MS2B(config->op_max_used_ms - MIN(output_used, config->op_max_used_ms)); - - if(output_flush_time != output_time) { -#ifdef SONGCHANGE_TIMEOUT - if(timeout < config->songchange_timeout) { - /* slow down output, but always write _some_ data */ - if(output_limit < in_format.bps/100/2) - output_limit = in_format.bps/100/2; - - if(op_free > output_limit) - op_free = output_limit; + g_free(buffer->data); + output_opened = FALSE; } - else { - if((output_used < config->op_max_used_ms) && (op_free > output_limit)) - op_free = output_limit; - else - op_free = MIN(op_free, 2304); /* avoid 0 */ - } -#else - /* slow down output, but always write _some_ data */ - if(output_limit < in_format.bps/100/2) - output_limit = in_format.bps/100/2; - - if(op_free > output_limit) - op_free = output_limit; -#endif - } - } - - /* --- write silence --------------------------------------------------- */ - - if(!paused && (buffer->silence <= 0) && (buffer->silence_len >= 4)) { - /* write as much silence as a) there is left and b) the device can take */ - length = buffer->silence_len; - if(length > op_free) length = op_free; - - /* make sure we always operate on stereo sample boundary */ - length &= -4; - - /* HACK: don't stay in here too long when in realtime mode (see below) */ - if(realtime) gettimeofday(&mark, NULL); - - /* write length bytes to the device */ - length_bak = length; - while(length > 0) { - data = zero_4k; - blen = sizeof(zero_4k); - if(blen > length) blen = length; - - /* make sure zero_4k is cleared. The effect plugin within - * the output plugin may have modified this buffer! (0.2.8) */ - memset(zero_4k, 0, blen); - - /* HACK: the original OSS plugin hangs when writing large - * blocks (greater than device buffer size) in realtime mode */ - if(the_op_config.max_write_enable && (blen > the_op_config.max_write_len)) - blen = the_op_config.max_write_len; - - /* finally, write data */ - the_op->write_audio(data, blen); - length -= blen; - - /* HACK: don't stay in here too long (force yielding every 10 ms) */ - if(realtime) { - gettimeofday(&tv, NULL); - dt = (tv.tv_sec - mark.tv_sec) * 1000 - + (tv.tv_usec - mark.tv_usec) / 1000; - if(dt >= 10) break; - } - } - - /* calculate how many bytes actually have been written */ - length = length_bak - length; - } - - else /* --- write data ------------------------------------------------- */ - - if(!paused && (buffer->preload <= 0) && (buffer->used >= 4)) { - /* write as much data as a) is available and b) the device can take */ - length = buffer->used; - if(length > op_free) length = op_free; - - /* HACK: throttle output (used with fast output plugins) */ - if(the_op_config.throttle_enable && !realtime && opened -#ifdef SONGCHANGE_TIMEOUT - && (timeout < config->songchange_timeout) -#endif - ) { - sync = buffer->sync_size - (buffer->size - buffer->used); - if(sync < 0) - length = 0; - else if(sync < length) - length = sync; - } + else + DEBUG(("[crossfade] buffer_thread_f: output already closed!\n")); - /* clip length to silence countdown (if applicable) */ - if((buffer->silence >= 4) && (length > buffer->silence)) - length = buffer->silence; - - /* clip length to reopen countdown (if applicable) */ - if((buffer->reopen >= 0) && (length > buffer->reopen)) - length = buffer->reopen; - - /* clip length to pause countdown (if applicable) */ - if((buffer->pause >= 0) && (length > buffer->pause)) - length = buffer->pause; - - /* make sure we always operate on stereo sample boundary */ - length &= -4; - - /* HACK: don't stay in here too long when in realtime mode (see below) */ - if(realtime) gettimeofday(&mark, NULL); - - /* write length bytes to the device */ - length_bak = length; - while(length > 0) { - data = buffer->data + buffer->rd_index; - blen = buffer->size - buffer->rd_index; - if(blen > length) blen = length; - - /* HACK: the original OSS plugin hangs when writing large - * blocks (greater than device buffer size) in realtime mode */ - if(the_op_config.max_write_enable - && (blen > the_op_config.max_write_len)) - blen = the_op_config.max_write_len; - - /* finally, write data */ - the_op->write_audio(data, blen); + /* ----------------------------------------------------------------------- */ - buffer->rd_index = (buffer->rd_index + blen) % buffer->size; - buffer->used -= blen; - length -= blen; - - /* HACK: don't stay in here too long (force yielding every 10 ms) */ - if(realtime) { - gettimeofday(&tv, NULL); - dt = (tv.tv_sec - mark.tv_sec) * 1000 - + (tv.tv_usec - mark.tv_usec) / 1000; - if(dt >= 10) break; - } - } - - /* calculate how many bytes actually have been written */ - length = length_bak - length; - } - else - length = 0; - - /* update realtime throttling */ - output_written += length; - output_streampos += length; - - /* --- check countdowns ------------------------------------------------ */ - - if(buffer->silence > 0) { - buffer->silence -= length; - if(buffer->silence < 0) - DEBUG(("[crossfade] buffer_thread_f: WARNING: silence overrun: %d\n", - buffer->silence)); - } - else if(buffer->silence_len > 0) { - buffer->silence_len -= length; - if(buffer->silence_len <= 0) { - if(buffer->silence_len < 0) - DEBUG(("[crossfade] buffer_thread_f: WARNING: silence_len overrun: %d\n", - buffer->silence_len)); - } - } - - if((buffer->reopen >= 0) && !((buffer->silence <= 0) && (buffer->silence_len > 0))) { - buffer->reopen -= length; - if(buffer->reopen <= 0) { - if(buffer->reopen < 0) - DEBUG(("[crossfade] buffer_thread_f: WARNING: reopen overrun: %d\n", - buffer->reopen)); + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); - DEBUG(("[crossfade] buffer_thread_f: closing/reopening device\n")); - if(buffer->reopen_sync) sync_output(); - - if(the_op->close_audio) the_op->close_audio(); - if(!the_op->open_audio(out_format.fmt, out_format.rate, out_format.nch)) { - DEBUG(("[crossfade] buffer_thread_f: reopening output plugin failed!\n")); - g_free(buffer->data); - output_opened = FALSE; - g_static_mutex_unlock(&buffer_mutex); - g_thread_exit(0); - } - - output_flush_time = 0; - output_written = 0; - output_streampos = 0; - - /* We need to take the leading gap killer into account here: - * It will fix streampos only after gapkilling has finished. - * So, if gapkilling is still in progress at this point, we - * have to fix it ourselves. */ - output_offset = buffer->used; - if((buffer->gap_len > 0) && (buffer->gap > 0)) - output_offset += buffer->gap_len - buffer->gap; - output_offset = B2MS(output_offset) - xfade_written_time(); - - /* make sure reopen is not 0 */ - buffer->reopen = -1; - } - } - - if(buffer->pause >= 0) { - buffer->pause -= length; - if(buffer->pause <= 0) { - if(buffer->pause < 0) - DEBUG(("[crossfade] buffer_thread_f: WARNING: pause overrun: %d\n", - buffer->pause)); - - DEBUG(("[crossfade] buffer_thread_f: pausing output\n")); + /* done */ + DEBUG(("[crossfade] buffer_thread_f: thread finished\n")); + g_thread_exit(0); + return NULL; +} - paused = TRUE; - sync_output(); - - if(!paused) - DEBUG(("[crossfade] buffer_thread_f: unpause during sync\n")) - else - the_op->pause(1); - - buffer->pause = -1; - } - } - } - - /* ----------------------------------------------------------------------- */ - - /* cleanup: close output */ - if(output_opened) { - xfade_stop_monitor(); - - if(!finishing) { - if(the_op->close_audio) the_op->close_audio(); - g_free(buffer->data); - output_opened = FALSE; - } - } - else - DEBUG(("[crossfade] buffer_thread_f: output already closed!\n")); - - /* acknowledge 'finishing' (see fini()) */ - finishing = FALSE; - - /* ----------------------------------------------------------------------- */ - - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); - - /* done */ - DEBUG(("[crossfade] buffer_thread_f: thread finished\n")); - g_thread_exit(0); - - return NULL; /* avoid compiler warning */ +void +xfade_cleanup() +{ + /* wait for buffer thread to clean up and terminate */ + stopped = TRUE; + g_static_mutex_unlock(&buffer_mutex); + if (g_thread_join(buffer_thread)) + PERROR("[crossfade] close: phtread_join()"); } void xfade_close_audio() { - DEBUG(("[crossfade] close:\n")); - - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + DEBUG(("[crossfade] close:\n")); - /* sanity... the vorbis plugin likes to call close_audio() twice */ - if(!opened) { - DEBUG(("[crossfade] close: WARNING: not opened!\n")); - g_static_mutex_unlock(&buffer_mutex); - return; - } + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); - /* HACK: FIXME: */ - if(input_stopped_for_restart && input_stopped_for_restart()) - DEBUG(("[crossfade] close: playback will restart soon\n")); - - if(playing) { - /* immediatelly close output when paused */ - if(paused) { - buffer->pause = -1; - paused = FALSE; - if(config->output_keep_opened) { - buffer->used = 0; - the_op->flush(0); - the_op->pause(0); - } - else - stopped = TRUE; - } + /* sanity... the vorbis plugin likes to call close_audio() twice */ + if (!opened) + { + DEBUG(("[crossfade] close: WARNING: not opened!\n")); + g_static_mutex_unlock(&buffer_mutex); + return; + } - /* HACK: If playlist_get_info_going is not true here, - * XMMS is about to exit. In this case, we stop - * the buffer thread before returning from this - * function. Otherwise, SEGFAULT may occur when - * XMMS tries to cleanup an output plugin which - * we are still using. - * - * NOTE: This hack has become obsolete as of 0.3.5. - * See output_list_hack(). - */ -#if 0 - if((xmms_playlist_get_info_going && !*xmms_playlist_get_info_going) || - (xmms_is_quitting && *xmms_is_quitting)) { - DEBUG(("[crossfade] close: stop (about to quit)\n")) - -#if 1 - /* wait for buffer thread to clean up and terminate */ - stopped = TRUE; - while(output_opened) { - g_static_mutex_unlock(&buffer_mutex); - xmms_usleep(10000); - g_static_mutex_lock(&buffer_mutex); - } -#endif - } - else -#endif - DEBUG(("[crossfade] close: stop\n")); + if (playing) + { + /* immediately close output when paused */ + if (paused) + { + buffer->pause = -1; + paused = FALSE; + if (config->output_keep_opened) + { + buffer->used = 0; + the_op->flush(0); + the_op->pause(0); + } + else + stopped = TRUE; + } - fade_config = &config->fc[FADE_CONFIG_MANUAL]; - } - else { - DEBUG(("[crossfade] close: songchange/eop\n")); + fade_config = &config->fc[FADE_CONFIG_MANUAL]; + } + else + { + DEBUG(("[crossfade] close: songchange/eop\n")); - /* kill trailing gap (does not use buffer->gap_*) */ - if(output_opened && xfade_cfg_gap_trail_enable(config)) { - gint gap_len = MS2B(xfade_cfg_gap_trail_len(config)) & -4; - gint gap_level = xfade_cfg_gap_trail_level(config); - gint length = MIN(gap_len, buffer->used); + /* kill trailing gap (does not use buffer->gap_*) */ + if (output_opened && xfade_cfg_gap_trail_enable(config)) + { + gint gap_len = MS2B(xfade_cfg_gap_trail_len(config)) & -4; + gint gap_level = xfade_cfg_gap_trail_level(config); + gint length = MIN(gap_len, buffer->used); + + DEBUG(("[crossfade] close: len=%d level=%d length=%d\n", gap_len, gap_level, length)); + + buffer->gap_killed = 0; + while (length > 0) + { + gint wr_xedni = (buffer->rd_index + buffer->used - 1) % buffer->size + 1; + gint blen = MIN(length, wr_xedni); + gint16 *p = buffer->data + wr_xedni; + gint index = 0; - DEBUG(("[crossfade] close: len=%d level=%d length=%d\n", gap_len, gap_level, length)); - - buffer->gap_killed = 0; - while(length > 0) { - gint wr_xedni = (buffer->rd_index + buffer->used-1) % buffer->size + 1; - gint blen = MIN(length, wr_xedni); - gint16 *p = buffer->data + wr_xedni; - gint index = 0; - - while(index < blen) { - gint16 right = *--p, left = *--p; - if(ABS(left) >= gap_level) break; - if(ABS(right) >= gap_level) break; - index += 4; - } - - buffer->used -= index; - buffer->gap_killed += index; - - if(index < blen) break; - length -= blen; - } - - DEBUG(("[crossfade] close: trailing gap size: %d/%d ms\n", - B2MS(buffer->gap_killed), B2MS(gap_len))); - } + while (index < blen) + { + gint16 right = *--p, left = *--p; + if (ABS(left) >= gap_level) + break; + if (ABS(right) >= gap_level) + break; + index += 4; + } + + buffer->used -= index; + buffer->gap_killed += index; - /* skip to previous zero crossing */ - if(output_opened && config->gap_crossing) { - int crossing; + if (index < blen) + break; + length -= blen; + } + + DEBUG(("[crossfade] close: trailing gap size: %d/%d ms\n", B2MS(buffer->gap_killed), B2MS(gap_len))); + } + + /* skip to previous zero crossing */ + if (output_opened && config->gap_crossing) + { + int crossing; - buffer->gap_skipped = 0; - for(crossing = 0; crossing<4; crossing++) { - while(buffer->used > 0) { - gint wr_xedni = (buffer->rd_index + buffer->used-1) % buffer->size + 1; - gint blen = MIN(buffer->used, wr_xedni); - gint16 *p = buffer->data + wr_xedni; - gint index = 0; + buffer->gap_skipped = 0; + for (crossing = 0; crossing < 4; crossing++) + { + while (buffer->used > 0) + { + gint wr_xedni = (buffer->rd_index + buffer->used - 1) % buffer->size + 1; + gint blen = MIN(buffer->used, wr_xedni); + gint16 *p = buffer->data + wr_xedni; + gint index = 0; - while(index < blen) { - gint16 left = (--p, *--p); - if((crossing & 1) ^ (left > 0)) break; - index += 4; - } + while (index < blen) + { + gint16 left = (--p, *--p); + if ((crossing & 1) ^ (left > 0)) + break; + index += 4; + } + + buffer->used -= index; + buffer->gap_skipped += index; - buffer->used -= index; - buffer->gap_skipped += index; + if (index < blen) + break; + } + } + DEBUG(("[crossfade] close: skipped %d bytes to previous zero crossing\n", buffer->gap_skipped)); - if(index < blen) break; + /* update gap_killed (for undoing gap_killer in case of EOP) */ + buffer->gap_killed += buffer->gap_skipped; + } + + fade_config = &config->fc[FADE_CONFIG_XFADE]; } - } - DEBUG(("[crossfade] close: skipped %d bytes to previous zero crossing\n", buffer->gap_skipped)); - /* update gap_killed (for undoing gap_killer in case of EOP) */ - buffer->gap_killed += buffer->gap_skipped; - } - - fade_config = &config->fc[FADE_CONFIG_XFADE]; - } + /* XMMS has left the building */ + opened = FALSE; - /* XMMS has left the building */ - opened = FALSE; - - /* update last_close */ - gettimeofday(&last_close, NULL); - input_playing = FALSE; - - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); + /* update last_close */ + gettimeofday(&last_close, NULL); + input_playing = FALSE; + + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); } void xfade_flush(gint time) { - DEBUG(("[crossfade] flush: time=%d\n", time)); + DEBUG(("[crossfade] flush: time=%d\n", time)); - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); - /* update streampos with new stream position (input format size) */ - streampos = ((gint64)time * in_format.bps / 1000) & -4; + /* update streampos with new stream position (input format size) */ + streampos = ((gint64) time * in_format.bps / 1000) & -4; - /* flush output device / apply seek crossfade */ - if(config->fc[FADE_CONFIG_SEEK].type == FADE_TYPE_FLUSH) { - /* flush output plugin */ - the_op->flush(time); - output_flush_time = time; - output_streampos = MS2B(time); + /* flush output device / apply seek crossfade */ + if (config->fc[FADE_CONFIG_SEEK].type == FADE_TYPE_FLUSH) + { + /* flush output plugin */ + the_op->flush(time); + output_flush_time = time; + output_streampos = MS2B(time); - /* flush buffer, disable leading gap killing */ - buffer_reset(buffer, config); - } - else if(paused) { - fade_config_t fc; + /* flush buffer, disable leading gap killing */ + buffer_reset(buffer, config); + } + else if (paused) + { + fade_config_t fc; - /* clear buffer */ - buffer->used = 0; + /* clear buffer */ + buffer->used = 0; - /* apply only the fade_in part of FADE_CONFIG_PAUSE */ - memcpy(&fc, &config->fc[FADE_CONFIG_PAUSE], sizeof(fc)); - fc.out_len_ms = 0; - fc.ofs_custom_ms = 0; - xfade_apply_fade_config(&fc); - } - else - xfade_apply_fade_config(&config->fc[FADE_CONFIG_SEEK]); + /* apply only the fade_in part of FADE_CONFIG_PAUSE */ + memcpy(&fc, &config->fc[FADE_CONFIG_PAUSE], sizeof(fc)); + fc.out_len_ms = 0; + fc.ofs_custom_ms = 0; + xfade_apply_fade_config(&fc); + } + else + xfade_apply_fade_config(&config->fc[FADE_CONFIG_SEEK]); - /* restart realtime throttling (should find another name for that var) */ - output_written = 0; + /* restart realtime throttling (should find another name for that var) */ + output_written = 0; - /* make sure that the gapkiller is disabled */ - buffer->gap = 0; + /* make sure that the gapkiller is disabled */ + buffer->gap = 0; - /* update output offset */ - output_offset = the_op->written_time() - time - + B2MS(buffer->used) + B2MS(buffer->silence_len); + /* update output offset */ + output_offset = the_op->written_time() - time + B2MS(buffer->used) + B2MS(buffer->silence_len); - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] flush: time=%d: done.\n", time)); + DEBUG(("[crossfade] flush: time=%d: done.\n", time)); #endif } void xfade_pause(short p) { - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); + + if (p) + { + fade_config_t *fc = &config->fc[FADE_CONFIG_PAUSE]; + if (fc->type == FADE_TYPE_PAUSE_ADV) + { + int fade, length, n; + int index = buffer->rd_index; + int out_len = MS2B(xfade_cfg_fadeout_len(fc)) & -4; + int in_len = MS2B(xfade_cfg_fadein_len(fc)) & -4; + int silence_len = MS2B(xfade_cfg_offset(fc)) & -4; - if(p) { - fade_config_t *fc = &config->fc[FADE_CONFIG_PAUSE]; - if(fc->type == FADE_TYPE_PAUSE_ADV) { - int fade, length, n; - int index = buffer->rd_index; - int out_len = MS2B(xfade_cfg_fadeout_len(fc)) & -4; - int in_len = MS2B(xfade_cfg_fadein_len(fc)) & -4; - int silence_len = MS2B(xfade_cfg_offset(fc)) & -4; + /* limit fadeout/fadein len to available data in buffer */ + if ((out_len + in_len) > buffer->used) + { + out_len = (buffer->used / 2) & -4; + in_len = out_len; + } + + DEBUG(("[crossfade] pause: paused=1 out=%d in=%d silence=%d\n", + B2MS(out_len), B2MS(in_len), B2MS(silence_len))); + + /* fade out (modifies buffer directly) */ + fade = 0; + length = out_len; + while (length > 0) + { + gint16 *p = buffer->data + index; + gint blen = buffer->size - index; + if (blen > length) + blen = length; - /* limit fadeout/fadein len to available data in buffer */ - if((out_len + in_len) > buffer->used) { - out_len = (buffer->used / 2) & -4; - in_len = out_len; - } + for (n = blen / 4; n > 0; n--) + { + gfloat factor = 1.0f - ((gfloat) fade / out_len); + *p = (gfloat) * p * factor; p++; + *p = (gfloat) * p * factor; p++; + fade += 4; + } + + index = (index + blen) % buffer->size; + length -= blen; + } + + /* fade in (modifies buffer directly) */ + fade = 0; + length = in_len; + while (length > 0) + { + gint16 *p = buffer->data + index; + gint blen = buffer->size - index; + if (blen > length) + blen = length; - DEBUG(("[crossfade] pause: paused=1 out=%d in=%d silence=%d\n", - B2MS(out_len), B2MS(in_len), B2MS(silence_len))); - - /* fade out (modifies buffer directly) */ - fade = 0; - length = out_len; - while(length > 0) { - gint16 *p = buffer->data + index; - gint blen = buffer->size - index; - if(blen > length) blen = length; - - for(n = blen/4; n > 0; n--) { - gfloat factor = 1.0f - ((gfloat)fade / out_len); - *p = (gfloat)*p * factor; p++; - *p = (gfloat)*p * factor; p++; - fade += 4; + for (n = blen / 4; n > 0; n--) + { + gfloat factor = (gfloat) fade / in_len; + *p = (gfloat) * p * factor; p++; + *p = (gfloat) * p * factor; p++; + fade += 4; + } + + index = (index + blen) % buffer->size; + length -= blen; + } + + /* start silence and pause countdowns */ + buffer->silence = out_len; + buffer->silence_len = silence_len; + buffer->pause = out_len + silence_len; + paused = FALSE; /* (!) will be set to TRUE in buffer_thread_f */ + } + else + { + the_op->pause(1); + paused = TRUE; + DEBUG(("[crossfade] pause: paused=1\n")); + } } - - index = (index + blen) % buffer->size; - length -= blen; - } - - /* fade in (modifies buffer directly) */ - fade = 0; - length = in_len; - while(length > 0) { - gint16 *p = buffer->data + index; - gint blen = buffer->size - index; - if(blen > length) blen = length; - - for(n = blen/4; n > 0; n--) { - gfloat factor = (gfloat)fade / in_len; - *p = (gfloat)*p * factor; p++; - *p = (gfloat)*p * factor; p++; - fade += 4; + else + { + the_op->pause(0); + buffer->pause = -1; + paused = FALSE; + DEBUG(("[crossfade] pause: paused=0\n")); } - - index = (index + blen) % buffer->size; - length -= blen; - } - /* start silence and pause countdowns */ - buffer->silence = out_len; - buffer->silence_len = silence_len; - buffer->pause = out_len + silence_len; - paused = FALSE; /* (!) will be set to TRUE in buffer_thread_f */ - } - else { - the_op->pause(1); - paused = TRUE; - DEBUG(("[crossfade] pause: paused=1\n")); - } - } - else { - the_op->pause(0); - buffer->pause = -1; - paused = FALSE; - DEBUG(("[crossfade] pause: paused=0\n")); - } - - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); } gint xfade_buffer_free() { - gint size, free; + gint size, free; #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] buffer_free:\n")); + DEBUG(("[crossfade] buffer_free:\n")); #endif - /* sanity check */ - if(!output_opened) { - DEBUG(("[crossfade] buffer_free: WARNING: output closed!\n")); - return buffer->sync_size; - } + /* sanity check */ + if (!output_opened) + { + DEBUG(("[crossfade] buffer_free: WARNING: output closed!\n")); + return buffer->sync_size; + } - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); - size = buffer->size; + size = buffer->size; - /* When running in realtime mode, we need to take special care here: - * While XMMS is writing data to the output plugin, it will not yield - * ANY processing time to the buffer thread. It will only stop when - * xfade_buffer_free() no longer reports free buffer space. */ - if(realtime) { - gint64 wanted = output_written + buffer->preload_size; + /* When running in realtime mode, we need to take special care here: + * While XMMS is writing data to the output plugin, it will not yield + * ANY processing time to the buffer thread. It will only stop when + * xfade_buffer_free() no longer reports free buffer space. */ + if (realtime) + { + gint64 wanted = output_written + buffer->preload_size; - /* Fix for XMMS misbehaviour (possibly a bug): If the free space as - * returned by xfade_buffer_free() is below a certain minimum block size - * (tests showed 2304 bytes), XMMS will not send more data until there - * is enough room for one of those blocks. - * - * This breaks preloading in realtime mode. To make sure that the pre- - * load buffer gets filled we request additional sync_size bytes. */ - wanted += buffer->sync_size; + /* Fix for XMMS misbehaviour (possibly a bug): If the free space as + * returned by xfade_buffer_free() is below a certain minimum block size + * (tests showed 2304 bytes), XMMS will not send more data until there + * is enough room for one of those blocks. + * + * This breaks preloading in realtime mode. To make sure that the pre- + * load buffer gets filled we request additional sync_size bytes. */ + wanted += buffer->sync_size; - if(wanted <= size) size = wanted; - } + if (wanted <= size) + size = wanted; + } - free = size - buffer->used; - if(free < 0) free = 0; - - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); + free = size - buffer->used; + if (free < 0) + free = 0; + + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); - /* Convert to input format size. For input rates > output rate this will - * return less free space than actually is available, but we don't care. */ - free /= (out_format.rate / (in_format.rate+1)) + 1; - if(in_format.is_8bit) free /= 2; - if(in_format.nch == 1) free /= 2; + /* Convert to input format size. For input rates > output rate this will + * return less free space than actually is available, but we don't care. */ + free /= (out_format.rate / (in_format.rate + 1)) + 1; + if (in_format.is_8bit) + free /= 2; + if (in_format.nch == 1) + free /= 2; #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] buffer_free: %d\n", free)); + DEBUG(("[crossfade] buffer_free: %d\n", free)); #endif - return free; + return free; } -gint +gint xfade_buffer_playing() { - /* always return FALSE here (if not in pause) so XMMS immediatelly - * starts playback of the next song */ + /* always return FALSE here (if not in pause) so XMMS immediatelly + * starts playback of the next song */ - /* - * NOTE: this causes trouble when playing HTTP audio streams. - * - * mpg123.lib will start prebuffering (and thus stalling output) when both - * 1) it's internal buffer is emptied (does happen all the time) - * 2) the output plugin's buffer_playing() (this function) returns FALSE - */ + /* + * NOTE: this causes trouble when playing HTTP audio streams. + * + * mpg123.lib will start prebuffering (and thus stalling output) when both + * 1) it's internal buffer is emptied (does happen all the time) + * 2) the output plugin's buffer_playing() (this function) returns FALSE + */ - if(paused) - playing = TRUE; - else - playing = (is_http && (buffer->used > 0) && the_op->buffer_playing()) - || ((buffer->reopen >= 0) || (buffer->silence > 0) || (buffer->silence_len > 0)); + if (paused) + playing = TRUE; + else + playing = (is_http && (buffer->used > 0) && the_op->buffer_playing()) + || ((buffer->reopen >= 0) || (buffer->silence > 0) || (buffer->silence_len > 0)); #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] buffer_playing: %d\n", playing)); + DEBUG(("[crossfade] buffer_playing: %d\n", playing)); #endif - return playing; + return playing; } gint xfade_written_time() { - if(!output_opened) return 0; - return (gint)(streampos * 1000 / in_format.bps); + if (!output_opened) + return 0; + return (gint) (streampos * 1000 / in_format.bps); } gint xfade_output_time() { - gint time; + gint time; #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] output_time:\n")); + DEBUG(("[crossfade] output_time:\n")); #endif - /* sanity check (note: this one _does_ happen all the time) */ - if(!output_opened) return 0; + /* sanity check (note: this one _does_ happen all the time) */ + if (!output_opened) + return 0; - /* lock buffer */ - g_static_mutex_lock(&buffer_mutex); + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); - time = the_op->output_time() - output_offset; - if(time < 0) time = 0; + time = the_op->output_time() - output_offset; + if (time < 0) + time = 0; - /* unlock buffer */ - g_static_mutex_unlock(&buffer_mutex); + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); #ifdef DEBUG_HARDCORE - DEBUG(("[crossfade] output_time: time=%d\n", time)); + DEBUG(("[crossfade] output_time: time=%d\n", time)); #endif - return time; + return time; }