resample filter: Implementation.
[paraslash.git] / resample_filter.c
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