From 0eb69b6d45c54deda1724b2db2202cf4057b0309 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 28 Oct 2012 15:10:59 +0100 Subject: [PATCH 1/1] resample filter: Implementation. 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 | 7 +- check_wav.c | 7 +- check_wav.h | 3 +- error.h | 7 +- m4/gengetopt/makefile | 5 +- m4/gengetopt/resample_filter.m4 | 15 +- resample_filter.c | 249 +++++++++++++++++++++++++++++++- write.c | 2 +- 8 files changed, 283 insertions(+), 12 deletions(-) diff --git a/buffer_tree.c b/buffer_tree.c index 4bcc88ab..1c4e046a 100644 --- a/buffer_tree.c +++ b/buffer_tree.c @@ -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; } diff --git a/check_wav.c b/check_wav.c index ab1c6188..acdbece1 100644 --- a/check_wav.c +++ b/check_wav.c @@ -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; diff --git a/check_wav.h b/check_wav.h index cdd48320..6a957765 100644 --- a/check_wav.h +++ b/check_wav.h @@ -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 4570e194..422ddfd2 100644 --- 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"), \ diff --git a/m4/gengetopt/makefile b/m4/gengetopt/makefile index af70a6f4..c3b42f44 100644 --- a/m4/gengetopt/makefile +++ b/m4/gengetopt/makefile @@ -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) $< > $@ diff --git a/m4/gengetopt/resample_filter.m4 b/m4/gengetopt/resample_filter.m4 index 0250a5e9..a4d081ff 100644 --- a/m4/gengetopt/resample_filter.m4 +++ b/m4/gengetopt/resample_filter.m4 @@ -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 diff --git a/resample_filter.c b/resample_filter.c index 5bdfe037..9d6743f1 100644 --- a/resample_filter.c +++ b/resample_filter.c @@ -7,6 +7,7 @@ /** \file resample_filter.c A sample rate converter based on libsamplerate. */ #include +#include #include "resample_filter.cmdline.h" #include "para.h" @@ -17,35 +18,278 @@ #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 8c57b3bd..0be4a783 100644 --- 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)); -- 2.39.2