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.
This commit is contained in:
Max Kellermann 2010-02-14 17:04:39 +01:00
parent 5e0117b444
commit 752dfb3d95
18 changed files with 310 additions and 161 deletions

View File

@ -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)

2
NEWS
View File

@ -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

View File

@ -29,6 +29,7 @@ music_chunk_init(struct music_chunk *chunk)
{
chunk->length = 0;
chunk->tag = NULL;
chunk->replay_gain_serial = 0;
}
void

View File

@ -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];

View File

@ -27,8 +27,6 @@
#include "buffer.h"
#include "pipe.h"
#include "chunk.h"
#include "replay_gain_state.h"
#include "replay_gain_config.h"
#include <glib.h>
@ -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;
}

View File

@ -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);

View File

@ -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;
};
/**

View File

@ -31,8 +31,6 @@
#include "mapper.h"
#include "path.h"
#include "uri.h"
#include "replay_gain_state.h"
#include "replay_gain_config.h"
#include <glib.h>
@ -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);

View File

@ -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 <string.h>
#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);
}

View File

@ -17,34 +17,21 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_REPLAY_GAIN_STATE_H
#define MPD_REPLAY_GAIN_STATE_H
#ifndef REPLAY_GAIN_FILTER_PLUGIN_H
#define REPLAY_GAIN_FILTER_PLUGIN_H
#include "check.h"
#include "replay_gain_info.h"
#include <stddef.h>
struct replay_gain_state;
struct audio_format;
struct replay_gain_state *
replay_gain_state_new(float preamp, float missing_preamp);
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_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);
replay_gain_filter_set_info(struct filter *filter,
const struct replay_gain_info *info);
#endif

View File

@ -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,
};

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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 <glib.h>
@ -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;

View File

@ -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 <glib.h>
#include <assert.h>
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));
}

View File

@ -23,6 +23,7 @@
#include "audio_format.h"
#include "filter_plugin.h"
#include "pcm_volume.h"
#include "idle.h"
#include <glib.h>
@ -31,6 +32,11 @@
#include <errno.h>
#include <unistd.h>
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)

View File

@ -26,6 +26,7 @@
#include "filter_registry.h"
#include "pcm_convert.h"
#include "event_pipe.h"
#include "idle.h"
#include <glib.h>
@ -33,6 +34,11 @@
#include <string.h>
#include <unistd.h>
void
idle_add(G_GNUC_UNUSED unsigned flags)
{
}
void
event_pipe_emit(G_GNUC_UNUSED enum pipe_event event)
{