Mercurial > audlegacy-plugins
comparison src/crossfade/crossfade.c @ 3059:2e241e90494a
Import work in progress xmms-crossfade rewrite.
author | William Pitcock <nenolod@atheme.org> |
---|---|
date | Fri, 24 Apr 2009 05:57:35 -0500 |
parents | |
children | 43a336a7791b |
comparison
equal
deleted
inserted
replaced
3058:2e649bf16ebc | 3059:2e241e90494a |
---|---|
1 /* | |
2 * XMMS Crossfade Plugin | |
3 * Copyright (C) 2000-2007 Peter Eisenlohr <peter@eisenlohr.org> | |
4 * | |
5 * based on the original OSS Output Plugin | |
6 * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies | |
7 * | |
8 * This program is free software; you can redistribute it and/or modify | |
9 * it under the terms of the GNU General Public License as published by | |
10 * the Free Software Foundation; either version 2 of the License, or | |
11 * (at your option) any later version. | |
12 * | |
13 * This program is distributed in the hope that it will be useful, | |
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 * GNU General Public License for more details. | |
17 * | |
18 * You should have received a copy of the GNU General Public License | |
19 * along with this program; if not, write to the Free Software | |
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |
21 * USA. | |
22 */ | |
23 /* indent -i8 -ts8 -hnl -bli0 -l128 -npcs -cli8 */ | |
24 | |
25 #ifdef HAVE_CONFIG_H | |
26 # include "config.h" | |
27 #endif | |
28 | |
29 #include "crossfade.h" | |
30 #include "cfgutil.h" | |
31 #include "format.h" | |
32 #include "convert.h" | |
33 #include "timing.h" | |
34 | |
35 #include "configure.h" | |
36 #include "monitor.h" | |
37 | |
38 #include "interface-2.0.h" | |
39 #include "support-2.0.h" | |
40 | |
41 #ifdef HAVE_LIBFFTW | |
42 # include "fft.h" | |
43 #endif | |
44 | |
45 #include <stdio.h> | |
46 #include <string.h> | |
47 #include <stdlib.h> | |
48 #include <ctype.h> | |
49 | |
50 #ifdef HAVE_DLFCN_H | |
51 # include <dlfcn.h> | |
52 #endif | |
53 #include <unistd.h> | |
54 #include <signal.h> | |
55 #include <sys/time.h> | |
56 #include <sys/types.h> | |
57 #include <sys/ioctl.h> | |
58 | |
59 #undef DEBUG_HARDCORE | |
60 | |
61 /* output plugin callback prototypes */ | |
62 static void xfade_init(); | |
63 static void xfade_cleanup(); /* audacious and patched only */ | |
64 static void xfade_set_volume(int l, int r); | |
65 static void xfade_get_volume(int *l, int *r); | |
66 static gint xfade_open_audio(AFormat fmt, int rate, int nch); | |
67 static void xfade_write_audio(void *ptr, int length); | |
68 static void xfade_close_audio(); | |
69 static void xfade_flush(int time); | |
70 static void xfade_pause(short paused); | |
71 static gint xfade_buffer_free(); | |
72 static gint xfade_buffer_playing(); | |
73 static gint xfade_written_time(); | |
74 static gint xfade_output_time(); | |
75 | |
76 /* output plugin callback table (extended, needs patched player) */ | |
77 static struct | |
78 { | |
79 OutputPlugin xfade_op; | |
80 void (*cleanup) (void); | |
81 } | |
82 xfade_op_private = | |
83 { | |
84 { | |
85 .description = "Crossfade Plugin", | |
86 .init = xfade_init, | |
87 .cleanup = xfade_cleanup, | |
88 .get_volume = xfade_get_volume, | |
89 .set_volume = xfade_set_volume, | |
90 .open_audio = xfade_open_audio, | |
91 .write_audio = xfade_write_audio, | |
92 .close_audio = xfade_close_audio, | |
93 .flush = xfade_flush, | |
94 .pause = xfade_pause, | |
95 .buffer_free = xfade_buffer_free, | |
96 .buffer_playing = xfade_buffer_playing, | |
97 .output_time = xfade_output_time, | |
98 .written_time = xfade_written_time, | |
99 .about = xfade_about, | |
100 .configure = xfade_configure, | |
101 .probe_priority = 2, | |
102 }, | |
103 NULL | |
104 }; | |
105 | |
106 static OutputPlugin *xfade_op = &xfade_op_private.xfade_op; | |
107 | |
108 static OutputPlugin *xfade_oplist[] = { &xfade_op_private.xfade_op, NULL }; | |
109 DECLARE_PLUGIN(crossfade, NULL, NULL, NULL, xfade_oplist, NULL, NULL, NULL); | |
110 | |
111 /* internal prototypes */ | |
112 static void load_symbols(); | |
113 static void output_list_hack(); | |
114 static gint open_output(); | |
115 static void buffer_reset(buffer_t *buf, config_t *cfg); | |
116 static void *buffer_thread_f(void *arg); | |
117 static void sync_output(); | |
118 | |
119 /* special XMMS symbols (dynamically looked up, see xfade_init) */ | |
120 static gboolean *xmms_playlist_get_info_going = NULL; /* XMMS */ | |
121 static gboolean *xmms_is_quitting = NULL; /* XMMS */ | |
122 static gboolean *input_stopped_for_restart = NULL; /* XMMS */ | |
123 static char * (*playlist_get_fadeinfo)(int) = NULL; /* XMMS patch */ | |
124 | |
125 static void (*xmms_input_get_song_info)(gchar *, gchar **, gint *); /* XMMS */ | |
126 static gchar **xmms_gentitle_format = NULL; /* XMMS private cfg */ | |
127 | |
128 /* This function has been stolen from libxmms/util.c. */ | |
129 void xfade_usleep(gint usec) | |
130 { | |
131 #if defined(HAVE_G_USLEEP) | |
132 g_usleep(usec); | |
133 #elif defined(HAVE_NANOSLEEP) | |
134 struct timespec req; | |
135 | |
136 req.tv_sec = usec / 1000000; | |
137 usec -= req.tv_sec * 1000000; | |
138 req.tv_nsec = usec * 1000; | |
139 | |
140 nanosleep(&req, NULL); | |
141 #else | |
142 struct timeval tv; | |
143 | |
144 tv.tv_sec = usec / 1000000; | |
145 usec -= tv.tv_sec * 1000000; | |
146 tv.tv_usec = usec; | |
147 select(0, NULL, NULL, NULL, &tv); | |
148 #endif | |
149 } | |
150 | |
151 | |
152 /* local variables */ | |
153 static gboolean realtime; | |
154 static gboolean is_http; | |
155 | |
156 static gint64 streampos; /* position within current song (input bps) */ | |
157 static gboolean playing; | |
158 gboolean opened; /* TRUE between open_audio() and close_audio() */ | |
159 static gboolean paused; /* TRUE: no playback (but still filling buffer) */ | |
160 static gboolean stopped; /* TRUE: stop buffer thread ASAP */ | |
161 static gboolean eop; /* TRUE: wait until buffer is empty then sync() */ | |
162 | |
163 #ifdef HAVE_OSS /* avoid 'defined but not used' compiler warning */ | |
164 static plugin_config_t default_op_config = DEFAULT_OP_CONFIG; | |
165 #endif | |
166 static plugin_config_t the_op_config = DEFAULT_OP_CONFIG; | |
167 OutputPlugin *the_op = NULL; | |
168 gint the_rate = 44100; | |
169 | |
170 static gboolean input_playing = FALSE; | |
171 | |
172 gboolean output_opened = FALSE; | |
173 gboolean output_restart = FALSE; /* used by XMMS 'songchange' patch */ | |
174 static gint output_flush_time = 0; | |
175 gint output_offset = 0; | |
176 static gint64 output_written = 0; | |
177 gint64 output_streampos = 0; | |
178 | |
179 static gchar zero_4k[4096]; | |
180 | |
181 #ifdef TIMING_COMMENTS | |
182 typedef struct | |
183 { | |
184 gboolean enable; | |
185 gint len_ms, volume, skip_ms, ofs_ms; | |
186 } | |
187 timing_half_config_t; | |
188 | |
189 typedef struct | |
190 { | |
191 timing_half_config_t in; | |
192 timing_half_config_t out; | |
193 } | |
194 timing_config_t; | |
195 | |
196 static timing_config_t last_timing, current_timing; | |
197 #endif | |
198 | |
199 /* | |
200 * Available fade configs: | |
201 * | |
202 * fc_start: First song, only in_len and in_level are used | |
203 * fc_xfade: Automatic crossfade at end of song | |
204 * fc_album: Like xfade but for consecutive songs of the same album | |
205 * fc_manual: Manual crossfade (triggered by Next or Prev) | |
206 * fc_stop: Last song, only out_len and out_level are used | |
207 * fc_eop: Last song, only out_len and out_level are used | |
208 * | |
209 * NOTE: As of version 0.2 of xmms-crossfade, | |
210 * only xfade and manual are implemented. | |
211 * | |
212 * With version 0.2.3, fc_album has been added. | |
213 * | |
214 * With 0.2.4, all configs are implemented: | |
215 * | |
216 * Available parameters: | |
217 * | |
218 * | start | xfade | manual | album | stop | eop | |
219 * ------------+-------+-------+--------+-------+------+------ | |
220 * in_len | yes | yes | yes | no | no | no | |
221 * in_volume | yes | yes | yes | no | no | no | |
222 * offset | no | yes | yes | no | +yes | +yes | |
223 * out_len | no | yes | yes | no | yes | yes | |
224 * out_volume | no | yes | yes | no | yes | yes | |
225 * flush (*) | no | no | yes | no | yes | no | |
226 * ------------+-------+-------+--------+-------+------+------ | |
227 * | |
228 * Parameters marked with (*) are not configureable by the user | |
229 * | |
230 * The offset parameters for 'stop' and 'eop' is used to store the | |
231 * length of the additional silence to be added. It may be >= 0 only. | |
232 * | |
233 */ | |
234 | |
235 static struct timeval last_close; | |
236 static struct timeval last_write; | |
237 | |
238 static gchar *last_filename = NULL; | |
239 | |
240 static format_t in_format; | |
241 static format_t out_format; | |
242 | |
243 static buffer_t the_buffer; | |
244 buffer_t *buffer = &the_buffer; | |
245 | |
246 static THREAD buffer_thread; | |
247 MUTEX buffer_mutex = MUTEX_INITIALIZER; | |
248 | |
249 static convert_context_t convert_context; | |
250 #ifdef HAVE_LIBFFTW | |
251 static fft_context_t fft_context; | |
252 #endif | |
253 | |
254 static config_t the_config; | |
255 config_t *config = &the_config; | |
256 config_t config_default = CONFIG_DEFAULT; | |
257 | |
258 static fade_config_t *fade_config = NULL; | |
259 | |
260 | |
261 /* this is the entry point for XMMS */ | |
262 OutputPlugin * | |
263 get_oplugin_info() | |
264 { | |
265 return xfade_op; | |
266 } | |
267 | |
268 OutputPlugin * | |
269 get_crossfade_oplugin_info() | |
270 { | |
271 return xfade_op; | |
272 } | |
273 | |
274 static gboolean | |
275 open_output_f(gpointer data) | |
276 { | |
277 DEBUG(("[crossfade] open_output_f: pid=%d\n", getpid())); | |
278 open_output(); | |
279 return FALSE; /* FALSE = 'do not call me again' */ | |
280 } | |
281 | |
282 void | |
283 xfade_realize_config() /* also called by xfade_init() */ | |
284 { | |
285 /* 0.3.0: keep device opened */ | |
286 if (config->output_keep_opened && !output_opened) | |
287 { | |
288 DEBUG(("[crossfade] realize_config: keeping output opened...\n")); | |
289 | |
290 /* 0.3.1: HACK: this will make sure that we start playing silence after startup */ | |
291 gettimeofday(&last_close, NULL); | |
292 | |
293 /* 0.3.1: HACK: Somehow, if we open output here at XMMS startup, there | |
294 will be leftover filedescriptors later when closing output again. | |
295 Opening output in a timeout function seems to work around this... */ | |
296 DEBUG(("[crossfade] realize_config: adding timeout (pid=%d)\n", (int) getpid())); | |
297 g_timeout_add(0, open_output_f, NULL); | |
298 } | |
299 } | |
300 | |
301 static gint | |
302 output_list_f(gconstpointer a, gconstpointer b) | |
303 { | |
304 OutputPlugin *op = (OutputPlugin *) a; | |
305 gchar *name = (gchar *) b; | |
306 | |
307 return strcmp(g_basename(op->filename), name); | |
308 } | |
309 | |
310 static OutputPlugin * | |
311 find_output() | |
312 { | |
313 GList *list, *element; | |
314 OutputPlugin *op = NULL; | |
315 | |
316 /* find output plugin */ | |
317 { | |
318 if (config->op_name && (list = xfplayer_get_output_list())) | |
319 if ((element = g_list_find_custom(list, config->op_name, output_list_f))) | |
320 op = element->data; | |
321 | |
322 if (op == xfade_op) | |
323 { | |
324 DEBUG(("[crossfade] find_output: can't use myself as output plugin!\n")); | |
325 op = NULL; | |
326 } | |
327 else if (!op) | |
328 { | |
329 DEBUG(("[crossfade] find_output: could not find output plugin \"%s\"\n", | |
330 config->op_name ? config->op_name : "#NULL#")); | |
331 } | |
332 else /* ok, we have a plugin. last, get its compatibility options */ | |
333 xfade_load_plugin_config(config->op_config_string, config->op_name, &the_op_config); | |
334 } | |
335 | |
336 return op; | |
337 } | |
338 | |
339 static gint | |
340 open_output() | |
341 { | |
342 /* sanity check */ | |
343 if (output_opened) | |
344 DEBUG(("[crossfade] open_output: WARNING: output_opened=TRUE!\n")); | |
345 | |
346 /* reset output_* */ | |
347 output_opened = FALSE; | |
348 output_flush_time = 0; | |
349 output_offset = 0; | |
350 output_written = 0; | |
351 output_streampos = 0; | |
352 | |
353 /* get output plugin (this will also init the_op_config) */ | |
354 if (!(the_op = find_output())) | |
355 { | |
356 DEBUG(("[crossfade] open_output: could not find any output!\n")); | |
357 return -1; | |
358 } | |
359 | |
360 /* print output plugin info */ | |
361 DEBUG(("[crossfade] open_output: using \"%s\" for output", the_op->description ? the_op->description : "#NULL#")); | |
362 | |
363 if (realtime) | |
364 DEBUG((" (RT)")); | |
365 | |
366 if (the_op_config.throttle_enable) | |
367 DEBUG((realtime ? " (throttled (disabled with RT))" : " (throttled)")); | |
368 | |
369 if (the_op_config.max_write_enable) | |
370 DEBUG((" (max_write=%d)", the_op_config.max_write_len)); | |
371 | |
372 DEBUG(("\n")); | |
373 | |
374 /* setup sample rate (note that OUTPUT_RATE is #defined as the_rate) */ | |
375 the_rate = config->output_rate; | |
376 | |
377 /* setup out_format. use host byte order for easy math */ | |
378 setup_format(FMT_S16_NE, OUTPUT_RATE, OUTPUT_NCH, &out_format); | |
379 | |
380 /* open plugin */ | |
381 if (!the_op->open_audio(out_format.fmt, out_format.rate, out_format.nch)) | |
382 { | |
383 DEBUG(("[crossfade] open_output: open_audio() failed!\n")); | |
384 the_op = NULL; | |
385 return -1; | |
386 } | |
387 | |
388 /* clear buffer struct */ | |
389 memset(buffer, 0, sizeof(*buffer)); | |
390 | |
391 /* calculate buffer size */ | |
392 buffer->mix_size = MS2B(xfade_mix_size_ms(config)) & -4; | |
393 buffer->sync_size = MS2B(config->sync_size_ms) & -4; | |
394 buffer->preload_size = MS2B(config->preload_size_ms) & -4; | |
395 | |
396 buffer->size = (buffer->mix_size + /* mixing area */ | |
397 buffer->sync_size + /* additional sync */ | |
398 buffer->preload_size); /* preload */ | |
399 | |
400 DEBUG(("[crossfade] open_output: buffer: size=%d (%d+%d+%d=%d ms) (%d Hz)\n", | |
401 buffer->size, | |
402 B2MS(buffer->mix_size), | |
403 B2MS(buffer->preload_size), | |
404 B2MS(buffer->sync_size), | |
405 B2MS(buffer->size), | |
406 the_rate)); | |
407 | |
408 /* allocate buffer */ | |
409 if (!(buffer->data = g_malloc0(buffer->size))) | |
410 { | |
411 DEBUG(("[crossfade] open_output: error allocating buffer!\n")); | |
412 the_op->close_audio(); | |
413 the_op = NULL; | |
414 return -1; | |
415 } | |
416 | |
417 /* reset buffer */ | |
418 buffer_reset(buffer, config); | |
419 | |
420 /* make sure stopped is TRUE -- otherwise the buffer thread would | |
421 * stop again immediatelly after it has been started. */ | |
422 stopped = FALSE; | |
423 | |
424 /* create and run buffer thread */ | |
425 if (THREAD_CREATE(buffer_thread, buffer_thread_f)) | |
426 { | |
427 PERROR("[crossfade] open_output: thread_create()"); | |
428 g_free(buffer->data); | |
429 the_op->close_audio(); | |
430 the_op = NULL; | |
431 return -1; | |
432 } | |
433 SCHED_YIELD; | |
434 | |
435 /* start updating monitor */ | |
436 xfade_start_monitor(); | |
437 | |
438 /* done */ | |
439 output_opened = TRUE; | |
440 return 0; | |
441 } | |
442 | |
443 static void | |
444 xfade_init() | |
445 { | |
446 /* load config */ | |
447 memset(config, 0, sizeof(*config)); | |
448 *config = config_default; | |
449 xfade_load_config(); | |
450 | |
451 /* set default strings if there is no existing config */ | |
452 if (!config->oss_alt_audio_device) config->oss_alt_audio_device = g_strdup(DEFAULT_OSS_ALT_AUDIO_DEVICE); | |
453 if (!config->oss_alt_mixer_device) config->oss_alt_mixer_device = g_strdup(DEFAULT_OSS_ALT_MIXER_DEVICE); | |
454 if (!config->op_config_string) config->op_config_string = g_strdup(DEFAULT_OP_CONFIG_STRING); | |
455 if (!config->op_name) config->op_name = g_strdup(DEFAULT_OP_NAME); | |
456 | |
457 /* check for realtime priority, it needs some special attention */ | |
458 realtime = xfplayer_check_realtime_priority(); | |
459 | |
460 /* show monitor win if enabled in config */ | |
461 xfade_check_monitor_win(); | |
462 | |
463 /* init contexts */ | |
464 convert_init(&convert_context); | |
465 #ifdef HAVE_LIBFFTW | |
466 fft_init(&fft_context); | |
467 #endif | |
468 | |
469 /* reset */ | |
470 stopped = FALSE; | |
471 | |
472 /* find current output plugin early so that volume control works | |
473 * even if playback has not started yet. */ | |
474 if (!(the_op = find_output())) | |
475 DEBUG(("[crossfade] init: could not find any output!\n")); | |
476 | |
477 /* load any dynamic linked symbols */ | |
478 load_symbols(); | |
479 | |
480 /* HACK: make sure we are at the beginning of XMMS' output plugin list */ | |
481 output_list_hack(); | |
482 | |
483 /* realize config -- will also setup the pre-mixing effect plugin */ | |
484 xfade_realize_config(); | |
485 } | |
486 | |
487 static void | |
488 load_symbols() | |
489 { | |
490 #ifdef HAVE_DLFCN_H | |
491 void *handle; | |
492 char *error; | |
493 gchar **xmms_cfg; | |
494 gchar * (*get_gentitle_format)(); | |
495 | |
496 /* open ourselves (that is, the XMMS binary) */ | |
497 handle = dlopen(NULL, RTLD_NOW); | |
498 if (!handle) | |
499 { | |
500 DEBUG(("[crossfade] init: dlopen(NULL) failed!\n")); | |
501 return; | |
502 } | |
503 | |
504 /* check for XMMS patches */ | |
505 DEBUG(("[crossfade] load_symbols: input_stopped_for_restart:")); | |
506 input_stopped_for_restart = dlsym(handle, "input_stopped_for_restart"); | |
507 DEBUG((!(error = dlerror())? " found\n" : " missing\n")); | |
508 | |
509 DEBUG(("[crossfade] load_symbols: is_quitting:")); | |
510 xmms_is_quitting = dlsym(handle, "is_quitting"); | |
511 DEBUG((!(error = dlerror())? " found\n" : " missing\n")); | |
512 | |
513 DEBUG(("[crossfade] load_symbols: playlist_get_fadeinfo:")); | |
514 playlist_get_fadeinfo = dlsym(handle, "playlist_get_fadeinfo"); | |
515 DEBUG((!(error = dlerror())? " found\n" : " missing\n")); | |
516 | |
517 /* check for some XMMS functions */ | |
518 xmms_playlist_get_info_going = dlsym(handle, "playlist_get_info_going"); | |
519 xmms_input_get_song_info = dlsym(handle, "input_get_song_info"); | |
520 | |
521 /* HACK: direct access to XMMS' config 'gentitle_format' */ | |
522 xmms_cfg = dlsym(handle, "cfg"); | |
523 get_gentitle_format = dlsym(handle, "xmms_get_gentitle_format"); | |
524 if (xmms_cfg && get_gentitle_format) | |
525 { | |
526 gchar *format = get_gentitle_format(); | |
527 | |
528 int i = 128; | |
529 gchar **p = (gchar **)xmms_cfg; | |
530 for (i = 128; i > 0 && *p != format; i--, p++); | |
531 if (*p == format) | |
532 xmms_gentitle_format = p; | |
533 } | |
534 | |
535 dlclose(handle); | |
536 #endif | |
537 } | |
538 | |
539 /* | |
540 HACK: Try to move ourselves to the beginning of XMMS output plugin list, | |
541 so that we will be freed first when XMMS is quitting. This way, we | |
542 avoid the segfault when using ALSA as the output plugin. | |
543 */ | |
544 static void | |
545 output_list_hack() | |
546 { | |
547 GList *output_list = xfplayer_get_output_list(); | |
548 if (!output_list) | |
549 return; | |
550 | |
551 int i0 = g_list_index(output_list, xfade_op), i1; | |
552 | |
553 GList *first = g_list_first(output_list); | |
554 GList *xfade = g_list_find(output_list, xfade_op); | |
555 xfade->data = first->data; | |
556 first->data = xfade_op; | |
557 | |
558 i1 = g_list_index(output_list, xfade_op); | |
559 if (i0 != i1) | |
560 DEBUG(("[crossfade] output_list_hack: crossfade moved from index %d to %d\n", i0, i1)); | |
561 } | |
562 | |
563 void | |
564 xfade_get_volume(int *l, int *r) | |
565 { | |
566 if (config->mixer_software) | |
567 { | |
568 *l = config->mixer_reverse | |
569 ? config->mixer_vol_right | |
570 : config->mixer_vol_left; | |
571 *r = config->mixer_reverse | |
572 ? config->mixer_vol_left | |
573 : config->mixer_vol_right; | |
574 } | |
575 else | |
576 { | |
577 if (the_op && the_op->get_volume) | |
578 { | |
579 if (config->mixer_reverse) | |
580 the_op->get_volume(r, l); | |
581 else | |
582 the_op->get_volume(l, r); | |
583 } | |
584 } | |
585 | |
586 /* DEBUG(("[crossfade] xfade_get_volume: l=%d r=%d\n", *l, *r)); */ | |
587 } | |
588 | |
589 void | |
590 xfade_set_volume(int l, int r) | |
591 { | |
592 /* DEBUG(("[crossfade] xfade_set_volume: l=%d r=%d\n", l, r)); */ | |
593 | |
594 if (!config->enable_mixer) | |
595 return; | |
596 | |
597 if (the_op && the_op->set_volume) | |
598 { | |
599 if (config->mixer_reverse) | |
600 the_op->set_volume(r, l); | |
601 else | |
602 the_op->set_volume(l, r); | |
603 } | |
604 } | |
605 | |
606 /*** buffer stuff ***********************************************************/ | |
607 | |
608 static void | |
609 buffer_mfg_reset(buffer_t *buf, config_t *cfg) | |
610 { | |
611 buf->mix = 0; | |
612 buf->fade = 0; | |
613 buf->gap = (cfg->gap_lead_enable ? MS2B(cfg->gap_lead_len_ms) & -4 : 0); | |
614 buf->gap_len = buf->gap; | |
615 buf->gap_level = cfg->gap_lead_level; | |
616 buf->gap_killed = 0; | |
617 buf->skip = 0; | |
618 buf->skip_len = 0; | |
619 } | |
620 | |
621 static void | |
622 buffer_reset(buffer_t *buf, config_t *cfg) | |
623 { | |
624 buffer_mfg_reset(buf, cfg); | |
625 | |
626 buf->rd_index = 0; | |
627 buf->used = 0; | |
628 buf->preload = buf->preload_size; | |
629 | |
630 buf->silence = 0; | |
631 buf->silence_len = 0; | |
632 buf->reopen = -1; | |
633 buf->pause = -1; | |
634 } | |
635 | |
636 /****************************************************************************/ | |
637 | |
638 static void | |
639 xfade_apply_fade_config(fade_config_t *fc) | |
640 { | |
641 gint out_skip, in_skip; | |
642 gint avail, out_len, in_len, offset, preload; | |
643 gint index, length, fade, n; | |
644 gfloat out_scale, in_scale; | |
645 gboolean out_skip_clipped = FALSE; | |
646 gboolean out_len_clipped = FALSE; | |
647 gboolean offset_clipped = FALSE; | |
648 | |
649 /* Overwrites mix and fade; may add silence */ | |
650 | |
651 /* | |
652 * Example 1: offset < 0 --> mix streams together | |
653 * Example 2: offset > 0 --> insert pause between streams | |
654 * | |
655 * |----- out_len -----| * |out_len| | |
656 * | | * | | | |
657 * ~~~~~-_ /T~~~~~~~T~~ * ~~~~~\ | /T~~ | |
658 * ~-_ / | | * \ | / | | |
659 * ~-_/ | | * \ | / | | |
660 * /~-_| | * \ | / | | |
661 * / T-_ | * \ | / | | |
662 * / | ~-_ | * \ | / | | |
663 * _________/______|_____~-|__ * ___________\__________/______|__ | |
664 * |in_len| | * | |in_len| | |
665 * |<-- offset ---| * |offset-->| | |
666 * | |
667 * a) avail: max(0, used - preload) | |
668 * b) out_len: 0 .. avail | |
669 * c) in_len: 0 .. # | |
670 * d) offset: -avail .. buffer->mix_size - out_size | |
671 * e) skip: min(used, preload) | |
672 * | |
673 */ | |
674 | |
675 out_scale = 1.0f - (gfloat) xfade_cfg_fadeout_volume(fc) / 100.0f; | |
676 in_scale = 1.0f - (gfloat) xfade_cfg_fadein_volume (fc) / 100.0f; | |
677 | |
678 /* rules (see above) */ | |
679 /* a: leave preload untouched */ | |
680 avail = buffer->used - buffer->preload_size; | |
681 if (avail < 0) | |
682 avail = 0; | |
683 | |
684 /* skip end of song */ | |
685 out_skip = MS2B(xfade_cfg_out_skip(fc)) & -4; | |
686 if (out_skip > avail) | |
687 { | |
688 DEBUG(("[crossfade] apply_fade_config: WARNING: clipping out_skip (%d -> %d)!\n", B2MS(out_skip), B2MS(avail))); | |
689 out_skip = avail; | |
690 out_skip_clipped = TRUE; | |
691 } | |
692 | |
693 if (out_skip > 0) | |
694 { | |
695 buffer->used -= out_skip; | |
696 avail -= out_skip; | |
697 } | |
698 | |
699 /* b: fadeout */ | |
700 out_len = MS2B(xfade_cfg_fadeout_len(fc)) & -4; | |
701 if (out_len > avail) | |
702 { | |
703 DEBUG(("[crossfade] apply_fade_config: WARNING: clipping out_len (%d -> %d)!\n", B2MS(out_len), B2MS(avail))); | |
704 out_len = avail; | |
705 out_len_clipped = TRUE; | |
706 } | |
707 else if (out_len < 0) | |
708 out_len = 0; | |
709 | |
710 /* skip beginning of song */ | |
711 in_skip = MS2B(xfade_cfg_in_skip(fc)) & -4; | |
712 if (in_skip < 0) | |
713 in_skip = 0; | |
714 | |
715 /* c: fadein */ | |
716 in_len = MS2B(xfade_cfg_fadein_len(fc)) & -4; | |
717 if (in_len < 0) | |
718 in_len = 0; | |
719 | |
720 /* d: offset (mixing point) */ | |
721 offset = MS2B(xfade_cfg_offset(fc)) & -4; | |
722 if (offset < -avail) | |
723 { | |
724 DEBUG(("[crossfade] apply_fade_config: WARNING: clipping offset (%d -> %d)!\n", B2MS(offset), -B2MS(avail))); | |
725 offset = -avail; | |
726 offset_clipped = TRUE; | |
727 } | |
728 if (offset > (buffer->mix_size - out_len)) | |
729 offset = buffer->mix_size - out_len; | |
730 | |
731 /* e */ | |
732 preload = buffer->preload_size; | |
733 if (preload > buffer->used) | |
734 preload = buffer->used; | |
735 | |
736 /* cut off rest of stream (decreases latency on manual songchange) */ | |
737 if (fc->flush) | |
738 { | |
739 gint cutoff = avail - MAX(out_len, -offset); /* MAX() -> glib.h */ | |
740 if (cutoff > 0) | |
741 { | |
742 DEBUG(("[crossfade] apply_fade_config: %d ms flushed\n", B2MS(cutoff))); | |
743 buffer->used -= cutoff; | |
744 avail -= cutoff; | |
745 } | |
746 | |
747 /* make sure there is no pending silence */ | |
748 buffer->silence = 0; | |
749 buffer->silence_len = 0; | |
750 } | |
751 | |
752 /* begin modifying buffer at index */ | |
753 index = (buffer->rd_index + buffer->used - out_len) % buffer->size; | |
754 | |
755 /* fade out (modifies buffer directly) */ | |
756 fade = 0; | |
757 length = out_len; | |
758 while (length > 0) | |
759 { | |
760 gint16 *p = buffer->data + index; | |
761 gint blen = buffer->size - index; | |
762 if (blen > length) blen = length; | |
763 | |
764 for (n = blen / 4; n > 0; n--) | |
765 { | |
766 gfloat factor = 1.0f - (((gfloat) fade / out_len) * out_scale); | |
767 *p = (gfloat)*p * factor; p++; | |
768 *p = (gfloat)*p * factor; p++; | |
769 fade += 4; | |
770 } | |
771 | |
772 index = (index + blen) % buffer->size; | |
773 length -= blen; | |
774 } | |
775 | |
776 /* Initialize fadein. Note that the actual fading / mixing will be done | |
777 * on-the-fly when audio data is received by xfade_write_audio() */ | |
778 | |
779 /* start skipping */ | |
780 if (in_skip > 0) | |
781 { | |
782 buffer->skip = in_skip; | |
783 buffer->skip_len = in_skip; | |
784 } | |
785 else | |
786 buffer->skip = 0; | |
787 | |
788 /* start fading in */ | |
789 if (in_len > 0) | |
790 { | |
791 buffer->fade = in_len; | |
792 buffer->fade_len = in_len; | |
793 buffer->fade_scale = in_scale; | |
794 } | |
795 else | |
796 buffer->fade = 0; | |
797 | |
798 /* start mixing */ | |
799 if (offset < 0) | |
800 { | |
801 length = -offset; | |
802 buffer->mix = length; | |
803 buffer->used -= length; | |
804 } | |
805 else | |
806 buffer->mix = 0; | |
807 | |
808 /* start silence if applicable (will be applied in buffer_thread_f) */ | |
809 if (offset > 0) | |
810 { | |
811 if ((buffer->silence > 0) || (buffer->silence_len > 0)) | |
812 DEBUG(("[crossfade] apply_config: WARNING: silence in progress (%d/%d ms)\n", | |
813 B2MS(buffer->silence), B2MS(buffer->silence_len))); | |
814 | |
815 buffer->silence = buffer->used; | |
816 buffer->silence_len = offset; | |
817 } | |
818 | |
819 /* done */ | |
820 if (in_skip || out_skip) | |
821 DEBUG(("[crossfade] apply_fade_config: out_skip=%d in_skip=%d\n", B2MS(out_skip), B2MS(in_skip))); | |
822 DEBUG(("[crossfade] apply_fade_config: avail=%d out=%d in=%d offset=%d preload=%d\n", | |
823 B2MS(avail), B2MS(out_len), B2MS(in_len), B2MS(offset), B2MS(preload))); | |
824 } | |
825 | |
826 static gint | |
827 extract_track(const gchar *name) | |
828 { | |
829 #if 1 | |
830 /* skip non-digits at beginning */ | |
831 while (*name && !isdigit(*name)) | |
832 name++; | |
833 | |
834 return atoi(name); | |
835 #else | |
836 /* Remove all but numbers. | |
837 * Will not work if a filename has number in the title, like "track-03-U2.mp3" | |
838 * Ideally, should look into id3 track entry and fallback to filename | |
839 * */ | |
840 gchar temp[8]; | |
841 int t = 0; | |
842 | |
843 memset(temp, 0, sizeof(temp)); | |
844 while (*name != '\0' && t < sizeof(temp)) | |
845 { | |
846 if (strcmp(name, "mp3") == 0) | |
847 break; | |
848 | |
849 if (isdigit(*name)) | |
850 temp[t++] = *name; | |
851 | |
852 name++; | |
853 } | |
854 return atoi(temp); | |
855 #endif | |
856 } | |
857 | |
858 static gint | |
859 album_match(gchar *old, gchar *new) | |
860 { | |
861 gchar *old_dir, *new_dir; | |
862 gboolean same_dir; | |
863 gint old_track = 0, new_track = 0; | |
864 | |
865 if (!old || !new) | |
866 return 0; | |
867 | |
868 old_dir = g_dirname(old); | |
869 new_dir = g_dirname(new); | |
870 same_dir = !strcmp(old_dir, new_dir); | |
871 g_free(old_dir); | |
872 g_free(new_dir); | |
873 | |
874 if (!same_dir) | |
875 { | |
876 DEBUG(("[crossfade] album_match: no match (different dirs)\n")); | |
877 return 0; | |
878 } | |
879 | |
880 old_track = extract_track(g_basename(old)); | |
881 new_track = extract_track(g_basename(new)); | |
882 | |
883 if (new_track <= 0) | |
884 { | |
885 DEBUG(("[crossfade] album_match: can't parse track number:\n")); | |
886 DEBUG(("[crossfade] album_match: ... \"%s\"\n", g_basename(new))); | |
887 return 0; | |
888 } | |
889 | |
890 if ((old_track < 0) || (old_track + 1 != new_track)) | |
891 { | |
892 DEBUG(("[crossfade] album_match: no match (same dir, but non-successive (%d, %d))\n", old_track, new_track)); | |
893 return 0; | |
894 } | |
895 | |
896 DEBUG(("[crossfade] album_match: match detected (same dir, successive tracks (%d, %d))\n", old_track, new_track)); | |
897 | |
898 return old_track; | |
899 } | |
900 | |
901 static gint | |
902 xfade_open_audio(AFormat fmt, int rate, int nch) | |
903 { | |
904 gint pos; | |
905 gchar *file, *title, *comment; | |
906 #if defined(HAVE_ID3LIB) | |
907 id3_t id3; | |
908 #endif | |
909 | |
910 struct timeval tv; | |
911 glong dt; | |
912 | |
913 DEBUG(("[crossfade]\n")); | |
914 DEBUG(("[crossfade] open_audio: pid=%d\n", (int) getpid())); | |
915 | |
916 /* sanity... don't do anything about it */ | |
917 if (opened) | |
918 DEBUG(("[crossfade] open_audio: WARNING: already opened!\n")); | |
919 | |
920 /* get filename */ | |
921 pos = xfplaylist_get_position (); | |
922 file = xfplaylist_get_filename (pos); | |
923 title = xfplaylist_get_songtitle(pos); | |
924 comment = playlist_get_fadeinfo ? playlist_get_fadeinfo(pos) : NULL; | |
925 | |
926 if (!file) | |
927 file = g_strdup(title); | |
928 | |
929 DEBUG(("[crossfade] open_audio: bname=\"%s\"\n", g_basename(file))); | |
930 DEBUG(("[crossfade] open_audio: title=\"%s\"\n", title)); | |
931 | |
932 #if 0 | |
933 /* HACK: try to get comment and track number from xmms by sneaking in a custom title format */ | |
934 if (xmms_gentitle_format) | |
935 { | |
936 gchar *old_gentitle_format = *xmms_gentitle_format; | |
937 | |
938 gchar *temp_title = NULL; | |
939 gint temp_length = 0; | |
940 | |
941 xmms_input_get_song_info(file, &temp_title, &temp_length); | |
942 DEBUG(("[crossfade] open_audio: TITLE: %s\n", temp_title)); | |
943 g_free(temp_title); | |
944 | |
945 *xmms_gentitle_format = "%n/%c"; | |
946 | |
947 xmms_input_get_song_info(file, &temp_title, &temp_length); | |
948 DEBUG(("[crossfade] open_audio: TRACK/COMMENT: %s\n", temp_title)); | |
949 g_free(temp_title); | |
950 | |
951 *xmms_gentitle_format = old_gentitle_format; | |
952 } | |
953 #endif | |
954 | |
955 /* try to read comment from ID3 tag */ | |
956 #if defined(HAVE_ID3LIB) | |
957 if (!comment && get_id3(file, &id3)) | |
958 comment = g_strdup(id3.comment); | |
959 #endif | |
960 | |
961 if (comment) | |
962 DEBUG(("[crossfade] open_audio: comment=\"%s\"\n", comment)) | |
963 else | |
964 DEBUG(("[crossfade] open_audio: comment=NULL\n")); | |
965 | |
966 /* is this an automatic crossfade? */ | |
967 if (last_filename && (fade_config == &config->fc[FADE_CONFIG_XFADE])) | |
968 { | |
969 /* check if next song is the same as the current one */ | |
970 if (config->no_xfade_if_same_file && !strcmp(last_filename, file)) | |
971 { | |
972 DEBUG(("[crossfade] open_audio: same file, disabling crossfade\n")); | |
973 fade_config = &config->fc[FADE_CONFIG_ALBUM]; | |
974 } | |
975 | |
976 /* check if next song is the next song from the same album */ | |
977 else if (config->album_detection && album_match(last_filename, file)) | |
978 { | |
979 gboolean use_fc_album = FALSE; | |
980 | |
981 if (xfade_cfg_gap_trail_enable(config)) | |
982 { | |
983 DEBUG(("[crossfade] album_match: " | |
984 "trailing gap: length=%d/%d ms\n", B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); | |
985 | |
986 if (buffer->gap_killed < buffer->gap_len) | |
987 { | |
988 DEBUG(("[crossfade] album_match: " | |
989 "trailing gap: -> no silence, probably pre-faded\n")); | |
990 use_fc_album = TRUE; | |
991 } | |
992 else | |
993 { | |
994 DEBUG(("[crossfade] album_match: " "trailing gap: -> silence, sticking to XFADE\n")); | |
995 } | |
996 } | |
997 else | |
998 { | |
999 DEBUG(("[crossfade] album_match: " "trailing gap killer disabled\n")); | |
1000 use_fc_album = TRUE; | |
1001 } | |
1002 | |
1003 if (use_fc_album) | |
1004 { | |
1005 DEBUG(("[crossfade] album_match: " "-> using FADE_CONFIG_ALBUM\n")); | |
1006 fade_config = &config->fc[FADE_CONFIG_ALBUM]; | |
1007 } | |
1008 } | |
1009 } | |
1010 g_free(last_filename); | |
1011 last_filename = g_strdup(file); | |
1012 | |
1013 #if 0 | |
1014 /* FIXME: finish this */ | |
1015 /* Check if this is a short song. */ | |
1016 if (fade_config == &config->fc[FADE_CONFIG_XFADE]) | |
1017 { | |
1018 DEBUG(("*** XFADE:\n")); | |
1019 int current_length = playlist_get_current_length(); | |
1020 DEBUG(("*** length=%d\n", current_length)); | |
1021 if (current_length < 30 * 1000) | |
1022 fade_config = &config->fc[FADE_CONFIG_ALBUM]; | |
1023 } | |
1024 #endif | |
1025 | |
1026 #ifdef TIMING_COMMENTS | |
1027 last_timing = current_timing; | |
1028 current_timing. in.enable = FALSE; | |
1029 current_timing.out.enable = FALSE; | |
1030 if (comment) | |
1031 { | |
1032 gchar *str; | |
1033 if ((str = strstr(comment, "fadein="))) | |
1034 { | |
1035 current_timing.in.enable = | |
1036 (3 == sscanf(str + 7, "%d,%d,%d", | |
1037 ¤t_timing.in.len_ms, | |
1038 ¤t_timing.in.volume, | |
1039 ¤t_timing.in.skip_ms)); | |
1040 current_timing.in.ofs_ms = 0; /* not used */ | |
1041 } | |
1042 | |
1043 if ((str = strstr(comment, "fadeout="))) | |
1044 { | |
1045 current_timing.out.enable = | |
1046 (4 == sscanf(str + 8, "%d,%d,%d,%d", | |
1047 ¤t_timing.out.len_ms, | |
1048 ¤t_timing.out.volume, | |
1049 ¤t_timing.out.skip_ms, | |
1050 ¤t_timing.out.ofs_ms)); | |
1051 } | |
1052 } | |
1053 | |
1054 #if 0 | |
1055 // | |
1056 // use the fade info only on a regular, non-manual songchange | |
1057 // | |
1058 if (fade_config && !((fade_config->config == FADE_CONFIG_XFADE) || | |
1059 (fade_config->config == FADE_CONFIG_ALBUM))) | |
1060 last_timing.out.enable = FALSE; | |
1061 #endif | |
1062 | |
1063 #if 1 | |
1064 if (last_timing.out.enable || current_timing.in.enable) | |
1065 #else | |
1066 if ((last_timing.out.enable || current_timing.in.enable) && | |
1067 (!fade_config || | |
1068 (fade_config->config == FADE_CONFIG_XFADE) || | |
1069 (fade_config->config == FADE_CONFIG_ALBUM))) | |
1070 #endif | |
1071 { | |
1072 config->fc[FADE_CONFIG_TIMING].out_len_ms = xfade_cfg_fadeout_len (fade_config); | |
1073 config->fc[FADE_CONFIG_TIMING].out_volume = xfade_cfg_fadeout_volume(fade_config); | |
1074 config->fc[FADE_CONFIG_TIMING].out_skip_ms = 0; | |
1075 config->fc[FADE_CONFIG_TIMING].ofs_custom_ms = xfade_cfg_offset (fade_config); | |
1076 config->fc[FADE_CONFIG_TIMING].in_skip_ms = 0; | |
1077 config->fc[FADE_CONFIG_TIMING].in_len_ms = xfade_cfg_fadein_len (fade_config); | |
1078 config->fc[FADE_CONFIG_TIMING].in_volume = xfade_cfg_fadein_volume (fade_config); | |
1079 config->fc[FADE_CONFIG_TIMING].flush = fade_config && fade_config->flush; | |
1080 | |
1081 if (last_timing.out.enable && current_timing.in.enable) | |
1082 config->fc[FADE_CONFIG_TIMING].ofs_custom_ms = 0; | |
1083 | |
1084 if (last_timing.out.enable) | |
1085 { | |
1086 DEBUG(("[crossfade] open_audio: TIMING: out: enable=%d len=%d volume=%d skip=%d ofs=%d\n", | |
1087 last_timing.out.enable, | |
1088 last_timing.out.len_ms, | |
1089 last_timing.out.volume, | |
1090 last_timing.out.skip_ms, | |
1091 last_timing.out.ofs_ms)); | |
1092 | |
1093 config->fc[FADE_CONFIG_TIMING].out_len_ms = last_timing.out.len_ms; | |
1094 config->fc[FADE_CONFIG_TIMING].out_volume = last_timing.out.volume; | |
1095 config->fc[FADE_CONFIG_TIMING].out_skip_ms = last_timing.out.skip_ms; | |
1096 config->fc[FADE_CONFIG_TIMING].ofs_custom_ms += last_timing.out.ofs_ms; | |
1097 } | |
1098 if (current_timing.in.enable) | |
1099 { | |
1100 DEBUG(("[crossfade] open_audio: TIMING: in: enable=%d len=%d volume=%d skip=%d\n", | |
1101 current_timing.in.enable, | |
1102 current_timing.in.len_ms, | |
1103 current_timing.in.volume, | |
1104 current_timing.in.skip_ms)); | |
1105 | |
1106 config->fc[FADE_CONFIG_TIMING].in_skip_ms = current_timing.in.skip_ms; | |
1107 config->fc[FADE_CONFIG_TIMING].in_len_ms = current_timing.in.len_ms; | |
1108 config->fc[FADE_CONFIG_TIMING].in_volume = current_timing.in.volume; | |
1109 config->fc[FADE_CONFIG_TIMING].ofs_custom_ms -= current_timing.in.ofs_ms; | |
1110 /* NOTE: in.ofs_ms is currently not used always 0 */ | |
1111 } | |
1112 | |
1113 fade_config = &config->fc[FADE_CONFIG_TIMING]; | |
1114 } | |
1115 #endif | |
1116 | |
1117 /* cleanup */ | |
1118 g_free(file); file = NULL; | |
1119 g_free(title); title = NULL; | |
1120 g_free(comment); comment = NULL; | |
1121 | |
1122 /* check for HTTP streaming */ | |
1123 if (config->enable_http_workaround && (0 == strncasecmp(file, "http://", 7))) | |
1124 { | |
1125 DEBUG(("[crossfade] open_audio: HTTP underrun workaround enabled.\n")); | |
1126 is_http = TRUE; | |
1127 } | |
1128 else | |
1129 is_http = FALSE; | |
1130 | |
1131 /* lock buffer */ | |
1132 MUTEX_LOCK(&buffer_mutex); | |
1133 | |
1134 /* reset writer timeout */ | |
1135 gettimeofday(&last_write, NULL); | |
1136 | |
1137 /* calculate time since last close() (don't care about overflows at 24h) */ | |
1138 if (output_opened) | |
1139 { | |
1140 gettimeofday(&tv, NULL); | |
1141 dt = (tv.tv_sec - last_close.tv_sec) * 1000 + (tv.tv_usec - last_close.tv_usec) / 1000; | |
1142 } | |
1143 else | |
1144 dt = 0; | |
1145 | |
1146 DEBUG(("[crossfade] open_audio: fmt=%s rate=%d nch=%d dt=%ld ms\n", format_name(fmt), rate, nch, dt)); | |
1147 | |
1148 /* check format */ | |
1149 if (setup_format(fmt, rate, nch, &in_format) < 0) | |
1150 { | |
1151 DEBUG(("[crossfade] open_audio: format not supported!\n")); | |
1152 return 0; | |
1153 } | |
1154 | |
1155 /* (re)open the device if necessary */ | |
1156 if (!output_opened) | |
1157 { | |
1158 if (open_output()) | |
1159 { | |
1160 DEBUG(("[crossfade] open_audio: error opening/configuring output!\n")); | |
1161 MUTEX_UNLOCK(&buffer_mutex); | |
1162 return 0; | |
1163 } | |
1164 fade_config = &config->fc[FADE_CONFIG_START]; | |
1165 } | |
1166 | |
1167 /* reset */ | |
1168 streampos = 0; | |
1169 playing = TRUE; | |
1170 opened = TRUE; | |
1171 paused = FALSE; | |
1172 | |
1173 /* reset mix/fade/gap */ | |
1174 buffer_mfg_reset(buffer, config); | |
1175 | |
1176 /* enable gap killer / zero crossing only for automatic/album songchange */ | |
1177 switch (fade_config->config) | |
1178 { | |
1179 case FADE_CONFIG_XFADE: | |
1180 case FADE_CONFIG_ALBUM: | |
1181 break; | |
1182 | |
1183 default: | |
1184 buffer->gap = GAP_SKIPPING_DONE; | |
1185 } | |
1186 | |
1187 /* restart realtime throttling */ | |
1188 output_written = 0; | |
1189 | |
1190 /* start mixing */ | |
1191 switch (fade_config ? fade_config->type : -1) | |
1192 { | |
1193 case FADE_TYPE_FLUSH: | |
1194 DEBUG(("[crossfade] open_audio: FLUSH:\n")); | |
1195 | |
1196 /* flush output plugin */ | |
1197 the_op->flush(0); | |
1198 output_streampos = 0; | |
1199 | |
1200 /* flush buffer */ | |
1201 buffer_reset(buffer, config); | |
1202 | |
1203 /* apply fade config (pause/fadein after flush) */ | |
1204 xfade_apply_fade_config(fade_config); | |
1205 | |
1206 /* also repopen device (if configured so in the plugin compat. options) */ | |
1207 if (the_op_config.force_reopen) | |
1208 { | |
1209 buffer->reopen = 0; | |
1210 buffer->reopen_sync = FALSE; | |
1211 } | |
1212 break; | |
1213 | |
1214 case FADE_TYPE_REOPEN: | |
1215 DEBUG(("[crossfade] open_audio: REOPEN:\n")); | |
1216 | |
1217 /* flush buffer if applicable */ | |
1218 if (fade_config->flush) | |
1219 buffer_reset(buffer, config); | |
1220 | |
1221 if (buffer->reopen >= 0) | |
1222 DEBUG(("[crossfade] open_audio: REOPEN: WARNING: reopen in progress (%d ms)\n", | |
1223 B2MS(buffer->reopen))); | |
1224 | |
1225 /* start reopen countdown (will be executed in buffer_thread_f) */ | |
1226 buffer->reopen = buffer->used; /* may be 0 */ | |
1227 buffer->reopen_sync = FALSE; | |
1228 break; | |
1229 | |
1230 case FADE_TYPE_NONE: | |
1231 case FADE_TYPE_PAUSE: | |
1232 case FADE_TYPE_SIMPLE_XF: | |
1233 case FADE_TYPE_ADVANCED_XF: | |
1234 case FADE_TYPE_FADEIN: | |
1235 case FADE_TYPE_FADEOUT: | |
1236 DEBUG(("[crossfade] open_audio: XFADE:\n")); | |
1237 | |
1238 /* apply fade config (do fadeout, init mix/fade/gap, add silence) */ | |
1239 xfade_apply_fade_config(fade_config); | |
1240 | |
1241 /* set reopen countdown. after buffer_thread_f has written | |
1242 * buffer->reopen bytes, it will close/reopen the output plugin. */ | |
1243 if (the_op_config.force_reopen && !(fade_config->config == FADE_CONFIG_START)) | |
1244 { | |
1245 if (buffer->reopen >= 0) | |
1246 DEBUG(("[crossfade] open_audio: XFADE: WARNING: reopen in progress (%d ms)\n", | |
1247 B2MS(buffer->reopen))); | |
1248 buffer->reopen = buffer->used; | |
1249 buffer->reopen_sync = TRUE; | |
1250 } | |
1251 break; | |
1252 } | |
1253 | |
1254 /* calculate offset of the output plugin */ | |
1255 output_offset = the_op->written_time() + B2MS(buffer->used) + B2MS(buffer->silence_len); | |
1256 | |
1257 /* unlock buffer */ | |
1258 MUTEX_UNLOCK(&buffer_mutex); | |
1259 | |
1260 /* done */ | |
1261 return 1; | |
1262 } | |
1263 | |
1264 void | |
1265 xfade_write_audio(void *ptr, int length) | |
1266 { | |
1267 gint free; | |
1268 gint ofs = 0; | |
1269 format_t format; | |
1270 | |
1271 #ifdef DEBUG_HARDCORE | |
1272 DEBUG(("[crossfade] write_audio: ptr=0x%08lx, length=%d\n", (long) ptr, length)); | |
1273 #endif | |
1274 | |
1275 /* sanity */ | |
1276 if (length <= 0) | |
1277 return; | |
1278 | |
1279 if (length & 3) | |
1280 { | |
1281 DEBUG(("[crossfade] write_audio: truncating %d bytes!\n", length & 3)); | |
1282 length &= -4; | |
1283 } | |
1284 | |
1285 /* update input accumulator (using input format size) */ | |
1286 streampos += length; | |
1287 | |
1288 /* convert sample format (signed-16bit-ne 44100hz stereo) */ | |
1289 format_copy(&format, &in_format); | |
1290 length = convert_flow(&convert_context, (gpointer *) &ptr, length, &format); | |
1291 | |
1292 /* lock buffer */ | |
1293 MUTEX_LOCK(&buffer_mutex); | |
1294 | |
1295 /* check if device has been closed, reopen if necessary */ | |
1296 if (!output_opened) | |
1297 { | |
1298 if (open_output()) | |
1299 { | |
1300 DEBUG(("[crossfade] write_audio: reopening failed!\n")); | |
1301 MUTEX_UNLOCK(&buffer_mutex); | |
1302 return; | |
1303 } | |
1304 } | |
1305 | |
1306 /* reset timeout */ | |
1307 gettimeofday(&last_write, NULL); | |
1308 | |
1309 /* calculate free buffer space, check for overflow (should never happen :) */ | |
1310 free = buffer->size - buffer->used; | |
1311 if (length > free) | |
1312 { | |
1313 DEBUG(("[crossfade] write_audio: %d bytes truncated!\n", length - free)); | |
1314 length = free; | |
1315 } | |
1316 | |
1317 /* skip beginning of song */ | |
1318 if ((length > 0) && (buffer->skip > 0)) | |
1319 { | |
1320 gint blen = MIN(length, buffer->skip); | |
1321 | |
1322 buffer->skip -= blen; | |
1323 length -= blen; | |
1324 ptr += blen; | |
1325 } | |
1326 | |
1327 /* kill leading gap */ | |
1328 if ((length > 0) && (buffer->gap > 0)) | |
1329 { | |
1330 gint blen = MIN(length, buffer->gap); | |
1331 gint16 *p = ptr; | |
1332 gint index = 0; | |
1333 | |
1334 gint16 left, right; | |
1335 while (index < blen) | |
1336 { | |
1337 left = *p++, right = *p++; | |
1338 if (ABS(left) >= buffer->gap_level) break; | |
1339 if (ABS(right) >= buffer->gap_level) break; | |
1340 index += 4; | |
1341 } | |
1342 | |
1343 buffer->gap -= index; | |
1344 length -= index; | |
1345 ptr += index; | |
1346 | |
1347 if ((index < blen) || (buffer->gap <= 0)) | |
1348 { | |
1349 buffer->gap_killed = buffer->gap_len - buffer->gap; | |
1350 buffer->gap = 0; | |
1351 | |
1352 DEBUG(("[crossfade] write_audio: leading gap size: %d/%d ms\n", | |
1353 B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); | |
1354 | |
1355 /* fix streampos */ | |
1356 streampos -= (gint64) buffer->gap_killed * in_format.bps / out_format.bps; | |
1357 } | |
1358 } | |
1359 | |
1360 /* start skipping to next crossing (if enabled) */ | |
1361 if (buffer->gap == 0) | |
1362 { | |
1363 if (config->gap_crossing) | |
1364 { | |
1365 buffer->gap = GAP_SKIPPING_POSITIVE; | |
1366 buffer->gap_skipped = 0; | |
1367 } | |
1368 else | |
1369 buffer->gap = GAP_SKIPPING_DONE; | |
1370 } | |
1371 | |
1372 /* skip until next zero crossing (pos -> neg) */ | |
1373 if ((length > 0) && (buffer->gap == GAP_SKIPPING_POSITIVE)) | |
1374 { | |
1375 gint16 *p = ptr; | |
1376 gint index = 0; | |
1377 | |
1378 gint16 left; | |
1379 while (index < length) | |
1380 { | |
1381 left = *p++; | |
1382 p++; | |
1383 if (left < 0) | |
1384 break; | |
1385 index += 4; | |
1386 } | |
1387 | |
1388 buffer->gap_skipped += index; | |
1389 length -= index; | |
1390 ptr += index; | |
1391 | |
1392 if (index < length) | |
1393 buffer->gap = GAP_SKIPPING_NEGATIVE; | |
1394 } | |
1395 | |
1396 /* skip until next zero crossing (neg -> pos) */ | |
1397 if ((length > 0) && (buffer->gap == GAP_SKIPPING_NEGATIVE)) | |
1398 { | |
1399 gint16 *p = ptr; | |
1400 gint index = 0; | |
1401 | |
1402 gint16 left; | |
1403 while (index < length) | |
1404 { | |
1405 left = *p++; | |
1406 p++; | |
1407 if (left >= 0) | |
1408 break; | |
1409 index += 4; | |
1410 } | |
1411 | |
1412 buffer->gap_skipped += index; | |
1413 length -= index; | |
1414 ptr += index; | |
1415 | |
1416 if (index < length) | |
1417 { | |
1418 DEBUG(("[crossfade] write_audio: %d samples to next crossing\n", buffer->gap_skipped)); | |
1419 buffer->gap = GAP_SKIPPING_DONE; | |
1420 } | |
1421 } | |
1422 | |
1423 /* update preload. the buffer thread will not write any | |
1424 * data to the device before preload is decreased below 1. */ | |
1425 if ((length > 0) && (buffer->preload > 0)) | |
1426 buffer->preload -= length; | |
1427 | |
1428 /* fadein -- FIXME: is modifying the input/effect buffer safe? */ | |
1429 if ((length > 0) && (buffer->fade > 0)) | |
1430 { | |
1431 gint16 *p = ptr; | |
1432 gint blen = MIN(length, buffer->fade); | |
1433 gint n; | |
1434 | |
1435 for (n = blen / 4; n > 0; n--) | |
1436 { | |
1437 gfloat factor = 1.0f - (((gfloat) buffer->fade / buffer->fade_len) * buffer->fade_scale); | |
1438 *p = (gfloat)*p * factor; p++; | |
1439 *p = (gfloat)*p * factor; p++; | |
1440 buffer->fade -= 4; | |
1441 } | |
1442 } | |
1443 | |
1444 /* mix */ | |
1445 while ((length > 0) && (buffer->mix > 0)) | |
1446 { | |
1447 gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; | |
1448 gint blen = buffer->size - wr_index; | |
1449 gint16 *p1 = buffer->data + wr_index; | |
1450 gint16 *p2 = ptr + ofs; | |
1451 gint n; | |
1452 | |
1453 if (blen > length) blen = length; | |
1454 if (blen > buffer->mix) blen = buffer->mix; | |
1455 | |
1456 for (n = blen / 2; n > 0; n--) | |
1457 { | |
1458 gint out = (gint)*p1 + *p2++; /* add */ | |
1459 if (out > 32767) /* clamp */ | |
1460 *p1++ = 32767; | |
1461 else if (out < -32768) | |
1462 *p1++ = -32768; | |
1463 else | |
1464 *p1++ = out; | |
1465 } | |
1466 | |
1467 buffer->used += blen; | |
1468 buffer->mix -= blen; | |
1469 length -= blen; | |
1470 ofs += blen; | |
1471 } | |
1472 | |
1473 /* normal write */ | |
1474 while (length > 0) | |
1475 { | |
1476 gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; | |
1477 gint blen = buffer->size - wr_index; | |
1478 | |
1479 if (blen > length) | |
1480 blen = length; | |
1481 | |
1482 memcpy(buffer->data + wr_index, ptr + ofs, blen); | |
1483 | |
1484 buffer->used += blen; | |
1485 length -= blen; | |
1486 ofs += blen; | |
1487 } | |
1488 | |
1489 /* unlock buffer */ | |
1490 MUTEX_UNLOCK(&buffer_mutex); | |
1491 #ifdef DEBUG_HARDCORE | |
1492 DEBUG(("[crossfade] write_audio: done.\n")); | |
1493 #endif | |
1494 } | |
1495 | |
1496 /* sync_output: wait for output plugin to finish playback */ | |
1497 /* is only called from within buffer_thread_f */ | |
1498 static void | |
1499 sync_output() | |
1500 { | |
1501 glong dt, total; | |
1502 gint opt, opt_last; | |
1503 struct timeval tv, tv_start, tv_last_change; | |
1504 gboolean was_closed = !opened; | |
1505 | |
1506 if (!the_op->buffer_playing || !the_op->buffer_playing()) | |
1507 { | |
1508 DEBUG(("[crossfade] sync_output: nothing to do\n")); | |
1509 return; | |
1510 } | |
1511 | |
1512 DEBUG(("[crossfade] sync_output: waiting for plugin...\n")); | |
1513 | |
1514 dt = 0; | |
1515 opt_last = 0; | |
1516 gettimeofday(&tv_start, NULL); | |
1517 gettimeofday(&tv_last_change, NULL); | |
1518 | |
1519 while ((dt < SYNC_OUTPUT_TIMEOUT) | |
1520 && !stopped && output_opened && !(was_closed && opened) && the_op && the_op->buffer_playing()) | |
1521 { | |
1522 | |
1523 /* use output_time() to check if the output plugin is still active */ | |
1524 if (the_op->output_time) | |
1525 { | |
1526 opt = the_op->output_time(); | |
1527 if (opt != opt_last) | |
1528 { | |
1529 /* output_time has changed */ | |
1530 opt_last = opt; | |
1531 gettimeofday(&tv_last_change, NULL); | |
1532 } | |
1533 else | |
1534 { | |
1535 /* calculate time since last change of the_op->output_time() */ | |
1536 gettimeofday(&tv, NULL); | |
1537 dt = (tv.tv_sec - tv_last_change.tv_sec) * 1000 + (tv.tv_usec - tv_last_change.tv_usec) / 1000; | |
1538 } | |
1539 } | |
1540 | |
1541 /* yield */ | |
1542 MUTEX_UNLOCK(&buffer_mutex); | |
1543 xfade_usleep(10000); | |
1544 MUTEX_LOCK(&buffer_mutex); | |
1545 } | |
1546 | |
1547 /* calculate total time we spent in here */ | |
1548 gettimeofday(&tv, NULL); | |
1549 total = (tv.tv_sec - tv_start.tv_sec) * 1000 + (tv.tv_usec - tv_start.tv_usec) / 1000; | |
1550 | |
1551 /* print some debug info */ | |
1552 /* *INDENT-OFF* */ | |
1553 if (stopped) | |
1554 DEBUG(("[crossfade] sync_output: ... stopped\n")) | |
1555 else if (was_closed && opened) | |
1556 DEBUG(("[crossfade] sync_output: ... reopened\n")) | |
1557 else if (dt >= SYNC_OUTPUT_TIMEOUT) | |
1558 DEBUG(("[crossfade] sync_output: ... TIMEOUT! (%ld ms)\n", total)) | |
1559 else | |
1560 DEBUG(("[crossfade] sync_output: ... done (%ld ms)\n", total)); | |
1561 /* *INDENT-ON* */ | |
1562 } | |
1563 | |
1564 void * | |
1565 buffer_thread_f(void *arg) | |
1566 { | |
1567 gpointer data; | |
1568 gint sync; | |
1569 gint op_free; | |
1570 gint length_bak, length, blen; | |
1571 glong timeout, dt; | |
1572 gboolean stopping; | |
1573 | |
1574 struct timeval tv; | |
1575 struct timeval mark; | |
1576 | |
1577 DEBUG(("[crossfade] buffer_thread_f: thread started (pid=%d)\n", (int) getpid())); | |
1578 | |
1579 /* lock buffer */ | |
1580 MUTEX_LOCK(&buffer_mutex); | |
1581 | |
1582 while (!stopped) | |
1583 { | |
1584 /* yield */ | |
1585 #ifdef DEBUG_HARDCORE | |
1586 DEBUG(("[crossfade] buffer_thread_f: yielding...\n")); | |
1587 #endif | |
1588 MUTEX_UNLOCK(&buffer_mutex); | |
1589 xfade_usleep(10000); | |
1590 MUTEX_LOCK(&buffer_mutex); | |
1591 | |
1592 /* --------------------------------------------------------------------- */ | |
1593 | |
1594 stopping = FALSE; | |
1595 | |
1596 /* V0.3.0: New timeout detection */ | |
1597 if (!opened) | |
1598 { | |
1599 gboolean current = xfplayer_input_playing(); | |
1600 | |
1601 /* also see fini() */ | |
1602 if (last_close.tv_sec || last_close.tv_usec) | |
1603 { | |
1604 gettimeofday(&tv, NULL); | |
1605 timeout = (tv.tv_sec - last_close.tv_sec) * 1000 | |
1606 + (tv.tv_usec - last_close.tv_usec) / 1000; | |
1607 } | |
1608 else | |
1609 timeout = -1; | |
1610 | |
1611 if (current != input_playing) | |
1612 { | |
1613 input_playing = current; | |
1614 | |
1615 if (current) | |
1616 DEBUG(("[crossfade] buffer_thread_f: input restarted after %ld ms\n", timeout)) | |
1617 else | |
1618 DEBUG(("[crossfade] buffer_thread_f: input stopped after + %ld ms\n", timeout)); | |
1619 } | |
1620 | |
1621 /* 0.3.0: HACK: output_keep_opened: play silence during prebuffering */ | |
1622 if (input_playing && config->output_keep_opened && (buffer->used == 0)) | |
1623 { | |
1624 buffer->silence = 0; | |
1625 buffer->silence_len = MS2B(100); | |
1626 } | |
1627 | |
1628 /* 0.3.9: Check for timeout only if we have not been stopped for restart */ | |
1629 /* 0.3.11: When using the songchange hack, depend on it's output_restart | |
1630 * flag. Without the hack, use the configuration's dialog songchange | |
1631 * timeout setting instead of a fixed timeout value. */ | |
1632 if (input_stopped_for_restart && !output_restart) | |
1633 { | |
1634 if (playing) | |
1635 DEBUG(("[crossfade] buffer_thread_f: timeout:" | |
1636 " stopping after %ld ms (songchange patch)\n", timeout)); | |
1637 stopping = TRUE; | |
1638 } | |
1639 else if (((timeout < 0) || (timeout >= config->songchange_timeout && !output_restart)) && !input_playing) | |
1640 { | |
1641 if (playing) | |
1642 DEBUG(("[crossfade] buffer_thread_f: timeout:" | |
1643 " input did not restart after %ld ms\n", timeout)); | |
1644 stopping = TRUE; | |
1645 } | |
1646 } | |
1647 | |
1648 /* V0.2.4: Moved the timeout checks in front of the buffer_free() check | |
1649 * below. Before, buffer_thread_f could (theoretically) loop | |
1650 * endlessly if buffer_free() returned 0 all the time. */ | |
1651 | |
1652 /* calculate time since last write to the buffer (ignore overflows) */ | |
1653 gettimeofday(&tv, NULL); | |
1654 timeout = (tv.tv_sec - last_write.tv_sec) * 1000 | |
1655 + (tv.tv_usec - last_write.tv_usec) / 1000; | |
1656 | |
1657 /* check for timeout/eop (note this is the only way out of this loop) */ | |
1658 if (stopping) | |
1659 { | |
1660 if (playing) | |
1661 { | |
1662 DEBUG(("[crossfade] buffer_thread_f: timeout: manual stop\n")); | |
1663 | |
1664 /* if CONFIG_STOP is of TYPE_NONE, immediatelly close the device... */ | |
1665 if ((config->fc[FADE_CONFIG_STOP].type == FADE_TYPE_NONE) && !config->output_keep_opened) | |
1666 break; | |
1667 | |
1668 /* special handling for pause */ | |
1669 if (paused) | |
1670 { | |
1671 DEBUG(("[crossfade] buffer_thread_f: timeout: paused, closing now...\n")); | |
1672 paused = FALSE; | |
1673 if (config->output_keep_opened) | |
1674 the_op->pause(0); | |
1675 else | |
1676 break; | |
1677 } | |
1678 else if (buffer->pause >= 0) | |
1679 { | |
1680 DEBUG(("[crossfade] buffer_thread_f: timeout: cancelling pause countdown\n")); | |
1681 buffer->pause = -1; | |
1682 } | |
1683 | |
1684 /* ...otherwise, do the fadeout first */ | |
1685 xfade_apply_fade_config(&config->fc[FADE_CONFIG_STOP]); | |
1686 | |
1687 /* force CONFIG_START in case the user restarts playback during fadeout */ | |
1688 fade_config = &config->fc[FADE_CONFIG_START]; | |
1689 playing = FALSE; | |
1690 eop = TRUE; | |
1691 } | |
1692 else | |
1693 { | |
1694 if (!eop) | |
1695 { | |
1696 DEBUG(("[crossfade] buffer_thread_f: timeout: end of playback\n")); | |
1697 | |
1698 /* 0.3.3: undo trailing gap killer at end of playlist */ | |
1699 if (buffer->gap_killed) | |
1700 { | |
1701 buffer->used += buffer->gap_killed; | |
1702 DEBUG(("[crossfade] buffer_thread_f: timeout:" | |
1703 " undoing trailing gap (%d ms)\n", B2MS(buffer->gap_killed))); | |
1704 } | |
1705 | |
1706 /* do the fadeout if applicable */ | |
1707 if (config->fc[FADE_CONFIG_EOP].type != FADE_TYPE_NONE) | |
1708 xfade_apply_fade_config(&config->fc[FADE_CONFIG_EOP]); | |
1709 | |
1710 fade_config = &config->fc[FADE_CONFIG_START]; /* see above */ | |
1711 eop = TRUE; | |
1712 } | |
1713 | |
1714 if (buffer->used == 0) | |
1715 { | |
1716 if (config->output_keep_opened) | |
1717 { | |
1718 /* 0.3.0: play silence while keeping the output opened */ | |
1719 buffer->silence = 0; | |
1720 buffer->silence_len = MS2B(100); | |
1721 } | |
1722 else if (buffer->silence_len <= 0) | |
1723 { | |
1724 sync_output(); | |
1725 if (opened) | |
1726 { | |
1727 DEBUG(("[crossfade] buffer_thread_f: timeout, eop: device has been reopened\n")); | |
1728 DEBUG(("[crossfade] buffer_thread_f: timeout, eop: -> continuing playback\n")); | |
1729 eop = FALSE; | |
1730 } | |
1731 else | |
1732 { | |
1733 DEBUG(("[crossfade] buffer_thread_f: timeout, eop: closing output...\n")); | |
1734 break; | |
1735 } | |
1736 } | |
1737 } | |
1738 } | |
1739 } | |
1740 else | |
1741 eop = FALSE; | |
1742 | |
1743 /* --------------------------------------------------------------------- */ | |
1744 | |
1745 /* get free space in device output buffer | |
1746 * NOTE: disk_writer always returns <big int> here */ | |
1747 op_free = the_op->buffer_free() & -4; | |
1748 | |
1749 /* continue waiting if there is no room in the device buffer */ | |
1750 if (op_free == 0) | |
1751 continue; | |
1752 | |
1753 /* --- Limit OP buffer use (decreases latency) ------------------------- */ | |
1754 | |
1755 /* HACK: limit output plugin buffer usage to decrease latency */ | |
1756 if (config->enable_op_max_used) | |
1757 { | |
1758 gint output_time = the_op->output_time(); | |
1759 gint output_used = the_op->written_time() - output_time; | |
1760 gint output_limit = MS2B(config->op_max_used_ms - MIN(output_used, config->op_max_used_ms)); | |
1761 | |
1762 if (output_flush_time != output_time) | |
1763 { | |
1764 /* slow down output, but always write _some_ data */ | |
1765 if (output_limit < in_format.bps / 100 / 2) | |
1766 output_limit = in_format.bps / 100 / 2; | |
1767 | |
1768 if (op_free > output_limit) | |
1769 op_free = output_limit; | |
1770 } | |
1771 } | |
1772 | |
1773 /* --- write silence --------------------------------------------------- */ | |
1774 | |
1775 if (!paused && (buffer->silence <= 0) && (buffer->silence_len >= 4)) | |
1776 { | |
1777 /* write as much silence as a) there is left and b) the device can take */ | |
1778 length = buffer->silence_len; | |
1779 if (length > op_free) | |
1780 length = op_free; | |
1781 | |
1782 /* make sure we always operate on stereo sample boundary */ | |
1783 length &= -4; | |
1784 | |
1785 /* HACK: don't stay in here too long when in realtime mode (see below) */ | |
1786 if (realtime) | |
1787 gettimeofday(&mark, NULL); | |
1788 | |
1789 /* write length bytes to the device */ | |
1790 length_bak = length; | |
1791 while (length > 0) | |
1792 { | |
1793 data = zero_4k; | |
1794 blen = sizeof(zero_4k); | |
1795 if (blen > length) | |
1796 blen = length; | |
1797 | |
1798 /* make sure zero_4k is cleared. The effect plugin within | |
1799 * the output plugin may have modified this buffer! (0.2.8) */ | |
1800 memset(zero_4k, 0, blen); | |
1801 | |
1802 /* HACK: the original OSS plugin hangs when writing large | |
1803 * blocks (greater than device buffer size) in realtime mode */ | |
1804 if (the_op_config.max_write_enable && (blen > the_op_config.max_write_len)) | |
1805 blen = the_op_config.max_write_len; | |
1806 | |
1807 /* finally, write data */ | |
1808 the_op->write_audio(data, blen); | |
1809 length -= blen; | |
1810 | |
1811 /* HACK: don't stay in here too long (force yielding every 10 ms) */ | |
1812 if (realtime) | |
1813 { | |
1814 gettimeofday(&tv, NULL); | |
1815 dt = (tv.tv_sec - mark.tv_sec) * 1000 | |
1816 + (tv.tv_usec - mark.tv_usec) / 1000; | |
1817 if (dt >= 10) | |
1818 break; | |
1819 } | |
1820 } | |
1821 | |
1822 /* calculate how many bytes actually have been written */ | |
1823 length = length_bak - length; | |
1824 } | |
1825 | |
1826 /* --- write data ------------------------------------------------- */ | |
1827 | |
1828 else if (!paused && (buffer->preload <= 0) && (buffer->used >= 4)) | |
1829 { | |
1830 /* write as much data as a) is available and b) the device can take */ | |
1831 length = buffer->used; | |
1832 if (length > op_free) | |
1833 length = op_free; | |
1834 | |
1835 /* HACK: throttle output (used with fast output plugins) */ | |
1836 if (the_op_config.throttle_enable && !realtime && opened) | |
1837 { | |
1838 sync = buffer->sync_size - (buffer->size - buffer->used); | |
1839 if (sync < 0) length = 0; | |
1840 else if (sync < length) length = sync; | |
1841 } | |
1842 | |
1843 /* clip length to silence countdown (if applicable) */ | |
1844 if ((buffer->silence >= 4) && (length > buffer->silence)) | |
1845 length = buffer->silence; | |
1846 | |
1847 /* clip length to reopen countdown (if applicable) */ | |
1848 if ((buffer->reopen >= 0) && (length > buffer->reopen)) | |
1849 length = buffer->reopen; | |
1850 | |
1851 /* clip length to pause countdown (if applicable) */ | |
1852 if ((buffer->pause >= 0) && (length > buffer->pause)) | |
1853 length = buffer->pause; | |
1854 | |
1855 /* make sure we always operate on stereo sample boundary */ | |
1856 length &= -4; | |
1857 | |
1858 /* HACK: don't stay in here too long when in realtime mode (see below) */ | |
1859 if (realtime) | |
1860 gettimeofday(&mark, NULL); | |
1861 | |
1862 /* write length bytes to the device */ | |
1863 length_bak = length; | |
1864 while (length > 0) | |
1865 { | |
1866 data = buffer->data + buffer->rd_index; | |
1867 blen = buffer->size - buffer->rd_index; | |
1868 if (blen > length) | |
1869 blen = length; | |
1870 | |
1871 /* HACK: the original OSS plugin hangs when writing large | |
1872 * blocks (greater than device buffer size) in realtime mode */ | |
1873 if (the_op_config.max_write_enable && (blen > the_op_config.max_write_len)) | |
1874 blen = the_op_config.max_write_len; | |
1875 | |
1876 #ifdef HAVE_LIBFFTW | |
1877 /* fft playground */ | |
1878 fft_flow(&fft_context, (gpointer) data, blen); | |
1879 #endif | |
1880 /* finally, write data */ | |
1881 the_op->write_audio(data, blen); | |
1882 | |
1883 buffer->rd_index = (buffer->rd_index + blen) % buffer->size; | |
1884 buffer->used -= blen; | |
1885 length -= blen; | |
1886 | |
1887 /* HACK: don't stay in here too long (force yielding every 10 ms) */ | |
1888 if (realtime) | |
1889 { | |
1890 gettimeofday(&tv, NULL); | |
1891 dt = (tv.tv_sec - mark.tv_sec) * 1000 + (tv.tv_usec - mark.tv_usec) / 1000; | |
1892 if (dt >= 10) | |
1893 break; | |
1894 } | |
1895 } | |
1896 | |
1897 /* calculate how many bytes actually have been written */ | |
1898 length = length_bak - length; | |
1899 } | |
1900 else | |
1901 length = 0; | |
1902 | |
1903 /* update realtime throttling */ | |
1904 output_written += length; | |
1905 output_streampos += length; | |
1906 | |
1907 /* --- check countdowns ------------------------------------------------ */ | |
1908 | |
1909 if (buffer->silence > 0) | |
1910 { | |
1911 buffer->silence -= length; | |
1912 if (buffer->silence < 0) | |
1913 DEBUG(("[crossfade] buffer_thread_f: WARNING: silence overrun: %d\n", buffer->silence)); | |
1914 } | |
1915 else if (buffer->silence_len > 0) | |
1916 { | |
1917 buffer->silence_len -= length; | |
1918 if (buffer->silence_len <= 0) | |
1919 { | |
1920 if (buffer->silence_len < 0) | |
1921 DEBUG(("[crossfade] buffer_thread_f: WARNING: silence_len overrun: %d\n", | |
1922 buffer->silence_len)); | |
1923 } | |
1924 } | |
1925 | |
1926 if ((buffer->reopen >= 0) && !((buffer->silence <= 0) && (buffer->silence_len > 0))) | |
1927 { | |
1928 buffer->reopen -= length; | |
1929 if (buffer->reopen <= 0) | |
1930 { | |
1931 if (buffer->reopen < 0) | |
1932 DEBUG(("[crossfade] buffer_thread_f: WARNING: reopen overrun: %d\n", buffer->reopen)); | |
1933 | |
1934 DEBUG(("[crossfade] buffer_thread_f: closing/reopening device\n")); | |
1935 if (buffer->reopen_sync) | |
1936 sync_output(); | |
1937 | |
1938 if (the_op->close_audio) | |
1939 the_op->close_audio(); | |
1940 | |
1941 if (!the_op->open_audio(out_format.fmt, out_format.rate, out_format.nch)) | |
1942 { | |
1943 DEBUG(("[crossfade] buffer_thread_f: reopening output plugin failed!\n")); | |
1944 g_free(buffer->data); | |
1945 output_opened = FALSE; | |
1946 MUTEX_UNLOCK(&buffer_mutex); | |
1947 THREAD_EXIT(0); | |
1948 return NULL; | |
1949 } | |
1950 | |
1951 output_flush_time = 0; | |
1952 output_written = 0; | |
1953 output_streampos = 0; | |
1954 | |
1955 /* We need to take the leading gap killer into account here: | |
1956 * It will fix streampos only after gapkilling has finished. | |
1957 * So, if gapkilling is still in progress at this point, we | |
1958 * have to fix it ourselves. */ | |
1959 output_offset = buffer->used; | |
1960 if ((buffer->gap_len > 0) && (buffer->gap > 0)) | |
1961 output_offset += buffer->gap_len - buffer->gap; | |
1962 output_offset = B2MS(output_offset) - xfade_written_time(); | |
1963 | |
1964 /* make sure reopen is not 0 */ | |
1965 buffer->reopen = -1; | |
1966 } | |
1967 } | |
1968 | |
1969 if (buffer->pause >= 0) | |
1970 { | |
1971 buffer->pause -= length; | |
1972 if (buffer->pause <= 0) | |
1973 { | |
1974 if (buffer->pause < 0) | |
1975 DEBUG(("[crossfade] buffer_thread_f: WARNING: pause overrun: %d\n", buffer->pause)); | |
1976 | |
1977 DEBUG(("[crossfade] buffer_thread_f: pausing output\n")); | |
1978 | |
1979 paused = TRUE; | |
1980 sync_output(); | |
1981 | |
1982 if (paused) | |
1983 the_op->pause(1); | |
1984 else | |
1985 DEBUG(("[crossfade] buffer_thread_f: unpause during sync\n")) buffer->pause = -1; | |
1986 } | |
1987 } | |
1988 } | |
1989 | |
1990 /* ----------------------------------------------------------------------- */ | |
1991 | |
1992 /* cleanup: close output */ | |
1993 if (output_opened) | |
1994 { | |
1995 xfade_stop_monitor(); | |
1996 | |
1997 DEBUG(("[crossfade] buffer_thread_f: closing output...\n")); | |
1998 | |
1999 if (the_op->close_audio) | |
2000 the_op->close_audio(); | |
2001 | |
2002 DEBUG(("[crossfade] buffer_thread_f: closing output... done\n")); | |
2003 | |
2004 g_free(buffer->data); | |
2005 output_opened = FALSE; | |
2006 } | |
2007 else | |
2008 DEBUG(("[crossfade] buffer_thread_f: output already closed!\n")); | |
2009 | |
2010 /* ----------------------------------------------------------------------- */ | |
2011 | |
2012 /* unlock buffer */ | |
2013 MUTEX_UNLOCK(&buffer_mutex); | |
2014 | |
2015 /* done */ | |
2016 DEBUG(("[crossfade] buffer_thread_f: thread finished\n")); | |
2017 THREAD_EXIT(0); | |
2018 return NULL; | |
2019 } | |
2020 | |
2021 void | |
2022 xfade_close_audio() | |
2023 { | |
2024 DEBUG(("[crossfade] close:\n")); | |
2025 DEBUG(("[crossfade] close: playing=%d filename=%s\n", | |
2026 xfplayer_input_playing(), xfplaylist_get_filename(xfplaylist_get_position()))); | |
2027 | |
2028 /* lock buffer */ | |
2029 MUTEX_LOCK(&buffer_mutex); | |
2030 | |
2031 /* sanity... the vorbis plugin likes to call close_audio() twice */ | |
2032 if (!opened) | |
2033 { | |
2034 DEBUG(("[crossfade] close: WARNING: not opened!\n")); | |
2035 MUTEX_UNLOCK(&buffer_mutex); | |
2036 return; | |
2037 } | |
2038 | |
2039 #if defined(COMPILE_FOR_AUDACIOUS) && AUDACIOUS_ABI_VERSION >= 2 | |
2040 /* HACK: to distinguish between STOP and EOP, check Audacious' | |
2041 input_playing() variable. It seems to be TRUE at this point | |
2042 only when the end of the playlist is reached. | |
2043 | |
2044 Normally, 'playing' is constantly being updated in the | |
2045 xfade_buffer_playing() callback, but Audacious does not seem | |
2046 to use it. Therefore, we can set 'playing' to FALSE here, | |
2047 which later 'buffer_thread' will interpret as EOP (see above). | |
2048 */ | |
2049 if (xfplayer_input_playing()) | |
2050 playing = FALSE; | |
2051 #else | |
2052 /* HACK: use patched XMMS 'input_stopped_for_restart' */ | |
2053 if (input_stopped_for_restart && *input_stopped_for_restart) | |
2054 { | |
2055 DEBUG(("[crossfade] close: playback will restart soon\n")); | |
2056 output_restart = TRUE; | |
2057 } | |
2058 else | |
2059 output_restart = FALSE; | |
2060 #endif | |
2061 | |
2062 if (playing) | |
2063 { | |
2064 /* immediatelly close output when paused */ | |
2065 if (paused) | |
2066 { | |
2067 buffer->pause = -1; | |
2068 paused = FALSE; | |
2069 if (config->output_keep_opened) | |
2070 { | |
2071 buffer->used = 0; | |
2072 the_op->flush(0); | |
2073 the_op->pause(0); | |
2074 } | |
2075 else | |
2076 stopped = TRUE; | |
2077 } | |
2078 | |
2079 /* HACK: If playlist_get_info_going is not true here, | |
2080 * XMMS is about to exit. In this case, we stop | |
2081 * the buffer thread before returning from this | |
2082 * function. Otherwise, SEGFAULT may occur when | |
2083 * XMMS tries to cleanup an output plugin which | |
2084 * we are still using. | |
2085 * | |
2086 * NOTE: This hack has become obsolete as of 0.3.5. | |
2087 * See output_list_hack(). | |
2088 * | |
2089 * NOTE: Not quite. There still are some problems when | |
2090 * XMMS is exitting while a song is playing. So | |
2091 * this HACK has been enabled again. | |
2092 * | |
2093 * NOTE: Another thing: If output_keep_opened is enabled, | |
2094 * close_audio() is never called, so that the patch | |
2095 * can not work. | |
2096 */ | |
2097 #if 1 | |
2098 if ((xmms_is_quitting && *xmms_is_quitting) | |
2099 || (xmms_playlist_get_info_going && !*xmms_playlist_get_info_going)) | |
2100 { | |
2101 DEBUG(("[crossfade] close: stop (about to quit)\n")) | |
2102 | |
2103 /* wait for buffer thread to clean up and terminate */ | |
2104 stopped = TRUE; | |
2105 #if 1 | |
2106 MUTEX_UNLOCK(&buffer_mutex); | |
2107 if (THREAD_JOIN(buffer_thread)) | |
2108 PERROR("[crossfade] close: phtread_join()"); | |
2109 MUTEX_LOCK(&buffer_mutex); | |
2110 #else | |
2111 while (output_opened) | |
2112 { | |
2113 MUTEX_UNLOCK(&buffer_mutex); | |
2114 xfade_usleep(10000); | |
2115 MUTEX_LOCK(&buffer_mutex); | |
2116 } | |
2117 #endif | |
2118 } | |
2119 else | |
2120 #endif | |
2121 DEBUG(("[crossfade] close: stop\n")); | |
2122 | |
2123 fade_config = &config->fc[FADE_CONFIG_MANUAL]; | |
2124 } | |
2125 else | |
2126 { | |
2127 /* gint x = *((gint *)0); */ /* force SEGFAULT for debugging */ | |
2128 DEBUG(("[crossfade] close: songchange/eop\n")); | |
2129 | |
2130 /* kill trailing gap (does not use buffer->gap_*) */ | |
2131 if (output_opened && xfade_cfg_gap_trail_enable(config)) | |
2132 { | |
2133 gint gap_len = MS2B(xfade_cfg_gap_trail_len(config)) & -4; | |
2134 gint gap_level = xfade_cfg_gap_trail_level(config); | |
2135 gint length = MIN(gap_len, buffer->used); | |
2136 | |
2137 /* DEBUG(("[crossfade] close: len=%d level=%d length=%d\n", gap_len, gap_level, length)); */ | |
2138 | |
2139 buffer->gap_killed = 0; | |
2140 while (length > 0) | |
2141 { | |
2142 gint wr_xedni = (buffer->rd_index + buffer->used - 1) % buffer->size + 1; | |
2143 gint blen = MIN(length, wr_xedni); | |
2144 gint16 *p = buffer->data + wr_xedni, left, right; | |
2145 gint index = 0; | |
2146 | |
2147 while (index < blen) | |
2148 { | |
2149 right = *--p; | |
2150 left = *--p; | |
2151 if (ABS(left) >= gap_level) break; | |
2152 if (ABS(right) >= gap_level) break; | |
2153 index += 4; | |
2154 } | |
2155 | |
2156 buffer->used -= index; | |
2157 buffer->gap_killed += index; | |
2158 | |
2159 if (index < blen) | |
2160 break; | |
2161 length -= blen; | |
2162 } | |
2163 | |
2164 DEBUG(("[crossfade] close: trailing gap size: %d/%d ms\n", B2MS(buffer->gap_killed), B2MS(gap_len))); | |
2165 } | |
2166 | |
2167 /* skip to previous zero crossing */ | |
2168 if (output_opened && config->gap_crossing) | |
2169 { | |
2170 int crossing; | |
2171 | |
2172 buffer->gap_skipped = 0; | |
2173 for (crossing = 0; crossing < 4; crossing++) | |
2174 { | |
2175 while (buffer->used > 0) | |
2176 { | |
2177 gint wr_xedni = (buffer->rd_index + buffer->used - 1) % buffer->size + 1; | |
2178 gint blen = MIN(buffer->used, wr_xedni); | |
2179 gint16 *p = buffer->data + wr_xedni, left; | |
2180 gint index = 0; | |
2181 | |
2182 while (index < blen) | |
2183 { | |
2184 left = (--p, *--p); | |
2185 if ((crossing & 1) ^ (left > 0)) | |
2186 break; | |
2187 index += 4; | |
2188 } | |
2189 | |
2190 buffer->used -= index; | |
2191 buffer->gap_skipped += index; | |
2192 | |
2193 if (index < blen) | |
2194 break; | |
2195 } | |
2196 } | |
2197 DEBUG(("[crossfade] close: skipped %d bytes to previous zero crossing\n", buffer->gap_skipped)); | |
2198 | |
2199 /* update gap_killed (for undoing gap_killer in case of EOP) */ | |
2200 buffer->gap_killed += buffer->gap_skipped; | |
2201 } | |
2202 | |
2203 fade_config = &config->fc[FADE_CONFIG_XFADE]; | |
2204 } | |
2205 | |
2206 /* XMMS has left the building */ | |
2207 opened = FALSE; | |
2208 | |
2209 /* update last_close */ | |
2210 gettimeofday(&last_close, NULL); | |
2211 input_playing = FALSE; | |
2212 | |
2213 /* unlock buffer */ | |
2214 MUTEX_UNLOCK(&buffer_mutex); | |
2215 } | |
2216 | |
2217 void | |
2218 xfade_flush(gint time) | |
2219 { | |
2220 gint pos; | |
2221 gchar *file; | |
2222 | |
2223 DEBUG(("[crossfade] flush: time=%d\n", time)); | |
2224 | |
2225 /* get filename */ | |
2226 pos = xfplaylist_get_position(); | |
2227 file = xfplaylist_get_filename(pos); | |
2228 | |
2229 if (!file) | |
2230 file = g_strdup(xfplaylist_get_songtitle(pos)); | |
2231 | |
2232 #if defined(COMPILE_FOR_AUDACIOUS) | |
2233 /* HACK: special handling for audacious, which just calls flush(0) on a songchange */ | |
2234 if (file && last_filename && strcmp(file, last_filename) != 0) | |
2235 { | |
2236 DEBUG(("[crossfade] flush: filename changed, forcing close/reopen...\n")); | |
2237 xfade_close_audio(); | |
2238 /* 0.3.14: xfade_close_audio sets fade_config to FADE_CONFIG_MANUAL, | |
2239 * but this is an automatic songchange */ | |
2240 fade_config = &config->fc[FADE_CONFIG_XFADE]; | |
2241 xfade_open_audio(in_format.fmt, in_format.rate, in_format.nch); | |
2242 DEBUG(("[crossfade] flush: filename changed, forcing close/reopen... done\n")); | |
2243 return; | |
2244 } | |
2245 #endif | |
2246 | |
2247 /* lock buffer */ | |
2248 MUTEX_LOCK(&buffer_mutex); | |
2249 | |
2250 /* update streampos with new stream position (input format size) */ | |
2251 streampos = ((gint64) time * in_format.bps / 1000) & -4; | |
2252 | |
2253 /* flush output device / apply seek crossfade */ | |
2254 if (config->fc[FADE_CONFIG_SEEK].type == FADE_TYPE_FLUSH) | |
2255 { | |
2256 /* flush output plugin */ | |
2257 the_op->flush(time); | |
2258 output_flush_time = time; | |
2259 output_streampos = MS2B(time); | |
2260 | |
2261 /* flush buffer, disable leading gap killing */ | |
2262 buffer_reset(buffer, config); | |
2263 } | |
2264 else if (paused) | |
2265 { | |
2266 fade_config_t fc; | |
2267 | |
2268 /* clear buffer */ | |
2269 buffer->used = 0; | |
2270 | |
2271 /* apply only the fade_in part of FADE_CONFIG_PAUSE */ | |
2272 memcpy(&fc, &config->fc[FADE_CONFIG_PAUSE], sizeof(fc)); | |
2273 fc.out_len_ms = 0; | |
2274 fc.ofs_custom_ms = 0; | |
2275 xfade_apply_fade_config(&fc); | |
2276 } | |
2277 else | |
2278 xfade_apply_fade_config(&config->fc[FADE_CONFIG_SEEK]); | |
2279 | |
2280 /* restart realtime throttling (should find another name for that var) */ | |
2281 output_written = 0; | |
2282 | |
2283 /* make sure that the gapkiller is disabled */ | |
2284 buffer->gap = 0; | |
2285 | |
2286 /* update output offset */ | |
2287 output_offset = the_op->written_time() - time + B2MS(buffer->used) + B2MS(buffer->silence_len); | |
2288 | |
2289 /* unlock buffer */ | |
2290 MUTEX_UNLOCK(&buffer_mutex); | |
2291 | |
2292 #ifdef DEBUG_HARDCORE | |
2293 DEBUG(("[crossfade] flush: time=%d: done.\n", time)); | |
2294 #endif | |
2295 } | |
2296 | |
2297 void | |
2298 xfade_pause(short p) | |
2299 { | |
2300 /* lock buffer */ | |
2301 MUTEX_LOCK(&buffer_mutex); | |
2302 | |
2303 if (p) | |
2304 { | |
2305 fade_config_t *fc = &config->fc[FADE_CONFIG_PAUSE]; | |
2306 if (fc->type == FADE_TYPE_PAUSE_ADV) | |
2307 { | |
2308 int fade, length, n; | |
2309 int index = buffer->rd_index; | |
2310 int out_len = MS2B(xfade_cfg_fadeout_len(fc)) & -4; | |
2311 int in_len = MS2B(xfade_cfg_fadein_len(fc)) & -4; | |
2312 int silence_len = MS2B(xfade_cfg_offset(fc)) & -4; | |
2313 | |
2314 /* limit fadeout/fadein len to available data in buffer */ | |
2315 if ((out_len + in_len) > buffer->used) | |
2316 { | |
2317 out_len = (buffer->used / 2) & -4; | |
2318 in_len = out_len; | |
2319 } | |
2320 | |
2321 DEBUG(("[crossfade] pause: paused=1 out=%d in=%d silence=%d\n", | |
2322 B2MS(out_len), B2MS(in_len), B2MS(silence_len))); | |
2323 | |
2324 /* fade out (modifies buffer directly) */ | |
2325 fade = 0; | |
2326 length = out_len; | |
2327 while (length > 0) | |
2328 { | |
2329 gint16 *p = buffer->data + index; | |
2330 gint blen = buffer->size - index; | |
2331 if (blen > length) | |
2332 blen = length; | |
2333 | |
2334 for (n = blen / 4; n > 0; n--) | |
2335 { | |
2336 gfloat factor = 1.0f - ((gfloat) fade / out_len); | |
2337 *p = (gfloat)*p * factor; p++; | |
2338 *p = (gfloat)*p * factor; p++; | |
2339 fade += 4; | |
2340 } | |
2341 | |
2342 index = (index + blen) % buffer->size; | |
2343 length -= blen; | |
2344 } | |
2345 | |
2346 /* fade in (modifies buffer directly) */ | |
2347 fade = 0; | |
2348 length = in_len; | |
2349 while (length > 0) | |
2350 { | |
2351 gint16 *p = buffer->data + index; | |
2352 gint blen = buffer->size - index; | |
2353 if (blen > length) | |
2354 blen = length; | |
2355 | |
2356 for (n = blen / 4; n > 0; n--) | |
2357 { | |
2358 gfloat factor = (gfloat) fade / in_len; | |
2359 *p = (gfloat)*p * factor; p++; | |
2360 *p = (gfloat)*p * factor; p++; | |
2361 fade += 4; | |
2362 } | |
2363 | |
2364 index = (index + blen) % buffer->size; | |
2365 length -= blen; | |
2366 } | |
2367 | |
2368 /* start silence and pause countdowns */ | |
2369 buffer->silence = out_len; | |
2370 buffer->silence_len = silence_len; | |
2371 buffer->pause = out_len + silence_len; | |
2372 paused = FALSE; /* (!) will be set to TRUE in buffer_thread_f */ | |
2373 } | |
2374 else | |
2375 { | |
2376 the_op->pause(1); | |
2377 paused = TRUE; | |
2378 DEBUG(("[crossfade] pause: paused=1\n")); | |
2379 } | |
2380 } | |
2381 else | |
2382 { | |
2383 the_op->pause(0); | |
2384 buffer->pause = -1; | |
2385 paused = FALSE; | |
2386 DEBUG(("[crossfade] pause: paused=0\n")); | |
2387 } | |
2388 | |
2389 /* unlock buffer */ | |
2390 MUTEX_UNLOCK(&buffer_mutex); | |
2391 } | |
2392 | |
2393 gint | |
2394 xfade_buffer_free() | |
2395 { | |
2396 gint size, free; | |
2397 | |
2398 #ifdef DEBUG_HARDCORE | |
2399 DEBUG(("[crossfade] buffer_free:\n")); | |
2400 #endif | |
2401 | |
2402 /* sanity check */ | |
2403 if (!output_opened) | |
2404 { | |
2405 DEBUG(("[crossfade] buffer_free: WARNING: output closed!\n")); | |
2406 return buffer->sync_size; | |
2407 } | |
2408 | |
2409 /* lock buffer */ | |
2410 MUTEX_LOCK(&buffer_mutex); | |
2411 | |
2412 size = buffer->size; | |
2413 | |
2414 /* When running in realtime mode, we need to take special care here: | |
2415 * While XMMS is writing data to the output plugin, it will not yield | |
2416 * ANY processing time to the buffer thread. It will only stop when | |
2417 * xfade_buffer_free() no longer reports free buffer space. */ | |
2418 if (realtime) | |
2419 { | |
2420 gint64 wanted = output_written + buffer->preload_size; | |
2421 | |
2422 /* Fix for XMMS misbehaviour (possibly a bug): If the free space as | |
2423 * returned by xfade_buffer_free() is below a certain minimum block size | |
2424 * (tests showed 2304 bytes), XMMS will not send more data until there | |
2425 * is enough room for one of those blocks. | |
2426 * | |
2427 * This breaks preloading in realtime mode. To make sure that the pre- | |
2428 * load buffer gets filled we request additional sync_size bytes. */ | |
2429 wanted += buffer->sync_size; | |
2430 | |
2431 if (wanted <= size) | |
2432 size = wanted; | |
2433 } | |
2434 | |
2435 free = size - buffer->used; | |
2436 if (free < 0) | |
2437 free = 0; | |
2438 | |
2439 /* unlock buffer */ | |
2440 MUTEX_UNLOCK(&buffer_mutex); | |
2441 | |
2442 /* Convert to input format size. For input rates > output rate this will | |
2443 * return less free space than actually is available, but we don't care. */ | |
2444 free /= (out_format.rate / (in_format.rate + 1)) + 1; | |
2445 if (in_format.is_8bit) | |
2446 free /= 2; | |
2447 if (in_format.nch == 1) | |
2448 free /= 2; | |
2449 | |
2450 #ifdef DEBUG_HARDCORE | |
2451 DEBUG(("[crossfade] buffer_free: %d\n", free)); | |
2452 #endif | |
2453 return free; | |
2454 } | |
2455 | |
2456 gint | |
2457 xfade_buffer_playing() | |
2458 { | |
2459 /* always return FALSE here (if not in pause) so XMMS immediatelly | |
2460 * starts playback of the next song */ | |
2461 | |
2462 /* | |
2463 * NOTE: this causes trouble when playing HTTP audio streams. | |
2464 * | |
2465 * mpg123.lib will start prebuffering (and thus stalling output) when both | |
2466 * 1) it's internal buffer is emptied (does happen all the time) | |
2467 * 2) the output plugin's buffer_playing() (this function) returns FALSE | |
2468 */ | |
2469 | |
2470 if (paused) | |
2471 playing = TRUE; | |
2472 else | |
2473 playing = | |
2474 (is_http && (buffer->used > 0) && the_op->buffer_playing()) | |
2475 || (buffer->reopen >= 0) | |
2476 || (buffer->silence > 0) | |
2477 || (buffer->silence_len > 0); | |
2478 | |
2479 #ifdef DEBUG_HARDCORE | |
2480 DEBUG(("[crossfade] buffer_playing: %d\n", playing)); | |
2481 #endif | |
2482 return playing; | |
2483 } | |
2484 | |
2485 gint | |
2486 xfade_written_time() | |
2487 { | |
2488 if (!output_opened) | |
2489 return 0; | |
2490 return (gint) (streampos * 1000 / in_format.bps); | |
2491 } | |
2492 | |
2493 gint | |
2494 xfade_output_time() | |
2495 { | |
2496 gint time; | |
2497 | |
2498 #ifdef DEBUG_HARDCORE | |
2499 DEBUG(("[crossfade] output_time:\n")); | |
2500 #endif | |
2501 | |
2502 /* sanity check (note: this one _does_ happen all the time) */ | |
2503 if (!output_opened) | |
2504 return 0; | |
2505 | |
2506 /* lock buffer */ | |
2507 MUTEX_LOCK(&buffer_mutex); | |
2508 | |
2509 time = the_op->output_time() - output_offset; | |
2510 if (time < 0) | |
2511 time = 0; | |
2512 | |
2513 /* unlock buffer */ | |
2514 MUTEX_UNLOCK(&buffer_mutex); | |
2515 | |
2516 #ifdef DEBUG_HARDCORE | |
2517 DEBUG(("[crossfade] output_time: time=%d\n", time)); | |
2518 #endif | |
2519 return time; | |
2520 } | |
2521 | |
2522 void | |
2523 xfade_cleanup() | |
2524 { | |
2525 DEBUG(("[crossfade] cleanup:\n")); | |
2526 | |
2527 /* lock buffer */ | |
2528 MUTEX_LOCK(&buffer_mutex); | |
2529 | |
2530 /* check if buffer thread is still running */ | |
2531 if (output_opened) | |
2532 { | |
2533 DEBUG(("[crossfade] cleanup: closing output\n")); | |
2534 | |
2535 stopped = TRUE; | |
2536 | |
2537 /* wait for buffer thread to clean up and terminate */ | |
2538 MUTEX_UNLOCK(&buffer_mutex); | |
2539 if (THREAD_JOIN(buffer_thread)) | |
2540 PERROR("[crossfade] close: thread_join()"); | |
2541 MUTEX_LOCK(&buffer_mutex); | |
2542 } | |
2543 | |
2544 /* unlock buffer */ | |
2545 MUTEX_UNLOCK(&buffer_mutex); | |
2546 | |
2547 DEBUG(("[crossfade] cleanup: done\n")); | |
2548 } |