From 752dfb3d95482c562e5d24c6ea839c4815de9a6d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Feb 2010 17:04:39 +0100 Subject: replay_gain: reimplement as a filter plugin Apply the replay gain in the output thread. This means a new setting will be active instantly, without going through the whole music pipe. And we might have different replay gain settings for each audio output device. --- Makefile.am | 9 +- NEWS | 2 + src/chunk.c | 1 + src/chunk.h | 15 +++ src/decoder_api.c | 27 +++-- src/decoder_internal.c | 9 +- src/decoder_internal.h | 9 +- src/decoder_thread.c | 6 - src/filter/replay_gain_filter_plugin.c | 193 +++++++++++++++++++++++++++++++++ src/filter/replay_gain_filter_plugin.h | 37 +++++++ src/filter_registry.c | 1 + src/filter_registry.h | 1 + src/output_init.c | 11 ++ src/output_internal.h | 12 ++ src/output_thread.c | 11 ++ src/replay_gain_state.c | 117 -------------------- src/replay_gain_state.h | 50 --------- test/run_filter.c | 6 + test/run_output.c | 6 + 19 files changed, 336 insertions(+), 187 deletions(-) create mode 100644 src/filter/replay_gain_filter_plugin.c create mode 100644 src/filter/replay_gain_filter_plugin.h delete mode 100644 src/replay_gain_state.c delete mode 100644 src/replay_gain_state.h diff --git a/Makefile.am b/Makefile.am index 0cbfffd49..ddb27a689 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,6 +55,7 @@ mpd_headers = \ src/filter/autoconvert_filter_plugin.h \ src/filter/chain_filter_plugin.h \ src/filter/convert_filter_plugin.h \ + src/filter/replay_gain_filter_plugin.h \ src/filter/volume_filter_plugin.h \ src/command.h \ src/idle.h \ @@ -175,7 +176,6 @@ mpd_headers = \ src/queue_save.h \ src/replay_gain_config.h \ src/replay_gain_info.h \ - src/replay_gain_state.h \ src/sig_handlers.h \ src/song.h \ src/song_print.h \ @@ -306,7 +306,6 @@ src_mpd_SOURCES = \ src/queue_save.c \ src/replay_gain_config.c \ src/replay_gain_info.c \ - src/replay_gain_state.c \ src/sig_handlers.c \ src/song.c \ src/song_update.c \ @@ -744,6 +743,7 @@ FILTER_SRC = \ src/filter/convert_filter_plugin.c \ src/filter/route_filter_plugin.c \ src/filter/normalize_filter_plugin.c \ + src/filter/replay_gain_filter_plugin.c \ src/filter/volume_filter_plugin.c @@ -900,6 +900,8 @@ test_run_filter_SOURCES = test/run_filter.c \ src/audio_check.c \ src/audio_format.c \ src/audio_parser.c \ + src/replay_gain_config.c \ + src/replay_gain_info.c \ src/AudioCompress/compress.c \ $(FILTER_SRC) @@ -985,10 +987,13 @@ test_run_output_SOURCES = test/run_output.c \ src/filter_config.c \ src/filter/autoconvert_filter_plugin.c \ src/filter/convert_filter_plugin.c \ + src/filter/replay_gain_filter_plugin.c \ src/filter/normalize_filter_plugin.c \ src/filter/volume_filter_plugin.c \ src/pcm_volume.c \ src/AudioCompress/compress.c \ + src/replay_gain_info.c \ + src/replay_gain_config.c \ src/fd_util.c \ $(OUTPUT_SRC) diff --git a/NEWS b/NEWS index 7cc25808e..4000a1933 100644 --- a/NEWS +++ b/NEWS @@ -70,6 +70,8 @@ ver 0.16 (20??/??/??) - rescan after metadata_to_use change * normalize: upgraded to AudioCompress 2.0 - automatically convert to 16 bit samples +* replay gain: + - reimplemented as a filter plugin * log unused/unknown block parameters * removed the deprecated "error_file" option * save state when stopped diff --git a/src/chunk.c b/src/chunk.c index 8fff3c7dd..5418b6cda 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -29,6 +29,7 @@ music_chunk_init(struct music_chunk *chunk) { chunk->length = 0; chunk->tag = NULL; + chunk->replay_gain_serial = 0; } void diff --git a/src/chunk.h b/src/chunk.h index 58bab3dc2..23f6321fb 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -20,6 +20,8 @@ #ifndef MPD_CHUNK_H #define MPD_CHUNK_H +#include "replay_gain_info.h" + #ifndef NDEBUG #include "audio_format.h" #endif @@ -59,6 +61,19 @@ struct music_chunk { */ struct tag *tag; + /** + * Replay gain information associated with this chunk. + * Only valid if the serial is not 0. + */ + struct replay_gain_info replay_gain_info; + + /** + * A serial number for checking if replay gain info has + * changed since the last chunk. The magic value 0 indicates + * that there is no replay gain info available. + */ + unsigned replay_gain_serial; + /** the data (probably PCM) */ char data[CHUNK_SIZE]; diff --git a/src/decoder_api.c b/src/decoder_api.c index 7dfe84062..285e21a6b 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -27,8 +27,6 @@ #include "buffer.h" #include "pipe.h" #include "chunk.h" -#include "replay_gain_state.h" -#include "replay_gain_config.h" #include @@ -343,13 +341,6 @@ decoder_data(struct decoder *decoder, memcpy(dest, data, nbytes); - /* apply replay gain or normalization */ - - replay_gain_state_set_mode(decoder->replay_gain, - replay_gain_mode); - replay_gain_state_apply(decoder->replay_gain, - dest, nbytes, &dc->out_audio_format); - /* expand the music pipe chunk */ full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); @@ -418,5 +409,21 @@ decoder_replay_gain(struct decoder *decoder, { assert(decoder != NULL); - replay_gain_state_set_info(decoder->replay_gain, replay_gain_info); + if (replay_gain_info != NULL) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + decoder->replay_gain_info = *replay_gain_info; + decoder->replay_gain_serial = serial; + + if (decoder->chunk != NULL) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder_flush_chunk(decoder); + player_lock_signal(); + } + } else + decoder->replay_gain_serial = 0; } diff --git a/src/decoder_internal.c b/src/decoder_internal.c index 7829b4d76..990d728e9 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -86,8 +86,15 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is) do { decoder->chunk = music_buffer_allocate(dc->buffer); - if (decoder->chunk != NULL) + if (decoder->chunk != NULL) { + decoder->chunk->replay_gain_serial = + decoder->replay_gain_serial; + if (decoder->replay_gain_serial != 0) + decoder->chunk->replay_gain_info = + decoder->replay_gain_info; + return decoder->chunk; + } decoder_lock(dc); cmd = need_chunks(dc, is, true); diff --git a/src/decoder_internal.h b/src/decoder_internal.h index 159b40b92..9e7e2037a 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -22,6 +22,7 @@ #include "decoder_command.h" #include "pcm_convert.h" +#include "replay_gain_info.h" struct input_stream; @@ -53,7 +54,13 @@ struct decoder { /** the chunk currently being written to */ struct music_chunk *chunk; - struct replay_gain_state *replay_gain; + struct replay_gain_info replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; }; /** diff --git a/src/decoder_thread.c b/src/decoder_thread.c index 42a537ffe..99fa2c7e2 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -31,8 +31,6 @@ #include "mapper.h" #include "path.h" #include "uri.h" -#include "replay_gain_state.h" -#include "replay_gain_config.h" #include @@ -351,8 +349,6 @@ decoder_run_song(struct decoder_control *dc, { struct decoder decoder = { .dc = dc, - .replay_gain = replay_gain_state_new(replay_gain_preamp, - replay_gain_missing_preamp), }; int ret; @@ -380,8 +376,6 @@ decoder_run_song(struct decoder_control *dc, pcm_convert_deinit(&decoder.conv_state); /* flush the last chunk */ - if (decoder.replay_gain != NULL) - replay_gain_state_free(decoder.replay_gain); if (decoder.chunk != NULL) decoder_flush_chunk(&decoder); diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c new file mode 100644 index 000000000..396f185cc --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/replay_gain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "audio_format.h" +#include "pcm_buffer.h" +#include "pcm_volume.h" +#include "replay_gain_info.h" +#include "replay_gain_config.h" + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "replay_gain" + +struct replay_gain_filter { + struct filter filter; + + enum replay_gain_mode mode; + + struct replay_gain_info info; + + /** + * The current volume, between 0 and #PCM_VOLUME_1 (both + * including). + */ + unsigned volume; + + struct audio_format audio_format; + + struct pcm_buffer buffer; +}; + +static inline GQuark +replay_gain_quark(void) +{ + return g_quark_from_static_string("replay_gain"); +} + +/** + * Recalculates the new volume after a property was changed. + */ +static void +replay_gain_filter_update(struct replay_gain_filter *filter) +{ + if (filter->mode != REPLAY_GAIN_OFF) { + const struct replay_gain_tuple *tuple = + &filter->info.tuples[filter->mode]; + float scale = replay_gain_tuple_defined(tuple) + ? replay_gain_tuple_scale(tuple, replay_gain_preamp) + : replay_gain_missing_preamp; + g_debug("scale=%f\n", (double)scale); + + filter->volume = pcm_float_to_volume(scale); + } else + filter->volume = PCM_VOLUME_1; +} + +static struct filter * +replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1); + + filter_init(&filter->filter, &replay_gain_filter_plugin); + + filter->mode = replay_gain_mode; + replay_gain_info_init(&filter->info); + filter->volume = PCM_VOLUME_1; + + return &filter->filter; +} + +static void +replay_gain_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +replay_gain_filter_open(struct filter *_filter, + struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + audio_format->reverse_endian = false; + + filter->audio_format = *audio_format; + pcm_buffer_init(&filter->buffer); + + return &filter->audio_format; +} + +static void +replay_gain_filter_close(struct filter *_filter) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); +} + +static const void * +replay_gain_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + bool success; + void *dest; + + /* check if the mode has been changed since the last call */ + if (filter->mode != replay_gain_mode) { + filter->mode = replay_gain_mode; + replay_gain_filter_update(filter); + } + + *dest_size_r = src_size; + + if (filter->volume >= PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + dest = pcm_buffer_get(&filter->buffer, src_size); + *dest_size_r = src_size; + + if (filter->volume <= 0) { + /* optimized special case: 0% volume = memset(0) */ + /* XXX is this valid for all sample formats? What + about floating point? */ + memset(dest, 0, src_size); + return dest; + } + + memcpy(dest, src, src_size); + + success = pcm_volume(dest, src_size, &filter->audio_format, + filter->volume); + if (!success) { + g_set_error(error_r, replay_gain_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin replay_gain_filter_plugin = { + .name = "replay_gain", + .init = replay_gain_filter_init, + .finish = replay_gain_filter_finish, + .open = replay_gain_filter_open, + .close = replay_gain_filter_close, + .filter = replay_gain_filter_filter, +}; + +void +replay_gain_filter_set_info(struct filter *_filter, + const struct replay_gain_info *info) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + if (info != NULL) + filter->info = *info; + else + replay_gain_info_init(&filter->info); + + replay_gain_filter_update(filter); +} diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h new file mode 100644 index 000000000..4ab9f42a6 --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef REPLAY_GAIN_FILTER_PLUGIN_H +#define REPLAY_GAIN_FILTER_PLUGIN_H + +#include "replay_gain_info.h" + +struct filter; + +/** + * Sets a new #replay_gain_info at the beginning of a new song. + * + * @param info the new #replay_gain_info value, or NULL if no replay + * gain data is available for the current song + */ +void +replay_gain_filter_set_info(struct filter *filter, + const struct replay_gain_info *info); + +#endif diff --git a/src/filter_registry.c b/src/filter_registry.c index 577e69bad..150043cc5 100644 --- a/src/filter_registry.c +++ b/src/filter_registry.c @@ -29,6 +29,7 @@ const struct filter_plugin *const filter_plugins[] = { &route_filter_plugin, &normalize_filter_plugin, &volume_filter_plugin, + &replay_gain_filter_plugin, NULL, }; diff --git a/src/filter_registry.h b/src/filter_registry.h index 938afefb3..551a7afa1 100644 --- a/src/filter_registry.h +++ b/src/filter_registry.h @@ -32,6 +32,7 @@ extern const struct filter_plugin convert_filter_plugin; extern const struct filter_plugin route_filter_plugin; extern const struct filter_plugin normalize_filter_plugin; extern const struct filter_plugin volume_filter_plugin; +extern const struct filter_plugin replay_gain_filter_plugin; const struct filter_plugin * filter_plugin_by_name(const char *name); diff --git a/src/output_init.c b/src/output_init.c index 2ce6b30f9..387915ddc 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -194,6 +194,17 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ao->filter = filter_chain_new(); assert(ao->filter != NULL); + /* create the replay_gain filter */ + + ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->replay_gain_filter != NULL); + + filter_chain_append(ao->filter, ao->replay_gain_filter); + ao->replay_gain_serial = 0; + + /* create the normalization filter (if configured) */ + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { struct filter *normalize_filter = filter_new(&normalize_filter_plugin, NULL, NULL); diff --git a/src/output_internal.h b/src/output_internal.h index 804fd8cdc..2a438c62f 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -134,6 +134,18 @@ struct audio_output { */ struct filter *filter; + /** + * The replay_gain_filter_plugin instance of this audio + * output. + */ + struct filter *replay_gain_filter; + + /** + * The serial number of the last replay gain info. 0 means no + * replay gain info was available. + */ + unsigned replay_gain_serial; + /** * The convert_filter_plugin instance of this audio output. * It is the last item in the filter chain, and is responsible diff --git a/src/output_thread.c b/src/output_thread.c index eb44d4c87..0e34c64b7 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -26,6 +26,7 @@ #include "player_control.h" #include "filter_plugin.h" #include "filter/convert_filter_plugin.h" +#include "filter/replay_gain_filter_plugin.h" #include @@ -261,6 +262,16 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) g_mutex_lock(ao->mutex); } + /* update replay gain */ + + if (chunk->replay_gain_serial != ao->replay_gain_serial) { + replay_gain_filter_set_info(ao->replay_gain_filter, + chunk->replay_gain_serial != 0 + ? &chunk->replay_gain_info + : NULL); + ao->replay_gain_serial = chunk->replay_gain_serial; + } + if (size == 0) return true; diff --git a/src/replay_gain_state.c b/src/replay_gain_state.c deleted file mode 100644 index cfb2ce120..000000000 --- a/src/replay_gain_state.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program 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; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "replay_gain_state.h" -#include "pcm_volume.h" - -#include - -#include - -struct replay_gain_state { - float preamp, missing_preamp; - - enum replay_gain_mode mode; - - struct replay_gain_info info; - - float scale; -}; - -struct replay_gain_state * -replay_gain_state_new(float preamp, float missing_preamp) -{ - struct replay_gain_state *state = g_new(struct replay_gain_state, 1); - - state->preamp = preamp; - state->scale = state->missing_preamp = missing_preamp; - state->mode = REPLAY_GAIN_OFF; - replay_gain_info_init(&state->info); - - return state; -} - -void -replay_gain_state_free(struct replay_gain_state *state) -{ - assert(state != NULL); - - g_free(state); -} - -static void -replay_gain_state_calc_scale(struct replay_gain_state *state) -{ - assert(state != NULL); - - if (state->mode == REPLAY_GAIN_OFF) - return; - - const struct replay_gain_tuple *tuple = - &state->info.tuples[state->mode]; - if (replay_gain_tuple_defined(tuple)) { - g_debug("computing ReplayGain scale with gain %f, peak %f", - tuple->gain, tuple->peak); - - state->scale = replay_gain_tuple_scale(tuple, state->preamp); - } else - state->scale = state->missing_preamp; -} - -void -replay_gain_state_set_mode(struct replay_gain_state *state, - enum replay_gain_mode mode) -{ - assert(state != NULL); - - if (mode == state->mode) - return; - - state->mode = mode; - - replay_gain_state_calc_scale(state); -} - -void -replay_gain_state_set_info(struct replay_gain_state *state, - const struct replay_gain_info *info) -{ - assert(state != NULL); - - if (info != NULL) - state->info = *info; - else - replay_gain_info_init(&state->info); - - replay_gain_state_calc_scale(state); -} - -void -replay_gain_state_apply(const struct replay_gain_state *state, - void *buffer, size_t size, - const struct audio_format *format) -{ - assert(state != NULL); - - if (state->mode == REPLAY_GAIN_OFF) - return; - - pcm_volume(buffer, size, format, pcm_float_to_volume(state->scale)); -} diff --git a/src/replay_gain_state.h b/src/replay_gain_state.h deleted file mode 100644 index bf687aa05..000000000 --- a/src/replay_gain_state.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program 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; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_REPLAY_GAIN_STATE_H -#define MPD_REPLAY_GAIN_STATE_H - -#include "check.h" -#include "replay_gain_info.h" - -#include - -struct replay_gain_state; -struct audio_format; - -struct replay_gain_state * -replay_gain_state_new(float preamp, float missing_preamp); - -void -replay_gain_state_free(struct replay_gain_state *state); - -void -replay_gain_state_set_mode(struct replay_gain_state *state, - enum replay_gain_mode mode); - -void -replay_gain_state_set_info(struct replay_gain_state *state, - const struct replay_gain_info *info); - -void -replay_gain_state_apply(const struct replay_gain_state *state, - void *buffer, size_t size, - const struct audio_format *format); - -#endif diff --git a/test/run_filter.c b/test/run_filter.c index 9b4d79510..dd22312d5 100644 --- a/test/run_filter.c +++ b/test/run_filter.c @@ -23,6 +23,7 @@ #include "audio_format.h" #include "filter_plugin.h" #include "pcm_volume.h" +#include "idle.h" #include @@ -31,6 +32,11 @@ #include #include +void +idle_add(G_GNUC_UNUSED unsigned flags) +{ +} + static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, const gchar *message, G_GNUC_UNUSED gpointer user_data) diff --git a/test/run_output.c b/test/run_output.c index 774ce99f9..6e0fffbd0 100644 --- a/test/run_output.c +++ b/test/run_output.c @@ -26,6 +26,7 @@ #include "filter_registry.h" #include "pcm_convert.h" #include "event_pipe.h" +#include "idle.h" #include @@ -33,6 +34,11 @@ #include #include +void +idle_add(G_GNUC_UNUSED unsigned flags) +{ +} + void event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) { -- cgit v1.2.3