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 4bcc88a..1c4e046 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 ab1c618..acdbece 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 cdd4832..6a95776 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 4570e19..422ddfd 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 af70a6f..c3b42f4 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 0250a5e..a4d081f 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 5bdfe03..9d6743f 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 8c57b3b..0be4a78 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));