Commit 752dfb3d authored by Max Kellermann's avatar Max Kellermann

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.
parent 5e0117b4
......@@ -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)
......
......@@ -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
......
......@@ -29,6 +29,7 @@ music_chunk_init(struct music_chunk *chunk)
{
chunk->length = 0;
chunk->tag = NULL;
chunk->replay_gain_serial = 0;
}
void
......
......@@ -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];
......
......@@ -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;
}
......@@ -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);
......
......@@ -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;
};
/**
......
......@@ -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);
......
/*
* 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);
}
......@@ -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);
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);
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_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
......@@ -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,
};
......
......@@ -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);
......
......@@ -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);
......
......@@ -135,6 +135,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
* for converting the input data into the appropriate format
......
......@@ -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;
......
/*
* 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));
}
......@@ -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)
......
......@@ -26,6 +26,7 @@
#include "filter_registry.h"
#include "pcm_convert.h"
#include "event_pipe.h"
#include "idle.h"
#include <glib.h>
......@@ -34,6 +35,11 @@
#include <unistd.h>
void
idle_add(G_GNUC_UNUSED unsigned flags)
{
}
void
event_pipe_emit(G_GNUC_UNUSED enum pipe_event event)
{
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment