From e47bdfe8e6f7da67c9714db7e650fa6a925f7847 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2009 10:01:47 +0200 Subject: output: attach a filter chain to each audio_output This patch adds initial filter support for audio outputs. Each audio output gets a "filter" attribute, which is used by ao_play_chunk(). The PCM conversion is now performed by convert_filter_plugin. audio_output.convert_state has been removed. --- Makefile.am | 2 ++ src/output_control.c | 3 ++ src/output_init.c | 15 ++++++++- src/output_internal.h | 15 +++++++-- src/output_thread.c | 92 ++++++++++++++++++++++++++++++++++++++++++--------- test/run_output.c | 23 +++++++++++++ 6 files changed, 132 insertions(+), 18 deletions(-) diff --git a/Makefile.am b/Makefile.am index 11397ac3d..d7551c11f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -739,6 +739,8 @@ test_run_output_SOURCES = test/run_output.c \ src/mixer_api.c \ src/mixer_control.c \ $(MIXER_SRC) \ + src/filter_plugin.c src/filter/chain_filter_plugin.c \ + src/filter/convert_filter_plugin.c \ $(OUTPUT_SRC) test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \ diff --git a/src/output_control.c b/src/output_control.c index 2c193c30f..70c6d2b1a 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -23,6 +23,7 @@ #include "output_thread.h" #include "mixer_control.h" #include "mixer_plugin.h" +#include "filter_plugin.h" #include #include @@ -171,4 +172,6 @@ void audio_output_finish(struct audio_output *ao) notify_deinit(&ao->notify); g_mutex_free(ao->mutex); + + filter_free(ao->filter); } diff --git a/src/output_init.c b/src/output_init.c index d72893ba8..3b2c9d54f 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -23,9 +23,14 @@ #include "output_list.h" #include "audio_parser.h" #include "mixer_control.h" +#include "filter_plugin.h" +#include "filter_registry.h" +#include "filter/chain_filter_plugin.h" #include +#include + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "output" @@ -124,7 +129,15 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ao->open = false; ao->fail_timer = NULL; - pcm_convert_init(&ao->convert_state); + /* set up the filter chain */ + + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); + + ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL); + assert(ao->convert_filter != NULL); + + filter_chain_append(ao->filter, ao->convert_filter); ao->config_audio_format = format != NULL; if (ao->config_audio_format) { diff --git a/src/output_internal.h b/src/output_internal.h index 5f7a24060..6ca179287 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -21,7 +21,6 @@ #define MPD_OUTPUT_INTERNAL_H #include "audio_format.h" -#include "pcm_convert.h" #include "notify.h" #include @@ -108,7 +107,19 @@ struct audio_output { */ struct audio_format out_audio_format; - struct pcm_convert_state convert_state; + /** + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. + */ + struct filter *filter; + + /** + * 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 + * for this audio output. + */ + struct filter *convert_filter; /** * The thread handle, or NULL if the output thread isn't diff --git a/src/output_thread.c b/src/output_thread.c index b3c134413..4b60d9d60 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -23,6 +23,8 @@ #include "chunk.h" #include "pipe.h" #include "player_control.h" +#include "filter_plugin.h" +#include "filter/convert_filter_plugin.h" #include @@ -45,12 +47,29 @@ ao_open(struct audio_output *ao) { bool success; GError *error = NULL; + const struct audio_format *filter_audio_format; assert(!ao->open); assert(ao->fail_timer == NULL); assert(ao->pipe != NULL); assert(ao->chunk == NULL); + /* open the filter */ + + filter_audio_format = filter_open(ao->filter, &ao->in_audio_format, + &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao->fail_timer = g_timer_new(); + return; + } + + if (!ao->config_audio_format) + ao->out_audio_format = *filter_audio_format; + success = ao_plugin_open(ao->plugin, ao->data, &ao->out_audio_format, &error); @@ -62,11 +81,12 @@ ao_open(struct audio_output *ao) ao->name, ao->plugin->name, error->message); g_error_free(error); + filter_close(ao->filter); ao->fail_timer = g_timer_new(); return; } - pcm_convert_init(&ao->convert_state); + convert_filter_set(ao->convert_filter, &ao->out_audio_format); g_mutex_lock(ao->mutex); ao->open = true; @@ -100,11 +120,45 @@ ao_close(struct audio_output *ao) g_mutex_unlock(ao->mutex); ao_plugin_close(ao->plugin, ao->data); - pcm_convert_deinit(&ao->convert_state); + filter_close(ao->filter); g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); } +static void +ao_reopen_filter(struct audio_output *ao) +{ + const struct audio_format *filter_audio_format; + GError *error = NULL; + + filter_close(ao->filter); + filter_audio_format = filter_open(ao->filter, &ao->in_audio_format, + &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + /* this is a little code duplication fro ao_close(), + but we cannot call this function because we must + not call filter_close(ao->filter) again */ + + ao->pipe = NULL; + + g_mutex_lock(ao->mutex); + ao->chunk = NULL; + ao->open = false; + g_mutex_unlock(ao->mutex); + + ao_plugin_close(ao->plugin, ao->data); + + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); +} + static void ao_reopen(struct audio_output *ao) { @@ -121,7 +175,11 @@ ao_reopen(struct audio_output *ao) ao->out_audio_format = ao->in_audio_format; } - if (!ao->open) + if (ao->open) + /* the audio format has changed, and all filters have + to be reconfigured */ + ao_reopen_filter(ao); + else ao_open(ao); } @@ -132,6 +190,8 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) size_t size = chunk->length; GError *error = NULL; + assert(ao != NULL); + assert(ao->filter != NULL); assert(!music_chunk_is_empty(chunk)); assert(music_chunk_check_format(chunk, &ao->in_audio_format)); assert(size % audio_format_frame_size(&ao->in_audio_format) == 0); @@ -142,18 +202,19 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) if (size == 0) return true; - if (!audio_format_equals(&ao->in_audio_format, - &ao->out_audio_format)) { - data = pcm_convert(&ao->convert_state, - &ao->in_audio_format, data, size, - &ao->out_audio_format, &size); - - /* under certain circumstances, pcm_convert() may - return an empty buffer - this condition should be - investigated further, but for now, do this check as - a workaround: */ - if (data == NULL) - return true; + data = filter_filter(ao->filter, data, size, &size, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao_plugin_cancel(ao->plugin, ao->data); + ao_close(ao); + + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer = g_timer_new(); + return false; } while (size > 0 && ao->command == AO_COMMAND_NONE) { @@ -271,6 +332,7 @@ static gpointer audio_output_task(gpointer arg) ao_plugin_cancel(ao->plugin, ao->data); ao_close(ao); + filter_close(ao->filter); ao_command_finished(ao); break; diff --git a/test/run_output.c b/test/run_output.c index 1a171198d..adf6e1dd9 100644 --- a/test/run_output.c +++ b/test/run_output.c @@ -22,6 +22,8 @@ #include "output_control.h" #include "conf.h" #include "audio_parser.h" +#include "filter_registry.h" +#include "pcm_convert.h" #include @@ -33,10 +35,31 @@ void pcm_convert_init(G_GNUC_UNUSED struct pcm_convert_state *state) { } +void pcm_convert_deinit(G_GNUC_UNUSED struct pcm_convert_state *state) +{ +} + +const void * +pcm_convert(G_GNUC_UNUSED struct pcm_convert_state *state, + G_GNUC_UNUSED const struct audio_format *src_format, + G_GNUC_UNUSED const void *src, G_GNUC_UNUSED size_t src_size, + G_GNUC_UNUSED const struct audio_format *dest_format, + G_GNUC_UNUSED size_t *dest_size_r) +{ + return NULL; +} + void notify_init(G_GNUC_UNUSED struct notify *notify) { } +const struct filter_plugin * +filter_plugin_by_name(G_GNUC_UNUSED const char *name) +{ + assert(false); + return NULL; +} + static const struct config_param * find_named_config_block(const char *block, const char *name) { -- cgit v1.2.3