]> git.tue.mpg.de Git - paraslash.git/commitdiff
resample filter: Implementation.
authorAndre Noll <maan@systemlinux.org>
Sun, 28 Oct 2012 14:10:59 +0000 (15:10 +0100)
committerAndre Noll <maan@systemlinux.org>
Sun, 25 Nov 2012 13:08:55 +0000 (14:08 +0100)
The resample filter allows to change the sample rate of a stream on the
fly. All the magic happens within libsamplerate, so the implementation
is quite simple.

However, one tricky thing to consider is how wav headers are treated:
Although the resample filter creates only a single task, it adds
two different nodes to the buffer tree, one for the wav detector and
another one for the resample filter itself.

At startup the generic code of para_filter or para_audiod adds only
the resample filter node, which in turn inserts the wav detector as
its own parent node. This requires to insert a new internal node to
the buffer tree which is currently not supported by the buffer tree
API. It is easy to implement this feature though, so this commit adds
the missing functionality to buffer_tree.c.

buffer_tree.c
check_wav.c
check_wav.h
error.h
m4/gengetopt/makefile
m4/gengetopt/resample_filter.m4
resample_filter.c
write.c

index 4bcc88abb9e56be5b5c29d7b9b9115ff84dfc009..1c4e046ad8f2aa478bcd4eaf5d94171f0224fd63 100644 (file)
@@ -295,9 +295,10 @@ struct btr_node *btr_new_node(struct btr_node_description *bnd)
                bnd->child->parent = btrn;
                goto out;
        }
-       PARA_EMERG_LOG("inserting internal nodes not yet supported.\n");
-       exit(EXIT_FAILURE);
-       assert(bnd->child->parent == bnd->parent);
+       list_add_tail(&btrn->node, &bnd->parent->children);
+       list_move(&bnd->child->node, &btrn->children);
+       bnd->child->parent = btrn;
+       PARA_INFO_LOG("added %s as internal node\n", bnd->name);
 out:
        return btrn;
 }
index ab1c61887cc1ac3536dd8c364fdd0a9a1bd5cf7b..acdbece13194185842fb79e5307b9b36ebd9d41f 100644 (file)
@@ -104,6 +104,8 @@ int check_wav_post_select(struct check_wav_context *cwc)
        uint16_t bps; /* bits per sample */
        const char *sample_formats[] = {SAMPLE_FORMATS};
 
+       if (!btrn)
+               return 0;
        ret = btr_node_status(btrn, cwc->min_iqs, BTR_NT_INTERNAL);
        if (ret <= 0)
                goto out;
@@ -159,7 +161,8 @@ out:
 }
 
 struct check_wav_context *check_wav_init(struct btr_node *parent,
-               struct wav_params *params, struct btr_node **cw_btrn)
+               struct btr_node *child, struct wav_params *params,
+               struct btr_node **cw_btrn)
 {
        struct check_wav_context *cwc = para_calloc(sizeof(*cwc));
 
@@ -167,7 +170,7 @@ struct check_wav_context *check_wav_init(struct btr_node *parent,
        cwc->min_iqs = WAV_HEADER_LEN;
        cwc->params = *params;
        cwc->btrn = btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = "check_wav", .parent = parent,
+               EMBRACE(.name = "check_wav", .parent = parent, .child = child,
                .handler = check_wav_exec, .context = cwc));
        if (cw_btrn)
                *cw_btrn = cwc->btrn;
index cdd483200782499d0fb360bc36779fe2dbced41d..6a9577658f9743f116a7ec314387131e1522505e 100644 (file)
@@ -39,7 +39,8 @@ struct wav_params {
        (dst)->sample_format_given = (src)->sample_format_given;
 
 struct check_wav_context *check_wav_init(struct btr_node *parent,
-               struct wav_params *params, struct btr_node **cw_btrn);
+               struct btr_node *child, struct wav_params *params,
+               struct btr_node **cw_btrn);
 void check_wav_pre_select(struct sched *s, struct check_wav_context *cwc);
 int check_wav_post_select(struct check_wav_context *cwc);
 void check_wav_shutdown(struct check_wav_context *cwc);
diff --git a/error.h b/error.h
index 4570e19442115540b6187f7ae2b2f648e4437f37..422ddfd263fba2d08e05dcfe0cb6eb73b1aac23b 100644 (file)
--- a/error.h
+++ b/error.h
@@ -34,7 +34,6 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 #define STDIN_ERRORS
 #define WRITE_ERRORS
 #define CHECK_WAV_ERRORS
-#define RESAMPLE_FILTER_ERRORS
 
 extern const char **para_errlist[];
 
@@ -47,6 +46,12 @@ extern const char **para_errlist[];
        PARA_ERROR(ALSA_MIX_GET_VAL, "could not read control element state"), \
        PARA_ERROR(ALSA_MIX_SET_VAL, "could not set control element state"), \
 
+
+#define RESAMPLE_FILTER_ERRORS \
+       PARA_ERROR(RESAMPLE_EOF, "resample filter: end of file"), \
+       PARA_ERROR(LIBSAMPLERATE, "secret rabbit code error"), \
+
+
 #define SIDEBAND_ERRORS \
        PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \
        PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
index af70a6f49601da8ff2f1a9cfeac3329d14969166..c3b42f441ffa4b445c193849180b51404c0d0009 100644 (file)
@@ -37,7 +37,10 @@ $(ggo_dir)/client.ggo: \
        $(m4_ggo_dir)/history_file.m4 \
        $(m4_ggo_dir)/complete.m4
 $(ggo_dir)/fade.ggo: $(m4_ggo_dir)/loglevel.m4 $(m4_ggo_dir)/config_file.m4
-
+$(ggo_dir)/resample_filter.ggo: \
+       $(m4_ggo_dir)/channels.m4 \
+       $(m4_ggo_dir)/sample_rate.m4 \
+       $(m4_ggo_dir)/sample_format.m4
 $(ggo_dir)/%.ggo: $(m4_ggo_dir)/%.m4 $(m4_ggo_dir)/header.m4 | $(ggo_dir)
        @[ -z "$(Q)" ] || echo 'M4 $<'
        $(Q) m4 -I $(m4_ggo_dir) $< > $@
index 0250a5e9a7a623c8dd227ed3dc2fff01fdf1c31f..a4d081ff51852a5f6e39a41ad320da5b0b92e6c3 100644 (file)
@@ -1,4 +1,6 @@
-option "converter" c
+include(header.m4)
+
+option "converter" C
 #~~~~~~~~~~~~~~~~~~~
 "choose converter type"
 enum typestr = "type"
@@ -26,3 +28,14 @@ details = "
        linear: A linear converter. Again the quality is poor, but
        the conversion speed is blindingly fast.
 "
+
+include(channels.m4)
+include(sample_rate.m4)
+include(sample_format.m4)
+
+option "dest-sample-rate" d
+#~~~~~~~~~~~~~~~~~~~~~~~~~~
+"sample rate to convert to"
+int typestr = "rate"
+default = "44100"
+optional
index 5bdfe03755d41d822a17cbc117b40f56ae6193f6..9d6743f12c61beceb13bb42a7545cf03d8f16d2b 100644 (file)
@@ -7,6 +7,7 @@
 /** \file resample_filter.c A sample rate converter based on libsamplerate. */
 
 #include <regex.h>
+#include <samplerate.h>
 
 #include "resample_filter.cmdline.h"
 #include "para.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
+#include "check_wav.h"
+
+struct resample_context {
+       int channels;
+       int source_sample_rate;
+       float ratio;
+       SRC_STATE *src_state;
+       struct check_wav_context *cwc;
+};
+
+static int resample_execute(struct btr_node *btrn, const char *cmd, char **result)
+{
+       struct filter_node *fn = btr_context(btrn);
+       struct resample_context *ctx = fn->private_data;
+       struct resample_filter_args_info *conf = fn->conf;
+
+       return decoder_execute(cmd, conf->dest_sample_rate_arg, ctx->channels,
+               result);
+}
 
 static void resample_close(struct filter_node *fn)
 {
-       free(fn->private_data);
+       struct resample_context *ctx = fn->private_data;
+
+       if (!ctx)
+               return;
+       check_wav_shutdown(ctx->cwc);
+       if (ctx->src_state)
+               src_delete(ctx->src_state);
+       free(ctx);
        fn->private_data = NULL;
 }
 
 static void resample_open(struct filter_node *fn)
 {
+       struct resample_context *ctx = para_calloc(sizeof(*ctx));
+       struct resample_filter_args_info *conf = fn->conf;
+       struct btr_node *btrn = fn->btrn;
+       struct wav_params wp;
+
+       fn->private_data = ctx;
+       fn->min_iqs = 2;
+       COPY_WAV_PARMS(&wp, conf);
+       ctx->cwc = check_wav_init(btr_parent(btrn), btrn, &wp, NULL);
+       btr_log_tree(btr_parent(btr_parent(btrn)), LL_INFO);
 }
 
 static void resample_pre_select(struct sched *s, struct task *t)
 {
        struct filter_node *fn = container_of(t, struct filter_node, task);
+       struct resample_context *ctx = fn->private_data;
+       int ret = btr_node_status(fn->btrn, fn->min_iqs, BTR_NT_INTERNAL);
+
+       if (ret != 0)
+               return sched_min_delay(s);
+       check_wav_pre_select(s, ctx->cwc);
+}
+
+static int get_btr_val(const char *what, struct btr_node *btrn)
+{
+       char *buf;
+       int32_t val;
+       int ret = btr_exec_up(btr_parent(btrn), what, &buf);
+
+       if (ret < 0) {
+               PARA_NOTICE_LOG("btr exec for \"%s\" failed\n", what);
+               return ret;
+       }
+       ret = para_atoi32(buf, &val);
+       free(buf);
+       return ret < 0? ret : val;
+}
+
+static int resample_set_params(struct filter_node *fn)
+{
+       int ret;
+       struct resample_context *ctx = fn->private_data;
+       struct resample_filter_args_info *conf = fn->conf;
+       struct btr_node *btrn = fn->btrn;
+
+       ctx->channels = conf->channels_arg;
+       if (!conf->channels_given) {
+               ret = get_btr_val("channels", btrn);
+               if (ret >= 0)
+                       ctx->channels = ret;
+       }
+
+       ctx->source_sample_rate = conf->sample_rate_arg;
+       if (!conf->sample_rate_given) {
+               ret = get_btr_val("sample_rate", btrn);
+               if (ret >= 0)
+                       ctx->source_sample_rate = ret;
+       }
+       /* reject all sample formats except 16 bit signed, little endian */
+       ret = get_btr_val("sample_format", btrn);
+       if (ret >= 0 && ret != SF_S16_LE) {
+               const char *sample_formats[] = {SAMPLE_FORMATS};
+               PARA_ERROR_LOG("unsupported sample format: %s\n",
+                       sample_formats[ret]);
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       }
+       ctx->ratio = (float)conf->dest_sample_rate_arg / ctx->source_sample_rate;
+       return 1;
+}
+
+static int resample_init(struct filter_node *fn)
+{
+       int ret, converter;
+       struct resample_context *ctx = fn->private_data;
+       struct resample_filter_args_info *conf = fn->conf;
+       struct btr_node *btrn = fn->btrn;
+
+       ret = -E_RESAMPLE_EOF;
+       if (btr_no_parent(btrn))
+               return ret;
+       if (btr_get_input_queue_size(btrn) == 0)
+               return 0;
+       ret = resample_set_params(fn);
+       if (ret < 0)
+               return ret;
+       switch (conf->converter_arg) {
+       case converter_arg_best:
+               converter = SRC_SINC_BEST_QUALITY;
+               break;
+       case converter_arg_medium:
+               converter = SRC_SINC_MEDIUM_QUALITY;
+               break;
+       case converter_arg_fastest:
+               converter = SRC_SINC_FASTEST;
+               break;
+       case converter_arg_zero_order_hold:
+               converter = SRC_ZERO_ORDER_HOLD;
+               break;
+       case converter_arg_linear:
+               converter = SRC_LINEAR;
+               break;
+       default:
+               assert(0);
+       }
+       ctx->src_state = src_new(converter, conf->channels_arg, &ret);
+       if (!ctx->src_state) {
+               PARA_ERROR_LOG("%s\n", src_strerror(ret));
+               return -E_LIBSAMPLERATE;
+       }
+       fn->min_iqs = 2 * ctx->channels;
+       return 1;
+}
+
+/* returns number of input frames used */
+static int resample_frames(int16_t *in, size_t num_frames, bool have_more,
+               struct resample_context *ctx, int16_t **result,
+               size_t *result_frames)
+{
+       int ret, num_samples, out_samples;
+       int16_t *out;
+       SRC_DATA data;
+
+       data.src_ratio = ctx->ratio;
+       data.end_of_input = !have_more;
+
+       data.input_frames = num_frames;
+       num_samples = num_frames * ctx->channels;
+       data.output_frames = num_frames * ctx->ratio + 1;
+       out_samples = data.output_frames * ctx->channels;
+
+       data.data_in = para_malloc(num_samples * sizeof(float));
+       src_short_to_float_array(in, data.data_in, num_samples);
+       data.data_out = para_malloc(out_samples * sizeof(float));
+       ret = src_process(ctx->src_state, &data);
+       free(data.data_in);
+       if (ret != 0) {
+               PARA_ERROR_LOG("%s\n", src_strerror(ret));
+               free(data.data_out);
+               return -E_LIBSAMPLERATE;
+       }
+       out_samples = data.output_frames_gen * ctx->channels;
+       out = para_malloc(out_samples * sizeof(short));
+       src_float_to_short_array(data.data_out, out, out_samples);
+       free(data.data_out);
+       *result = out;
+       *result_frames = data.output_frames_gen;
+       return data.input_frames_used;
 }
 
 static void resample_post_select(__a_unused struct sched *s, struct task *t)
 {
+       int ret;
        struct filter_node *fn = container_of(t, struct filter_node, task);
+       struct resample_context *ctx = fn->private_data;
+       struct resample_filter_args_info *conf = fn->conf;
+       struct btr_node *btrn = fn->btrn;
+       int16_t *in, *out;
+       size_t in_bytes, num_frames;
+       bool have_more;
+
+       ret = check_wav_post_select(ctx->cwc);
+       if (ret < 0)
+               goto out;
+       if (!ctx->src_state) {
+               ret = resample_init(fn);
+               if (ret <= 0)
+                       goto out;
+       }
+       ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+       if (ret <= 0)
+               goto out;
+       if (ctx->source_sample_rate == conf->dest_sample_rate_arg) {
+               /*
+                * No resampling necessary. We do not splice ourselves out
+                * though, since our children might want to ask us through the
+                * btr exec mechanism for the destination samplerate and the
+                * channel count.
+                */
+               return btr_pushdown(btrn);
+       }
+       btr_merge(btrn, fn->min_iqs);
+       in_bytes = btr_next_buffer(btrn, (char **)&in);
+       ret = -E_RESAMPLE_EOF;
+       num_frames = in_bytes / 2 / ctx->channels;
+       if (num_frames == 0)
+               goto out;
+       have_more = !btr_no_parent(btrn) ||
+               btr_next_buffer_omit(btrn, in_bytes, NULL) > 0;
+       ret = resample_frames(in, num_frames, have_more, ctx, &out, &num_frames);
+       if (ret < 0)
+               goto out;
+       btr_consume(btrn, ret * 2 * ctx->channels);
+       btr_add_output((char *)out, num_frames * 2 * ctx->channels, btrn);
+       return;
+out:
+       if (ret < 0) {
+               t->error = ret;
+               btr_remove_node(&fn->btrn);
+               /* This releases the check_wav btr node */
+               check_wav_post_select(ctx->cwc);
+       }
 }
 
 static int resample_parse_config(int argc, char **argv, void **config)
 {
-       return 0;
+       int ret, val, given;
+       struct resample_filter_args_info *conf = para_calloc(sizeof(*conf));
+
+       resample_filter_cmdline_parser(argc, argv, conf);
+
+       /* sanity checks */
+       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+       val = conf->channels_arg;
+       given = conf->channels_given;
+       if (val < 0 || (val == 0 && given))
+               goto err;
+       val = conf->sample_rate_arg;
+       given = conf->sample_rate_given;
+       if (val < 0 || (val == 0 && given))
+               goto err;
+       val = conf->dest_sample_rate_arg;
+       given = conf->dest_sample_rate_given;
+       if (val < 0 || (val == 0 && given))
+               goto err;
+       *config = conf;
+       return 1;
+err:
+       free(conf);
+       return ret;
 }
 
 static void resample_free_config(void *conf)
 {
+       if (!conf)
+               return;
        resample_filter_cmdline_parser_free(conf);
+       free(conf);
 }
 
 /**
@@ -64,6 +308,7 @@ void resample_filter_init(struct filter *f)
        f->post_select = resample_post_select;
        f->parse_config = resample_parse_config;
        f->free_config = resample_free_config;
+       f->execute = resample_execute;
        f->help = (struct ggo_help) {
                .short_help = resample_filter_args_info_help,
                .detailed_help = resample_filter_args_info_detailed_help
diff --git a/write.c b/write.c
index 8c57b3bde28fc45d2337f316a0c7cf8d41f4d112..0be4a783cfb196a8b8b66f43c00a91abca578c54 100644 (file)
--- a/write.c
+++ b/write.c
@@ -114,7 +114,7 @@ static int setup_and_schedule(void)
        register_task(&s, &sit.task);
 
        COPY_WAV_PARMS(&wp, &conf);
-       wt.cwc = check_wav_init(sit.btrn, &wp, &cw_btrn);
+       wt.cwc = check_wav_init(sit.btrn, NULL, &wp, &cw_btrn);
        register_task(&s, &wt.task);
        if (!conf.writer_given) {
                wns = para_calloc(sizeof(*wns));