/* * output.c * Copyright 2009-2010 John Lindgren * * This file is part of Audacious. * * Audacious is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, version 2 or version 3 of the License. * * Audacious is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * Audacious. If not, see . * * The Audacious team does not consider modular code linking to Audacious or * using our public API to be a derived work. */ #include #include "config.h" #include "audio.h" #include "audconfig.h" #include "effect.h" #include "equalizer.h" #include "flow.h" #include "output.h" #include "playback.h" #include "pluginenum.h" #include "plugin-registry.h" #include "vis_runner.h" #define SW_VOLUME_RANGE 40 /* decibels */ OutputPlugin * current_output_plugin = NULL; #define COP current_output_plugin static gboolean plugin_list_func (void * plugin, void * data) { GList * * list = data; * list = g_list_prepend (* list, plugin); return TRUE; } GList * get_output_list (void) { static GList * list = NULL; if (list == NULL) plugin_for_each (PLUGIN_TYPE_OUTPUT, plugin_list_func, & list); return list; } void output_get_volume (gint * l, gint * r) { if (cfg.software_volume_control) { * l = cfg.sw_volume_left; * r = cfg.sw_volume_right; } else if (COP != NULL && COP->get_volume != NULL) COP->get_volume (l, r); else { * l = 0; * r = 0; } } void output_set_volume (gint l, gint r) { if (cfg.software_volume_control) { cfg.sw_volume_left = l; cfg.sw_volume_right = r; } else if (COP != NULL && COP->set_volume != NULL) COP->set_volume (l, r); } static GMutex * output_mutex; static gboolean output_opened, output_leave_open, output_paused; static AFormat decoder_format, output_format; static gint decoder_channels, decoder_rate, effect_channels, effect_rate, output_channels, output_rate; static gint64 frames_written; static gboolean have_replay_gain; static ReplayGainInfo replay_gain_info; #define REMOVE_SOURCE(s) \ do { \ if (s != 0) { \ g_source_remove (s); \ s = 0; \ } \ } while (0) #define LOCK g_mutex_lock (output_mutex) #define UNLOCK g_mutex_unlock (output_mutex) static void write_buffers (void); static void drain (void); /* output_mutex must be locked */ static void real_close (void) { vis_runner_start_stop (FALSE, FALSE); COP->close_audio (); output_opened = FALSE; output_leave_open = FALSE; } void output_init (void) { output_mutex = g_mutex_new (); output_opened = FALSE; output_leave_open = FALSE; vis_runner_init (); } void output_cleanup (void) { LOCK; if (output_leave_open) real_close (); UNLOCK; g_mutex_free (output_mutex); } static gboolean output_open_audio (AFormat format, gint rate, gint channels) { if (COP == NULL) { fprintf (stderr, "No output plugin selected.\n"); return FALSE; } LOCK; if (output_leave_open && COP->set_written_time != NULL) { vis_runner_time_offset (- frames_written * (gint64) 1000 / decoder_rate); COP->set_written_time (0); } decoder_format = format; decoder_channels = channels; decoder_rate = rate; frames_written = 0; effect_channels = channels; effect_rate = rate; new_effect_start (& effect_channels, & effect_rate); eq_set_format (effect_channels, effect_rate); if (output_leave_open && COP->set_written_time != NULL && effect_channels == output_channels && effect_rate == output_rate) output_opened = TRUE; else { if (output_leave_open) { UNLOCK; drain (); LOCK; real_close (); } output_format = cfg.output_bit_depth == 32 ? FMT_S32_NE : cfg.output_bit_depth == 24 ? FMT_S24_NE : cfg.output_bit_depth == 16 ? FMT_S16_NE : FMT_FLOAT; output_channels = effect_channels; output_rate = effect_rate; if (COP->open_audio (output_format, output_rate, output_channels) > 0) { vis_runner_start_stop (TRUE, FALSE); output_opened = TRUE; } } output_leave_open = FALSE; output_paused = FALSE; UNLOCK; return output_opened; } static void output_close_audio (void) { LOCK; output_opened = FALSE; if (! output_leave_open) { new_effect_flush (); real_close (); } UNLOCK; } static void output_flush (gint time) { LOCK; frames_written = time * (gint64) decoder_rate / 1000; vis_runner_flush (); new_effect_flush (); COP->flush (new_effect_decoder_to_output_time (time)); UNLOCK; } static void output_pause (gboolean pause) { LOCK; COP->pause (pause); vis_runner_start_stop (TRUE, pause); output_paused = pause; UNLOCK; } static gint get_written_time (void) { gint time = 0; LOCK; if (output_opened) time = frames_written * (gint64) 1000 / decoder_rate; UNLOCK; return time; } static gboolean output_buffer_playing (void) { LOCK; if (! output_paused) { UNLOCK; write_buffers (); LOCK; output_leave_open = TRUE; } UNLOCK; return FALSE; } static Flow * get_legacy_flow (void) { static Flow * flow = NULL; if (flow == NULL) { flow = flow_new (); flow_link_element (flow, effect_flow); } return flow; } static void output_set_replaygain_info (ReplayGainInfo * info) { AUDDBG ("Replay Gain info:\n"); AUDDBG (" album gain: %f dB\n", info->album_gain); AUDDBG (" album peak: %f\n", info->album_peak); AUDDBG (" track gain: %f dB\n", info->track_gain); AUDDBG (" track peak: %f\n", info->track_peak); have_replay_gain = TRUE; memcpy (& replay_gain_info, info, sizeof (ReplayGainInfo)); } static void apply_replay_gain (gfloat * data, gint samples) { gfloat factor = powf (10, (gfloat) cfg.replay_gain_preamp / 20); if (! cfg.enable_replay_gain) return; if (have_replay_gain) { if (cfg.replay_gain_album) { factor *= powf (10, replay_gain_info.album_gain / 20); if (cfg.enable_clipping_prevention && replay_gain_info.album_peak * factor > 1) factor = 1 / replay_gain_info.album_peak; } else { factor *= powf (10, replay_gain_info.track_gain / 20); if (cfg.enable_clipping_prevention && replay_gain_info.track_peak * factor > 1) factor = 1 / replay_gain_info.track_peak; } } else factor *= powf (10, (gfloat) cfg.default_gain / 20); if (factor < 0.99 || factor > 1.01) audio_amplify (data, 1, samples, & factor); } static void apply_software_volume (gfloat * data, gint channels, gint frames) { gfloat left_factor, right_factor; gfloat factors[channels]; gint channel; if (! cfg.software_volume_control || (cfg.sw_volume_left == 100 && cfg.sw_volume_right == 100)) return; left_factor = (cfg.sw_volume_left == 0) ? 0 : powf (10, (gfloat) SW_VOLUME_RANGE * (cfg.sw_volume_left - 100) / 100 / 20); right_factor = (cfg.sw_volume_right == 0) ? 0 : powf (10, (gfloat) SW_VOLUME_RANGE * (cfg.sw_volume_right - 100) / 100 / 20); if (channels == 2) { factors[0] = left_factor; factors[1] = right_factor; } else { for (channel = 0; channel < channels; channel ++) factors[channel] = MAX (left_factor, right_factor); } audio_amplify (data, channels, frames, factors); } static void do_write (void * data, gint samples) { void * allocated = NULL; eq_filter (data, samples); apply_software_volume (data, output_channels, samples / output_channels); if (output_format != FMT_FLOAT) { void * new = g_malloc (FMT_SIZEOF (output_format) * samples); audio_to_int (data, new, output_format, samples); data = new; g_free (allocated); allocated = new; } if (output_format == FMT_S16_NE) { samples = flow_execute (get_legacy_flow (), 0, & data, 2 * samples, output_format, output_rate, output_channels) / 2; if (data != allocated) { g_free (allocated); allocated = NULL; } } if (COP->buffer_free == NULL) COP->write_audio (data, FMT_SIZEOF (output_format) * samples); else { while (1) { gint ready = COP->buffer_free () / FMT_SIZEOF (output_format); ready = MIN (ready, samples); COP->write_audio (data, FMT_SIZEOF (output_format) * ready); data = (char *) data + FMT_SIZEOF (output_format) * ready; samples -= ready; if (samples == 0) break; g_usleep (50000); } } g_free (allocated); } static void output_write_audio (void * data, gint size) { gint samples = size / FMT_SIZEOF (decoder_format); void * allocated = NULL; LOCK; frames_written += samples / decoder_channels; UNLOCK; if (decoder_format != FMT_FLOAT) { gfloat * new = g_malloc (sizeof (gfloat) * samples); audio_from_int (data, decoder_format, new, samples); data = new; g_free (allocated); allocated = new; } apply_replay_gain (data, samples); vis_runner_pass_audio (frames_written * (gint64) 1000 / decoder_rate, data, samples, decoder_channels); new_effect_process ((gfloat * *) & data, & samples); if (data != allocated) { g_free (allocated); allocated = NULL; } do_write (data, samples); g_free (allocated); } static void write_buffers (void) { gfloat * data = NULL; gint samples = 0; new_effect_finish (& data, & samples); do_write (data, samples); } static void drain (void) { if (COP->buffer_playing != NULL) { while (COP->buffer_playing ()) g_usleep (30000); } else COP->drain (); } struct OutputAPI output_api = { .open_audio = output_open_audio, .set_replaygain_info = output_set_replaygain_info, .write_audio = output_write_audio, .close_audio = output_close_audio, .pause = output_pause, .flush = output_flush, .written_time = get_written_time, .buffer_playing = output_buffer_playing, }; gint get_output_time (void) { gint time = 0; LOCK; if (output_opened) { time = new_effect_output_to_decoder_time (COP->output_time ()); time = MAX (0, time); } UNLOCK; return time; } void output_drain (void) { LOCK; if (output_leave_open) { UNLOCK; write_buffers (); /* tell effect plugins this is the last song */ drain (); LOCK; real_close (); } UNLOCK; } void set_current_output_plugin (OutputPlugin * plugin) { OutputPlugin * old = COP; gboolean playing = playback_get_playing (); gboolean paused = FALSE; gint time = 0; if (playing) { paused = playback_get_paused (); time = playback_get_time (); playback_stop (); } /* This function is also used to restart playback (for example, when resampling is switched on or off), in which case we don't need to do an init cycle. -jlindgren */ if (plugin != COP) { COP = NULL; if (old != NULL && old->cleanup != NULL) old->cleanup (); if (plugin->init () == OUTPUT_PLUGIN_INIT_FOUND_DEVICES) COP = plugin; else { fprintf (stderr, "Output plugin failed to load: %s\n", plugin->description); if (old == NULL || old->init () != OUTPUT_PLUGIN_INIT_FOUND_DEVICES) return; fprintf (stderr, "Falling back to: %s\n", old->description); COP = old; } } if (playing) { playback_initiate (); if (paused) playback_pause (); playback_seek (time); } }