]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/resample'
authorAndre Noll <maan@systemlinux.org>
Sun, 16 Dec 2012 23:09:22 +0000 (00:09 +0100)
committerAndre Noll <maan@systemlinux.org>
Sun, 16 Dec 2012 23:09:22 +0000 (00:09 +0100)
Was cooking for quite some time. The merge conflicted slightly,
but this was easy to fix.

However, even with the conflict resolved, the merged tree would not
compile because after the merge para_play depends on libsamplerate,
but this dependency was not encoded in configure.ac.

There was no way to fix this issue in either of the two branches
involved without rewriting history: The recently merged t/afh_receiver
branch (which introduced para_play) had no idea of libsamplerate
while t/resample has no idea of para_play.

This merge commit fixes both issues.

0eb69b resample filter: Implementation.
cad284 resample filter: Infrastructure.
37e0df check_wav: Ask parent nodes before falling back to defaults.
216399 Replace check_wav_task by write_task.
1af65c Move wav detection code to a separate file.
d5dc1c write: Make wav-related config options modular.
c25d04 para_filter: Call proper ->free_config method on shutdown.

Conflicts:
m4/gengetopt/makefile

15 files changed:
buffer_tree.c
buffer_tree.h
check_wav.c [new file with mode: 0644]
check_wav.h [new file with mode: 0644]
configure.ac
error.h
filter.c
m4/gengetopt/channels.m4 [new file with mode: 0644]
m4/gengetopt/makefile
m4/gengetopt/resample_filter.m4 [new file with mode: 0644]
m4/gengetopt/sample_format.m4 [new file with mode: 0644]
m4/gengetopt/sample_rate.m4 [new file with mode: 0644]
m4/gengetopt/write.m4
resample_filter.c [new file with mode: 0644]
write.c

index 63be36d7a10042c5f21b6a95677cdea369ffa12c..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;
 }
@@ -1225,3 +1226,8 @@ void btr_get_node_start(struct btr_node *btrn, struct timeval *tv)
 {
        *tv = btrn->start;
 }
+
+struct btr_node *btr_parent(struct btr_node *btrn)
+{
+       return btrn->parent;
+}
index b0817759ee145dfd4ef7bb9ddcb5412b6d00b0fd..071b055c9e21a5469a91f6f1830302fd48352369 100644 (file)
@@ -204,3 +204,4 @@ int btr_node_status(struct btr_node *btrn, size_t min_iqs,
 void btr_get_node_start(struct btr_node *btrn, struct timeval *tv);
 struct btr_node *btr_search_node(const char *name, struct btr_node *root);
 void btr_drain(struct btr_node *btrn);
+struct btr_node *btr_parent(struct btr_node *btrn);
diff --git a/check_wav.c b/check_wav.c
new file mode 100644 (file)
index 0000000..acdbece
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2005-2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file check_wav.c Detect and delete a wav header. */
+
+#include <regex.h>
+
+#include "para.h"
+#include "string.h"
+#include "list.h"
+#include "sched.h"
+#include "buffer_tree.h"
+#include "error.h"
+#include "check_wav.h"
+
+/** Length of a standard wav header. */
+#define WAV_HEADER_LEN 44
+
+enum check_wav_state {
+       CWS_NEED_HEADER,
+       CWS_HAVE_HEADER,
+       CWS_NO_HEADER,
+};
+
+struct check_wav_context {
+       enum check_wav_state state;
+       struct btr_node *btrn;
+       size_t min_iqs;
+       /* Command line args. */
+       struct wav_params params;
+       /* Extracted from the wav header.*/
+       unsigned channels;
+       unsigned sample_format;
+       unsigned sample_rate;
+};
+
+void check_wav_pre_select(struct sched *s, struct check_wav_context *cwc)
+{
+       int ret = btr_node_status(cwc->btrn, cwc->min_iqs, BTR_NT_INTERNAL);
+       if (ret != 0)
+               sched_min_delay(s);
+}
+
+static int check_wav_exec(struct btr_node *btrn, const char *cmd, char **result)
+{
+       struct check_wav_context *cwc = btr_context(btrn);
+       int val, header_val, given, arg;
+
+       header_val = cwc->channels;
+       arg = cwc->params.channels_arg;
+       given = cwc->params.channels_given;
+       if (!strcmp(cmd, "channels"))
+               goto out;
+
+       header_val = cwc->sample_rate;
+       arg = cwc->params.sample_rate_arg;
+       given = cwc->params.sample_rate_given;
+       if (!strcmp(cmd, "sample_rate"))
+               goto out;
+
+       header_val = cwc->sample_format;
+       arg = cwc->params.sample_format_arg;
+       given = cwc->params.sample_format_given;
+       if (!strcmp(cmd, "sample_format"))
+               goto out;
+
+       return -ERRNO_TO_PARA_ERROR(ENOTSUP);
+out:
+       if (given)
+               val = arg;
+       else {
+               switch (cwc->state) {
+               case CWS_HAVE_HEADER:
+                       val = header_val;
+                       break;
+               case CWS_NO_HEADER:
+                       /*
+                        * No wav header available and no value specified at
+                        * the command line. Maybe one of our parent nodes
+                        * knows.
+                        */
+                       if (btr_exec_up(btr_parent(cwc->btrn), cmd, result) >= 0)
+                               return 1;
+                       /* Use default value */
+                       val = arg;
+                       break;
+               default:
+                       return -E_BTR_NAVAIL;
+               }
+       }
+       *result = make_message("%d", val);
+       return 1;
+}
+
+int check_wav_post_select(struct check_wav_context *cwc)
+{
+       struct btr_node *btrn = cwc->btrn;
+       unsigned char *a;
+       size_t sz;
+       int ret;
+       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;
+       if (cwc->state != CWS_NEED_HEADER)
+               goto pushdown;
+       btr_merge(btrn, cwc->min_iqs);
+       sz = btr_next_buffer(btrn, (char **)&a);
+       if (sz < cwc->min_iqs) /* file size less than WAV_HEADER_SIZE */
+               goto pushdown;
+       cwc->min_iqs = 0;
+       /*
+        * The default byte ordering assumed for WAVE data files is
+        * little-endian. Files written using the big-endian byte ordering
+        * scheme have the identifier RIFX instead of RIFF.
+        */
+       if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' ||
+                       (a[3] != 'F' && a[3] != 'X')) {
+               PARA_NOTICE_LOG("wav header not found\n");
+               cwc->state = CWS_NO_HEADER;
+               goto out;
+       }
+       PARA_INFO_LOG("found wav header\n");
+       cwc->state = CWS_HAVE_HEADER;
+       /* Only set those values which have not already been set. */
+       cwc->channels = (unsigned)a[22];
+       cwc->sample_rate = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24);
+       bps = a[34] + ((unsigned)a[35] << 8);
+       if (bps != 8 && bps != 16) {
+               PARA_WARNING_LOG("%u bps not supported, assuming 16\n",
+                       bps);
+               bps = 16;
+       }
+       /*
+        * 8-bit samples are stored as unsigned bytes, ranging from 0
+        * to 255.  16-bit samples are stored as 2's-complement signed
+        * integers, ranging from -32768 to 32767.
+        */
+       if (bps == 8)
+               cwc->sample_format = SF_U8;
+       else
+               cwc->sample_format = (a[3] == 'F')?
+                       SF_S16_LE : SF_S16_BE;
+       PARA_NOTICE_LOG("%dHz, %s, %s\n", cwc->sample_rate,
+               cwc->channels == 1? "mono" : "stereo",
+               sample_formats[cwc->sample_format]);
+       btr_consume(btrn, WAV_HEADER_LEN);
+pushdown:
+       btr_pushdown(btrn);
+out:
+       if (ret < 0)
+               btr_remove_node(&cwc->btrn);
+       return ret;
+}
+
+struct check_wav_context *check_wav_init(struct btr_node *parent,
+               struct btr_node *child, struct wav_params *params,
+               struct btr_node **cw_btrn)
+{
+       struct check_wav_context *cwc = para_calloc(sizeof(*cwc));
+
+       cwc->state = CWS_NEED_HEADER;
+       cwc->min_iqs = WAV_HEADER_LEN;
+       cwc->params = *params;
+       cwc->btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = "check_wav", .parent = parent, .child = child,
+               .handler = check_wav_exec, .context = cwc));
+       if (cw_btrn)
+               *cw_btrn = cwc->btrn;
+       return cwc;
+}
+
+void check_wav_shutdown(struct check_wav_context *cwc)
+{
+       free(cwc);
+}
diff --git a/check_wav.h b/check_wav.h
new file mode 100644 (file)
index 0000000..6a95776
--- /dev/null
@@ -0,0 +1,46 @@
+struct check_wav_context;
+
+/**
+ * These come from the command line arguments.
+ *
+ * Different users of the check_wav API have different arg_info structs,
+ * so we need a universal variant for these.
+ */
+struct wav_params {
+       /** Number of channels, or the default value. */
+       int channels_arg;
+       /** Whether the channel count was given. */
+       int channels_given;
+       /** Same semantics as \a channels_count. */
+       int sample_rate_arg;
+       /** Whether the sample rate was given. */
+       int sample_rate_given;
+       /** Same semantics as \a sample_rate. */
+       int sample_format_arg;
+       /** Whether the sample format was given. */
+       int sample_format_given;
+};
+
+/**
+ * Copy the wav parameters.
+ *
+ * \param dst Usually a pointer to struct wav_params.
+ * \param src Usually a pointer to some args_info struct.
+ *
+ * This can not be implemented as a function since the type of the structure
+ * pointed to by \a src depends on the application.
+ */
+#define COPY_WAV_PARMS(dst, src) \
+       (dst)->channels_arg = (src)->channels_arg; \
+       (dst)->channels_given = (src)->channels_given; \
+       (dst)->sample_rate_arg = (src)->sample_rate_arg; \
+       (dst)->sample_rate_given = (src)->sample_rate_given; \
+       (dst)->sample_format_arg = (src)->sample_format_arg; \
+       (dst)->sample_format_given = (src)->sample_format_given;
+
+struct check_wav_context *check_wav_init(struct btr_node *parent,
+               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);
index 444f9ed112c24a499ff26e0247171a19d69d906f..f3e8fb070a2aa03254e2147c1c7392c32ad2ae24 100644 (file)
@@ -100,7 +100,7 @@ all_errlist_objs="mp3_afh afh_common net string signal time daemon
        dccp_recv recv_common write_common file_write audiod_command
        client_common recv stdout filter stdin audioc write client
        exec send_common ggo udp_recv color fec fecdec_filter
-       prebuffer_filter bitstream imdct
+       prebuffer_filter bitstream imdct check_wav
        wma_afh wma_common wmadec_filter buffer_tree crypt_common
        gui gui_theme sideband afh_recv play"
 
@@ -142,7 +142,7 @@ afh_ldflags=""
 
 write_cmdline_objs="add_cmdline(write file_write)"
 write_errlist_objs="write write_common file_write time fd string sched stdin
-       buffer_tree ggo"
+       buffer_tree ggo check_wav"
 write_ldflags=""
 writers=" file"
 default_writer="FILE_WRITE"
@@ -1107,6 +1107,47 @@ fi
 CPPFLAGS="$OLD_CPPFLAGS"
 LDFLAGS="$OLD_LDFLAGS"
 LIBS="$OLD_LIBS"
+############################################################# libsamplerate
+OLD_CPPFLAGS="$CPPFLAGS"
+OLD_LD_FLAGS="$LDFLAGS"
+OLD_LIBS="$LIBS"
+
+have_samplerate="yes"
+AC_ARG_WITH(samplerate_headers, [AS_HELP_STRING(--with-samplerate-headers=dir,
+       [look for samplerate headers also in dir])])
+if test -n "$with_samplerate_headers"; then
+       samplerate_cppflags="-I$with_samplerate_headers"
+       CPPFLAGS="$CPPFLAGS $samplerate_cppflags"
+fi
+AC_ARG_WITH(samplerate_libs, [AS_HELP_STRING(--with-samplerate-libs=dir,
+       [look for samplerate libs also in dir])])
+if test -n "$with_samplerate_libs"; then
+       samplerate_libs="-L$with_samplerate_libs"
+       LDFLAGS="$LDFLAGS $samplerate_libs"
+fi
+
+AC_CHECK_HEADER(samplerate.h, [], have_samplerate=no)
+AC_CHECK_LIB([samplerate], [src_process], [], have_samplerate=no, [])
+
+if test "$have_samplerate" = "yes"; then
+       all_errlist_objs="$all_errlist_objs resample_filter"
+       filter_errlist_objs="$filter_errlist_objs resample_filter check_wav"
+       filter_cmdline_objs="$filter_cmdline_objs add_cmdline(resample_filter)"
+       audiod_errlist_objs="$audiod_errlist_objs resample_filter check_wav"
+       audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(resample_filter)"
+       play_errlist_objs="$play_errlist_objs resample_filter check_wav"
+       play_cmdline_objs="$play_cmdline_objs add_cmdline(resample_filter)"
+       filter_ldflags="$filter_ldflags $samplerate_libs -lsamplerate"
+       audiod_ldflags="$audiod_ldflags $samplerate_libs -lsamplerate"
+       play_ldflags="$play_ldflags $samplerate_libs -lsamplerate"
+       filters="$filters resample"
+       AC_SUBST(samplerate_cppflags)
+else
+       AC_MSG_WARN([no resample support in para_audiod/para_filter])
+fi
+CPPFLAGS="$OLD_CPPFLAGS"
+LDFLAGS="$OLD_LDFLAGS"
+LIBS="$OLD_LIBS"
 ############################################################# error2.h
 AC_MSG_NOTICE(creating error2.h)
 for i in $executables; do
diff --git a/error.h b/error.h
index 981b2b794d40e2df30527245c1edb20d17d4e4bc..164f8adbf11d4bcf57251fc5832af23ce630d76f 100644 (file)
--- a/error.h
+++ b/error.h
@@ -33,6 +33,7 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 #define FILE_WRITE_ERRORS
 #define STDIN_ERRORS
 #define WRITE_ERRORS
+#define CHECK_WAV_ERRORS
 
 extern const char **para_errlist[];
 
@@ -45,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 55b48ea257a99a618968bf79c91097e88ecf4dae..38122977f4bad9f0982fe30594e4f36c50c64176 100644 (file)
--- a/filter.c
+++ b/filter.c
@@ -162,7 +162,8 @@ out_cleanup:
                if (f->close)
                        f->close(fn);
                btr_remove_node(&fn->btrn);
-               free(fn->conf);
+               if (f->free_config)
+                       f->free_config(fn->conf);
                free(fn);
        }
        free(fns);
diff --git a/m4/gengetopt/channels.m4 b/m4/gengetopt/channels.m4
new file mode 100644 (file)
index 0000000..64c2518
--- /dev/null
@@ -0,0 +1,14 @@
+<qu>
+option "channels" c
+#~~~~~~~~~~~~~~~~~~
+"specify number of channels"
+int typestr = "num"
+default = "2"
+optional
+details = "
+       It is only necessary to specify this option for raw audio. If
+       it is not given, the channel count is queried from the parent
+       buffer tree nodes (e.g. the decoder) or the wav header. Only
+       if this fails, the default value applies.
+"
+</qu>
index c88dfd984be2bd3864e710a257fc6244869cfd1e..138ac30b909f4b95bb0d2d50921f9930e4493840 100644 (file)
@@ -27,13 +27,20 @@ $(ggo_dir)/filter.ggo: $(m4_ggo_dir)/loglevel.m4
 $(ggo_dir)/fsck.ggo: $(m4_ggo_dir)/loglevel.m4
 $(ggo_dir)/gui.ggo: $(m4_ggo_dir)/loglevel.m4 $(m4_ggo_dir)/config_file.m4
 $(ggo_dir)/recv.ggo: $(m4_ggo_dir)/loglevel.m4
-$(ggo_dir)/write.ggo: $(m4_ggo_dir)/loglevel.m4
+$(ggo_dir)/write.ggo: $(m4_ggo_dir)/loglevel.m4 \
+       $(m4_ggo_dir)/channels.m4 \
+       $(m4_ggo_dir)/sample_rate.m4 \
+       $(m4_ggo_dir)/sample_format.m4
 $(ggo_dir)/client.ggo: \
        $(m4_ggo_dir)/loglevel.m4 \
        $(m4_ggo_dir)/config_file.m4 \
        $(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)/play.ggo: $(m4_ggo_dir)/loglevel.m4 $(m4_ggo_dir)/config_file.m4
 
 $(ggo_dir)/%.ggo: $(m4_ggo_dir)/%.m4 $(m4_ggo_dir)/header.m4 | $(ggo_dir)
diff --git a/m4/gengetopt/resample_filter.m4 b/m4/gengetopt/resample_filter.m4
new file mode 100644 (file)
index 0000000..a4d081f
--- /dev/null
@@ -0,0 +1,41 @@
+include(header.m4)
+
+option "converter" C
+#~~~~~~~~~~~~~~~~~~~
+"choose converter type"
+enum typestr = "type"
+values = "best", "medium", "fastest", "zero_order_hold", "linear"
+default = "medium"
+optional
+
+details = "
+       best: This is a bandlimited interpolator derived from the
+       mathematical sinc function and this is the highest quality
+       sinc based converter, providing a worst case Signal-to-Noise
+       Ratio (SNR) of 97 decibels (dB) at a bandwidth of 97%.
+
+       medium: This is another bandlimited interpolator much like the
+       previous one. It has an SNR of 97dB and a bandwidth of 90%. The
+       speed of the conversion is much faster than the previous one.
+
+       fastest: This is the fastest bandlimited interpolator and
+       has an SNR of 97dB and a bandwidth of 80%.
+
+       zero_order_hold: A Zero Order Hold converter (interpolated
+       value is equal to the last value). The quality is poor but
+       the conversion speed is blindlingly fast.
+
+       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/m4/gengetopt/sample_format.m4 b/m4/gengetopt/sample_format.m4
new file mode 100644 (file)
index 0000000..0a10033
--- /dev/null
@@ -0,0 +1,13 @@
+<qu>
+option "sample-format" f
+#~~~~~~~~~~~~~~~~~~~~~~~
+"specify sample format"
+# This must match the enum sample_format of para.h
+values = "S8", "U8", "S16_LE", "S16_BE", "U16_LE", "U16_BE" enum
+default = "S16_LE"
+optional
+details = "
+       It is only neccessary to specify this for raw audio. See the
+       discussion of the --channels option.
+"
+</qu>
diff --git a/m4/gengetopt/sample_rate.m4 b/m4/gengetopt/sample_rate.m4
new file mode 100644 (file)
index 0000000..b3d107d
--- /dev/null
@@ -0,0 +1,12 @@
+<qu>
+option "sample-rate" s
+#~~~~~~~~~~~~~~~~~~~~~
+"do not guess the input sample rate"
+int typestr = "num"
+default = "44100"
+optional
+details = "
+       It is only neccessary to specify this for raw audio. See the
+       discussion of the --channels option.
+"
+</qu>
index 56698e187839786bdbb86558cc8c1171b945dba9..c022bc8f24c83d6ed490cde57159d64b7e571538 100644 (file)
@@ -15,31 +15,6 @@ details="
        more than once.
 "
 
-text "
-       The following options are only necessary for raw audio. When
-       playing wav files this information is obtained from the
-       wave header.
-"
-
-option "channels" c
-#~~~~~~~~~~~~~~~~~~
-"specify number of channels"
-int typestr = "num"
-default = "2"
-optional
-
-option "sample-rate" s
-#~~~~~~~~~~~~~~~~~~~~~
-"force given sample rate"
-int typestr = "num"
-default = "44100"
-optional
-
-option "sample-format" f
-#~~~~~~~~~~~~~~~~~~~~~~~
-"specify sample format"
-# This must match the enum sample_format of para.h
-values = "S8", "U8", "S16_LE", "S16_BE", "U16_LE", "U16_BE" enum
-default = "S16_LE"
-optional
-
+include(channels.m4)
+include(sample_rate.m4)
+include(sample_format.m4)
diff --git a/resample_filter.c b/resample_filter.c
new file mode 100644 (file)
index 0000000..9d6743f
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \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 "error.h"
+#include "list.h"
+#include "sched.h"
+#include "ggo.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)
+{
+       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)
+{
+       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);
+}
+
+/**
+ * The init function of the resample filter.
+ *
+ * \param f Structure to initialize.
+ */
+void resample_filter_init(struct filter *f)
+{
+       struct resample_filter_args_info dummy;
+
+       resample_filter_cmdline_parser_init(&dummy);
+       f->close = resample_close;
+       f->open = resample_open;
+       f->pre_select = resample_pre_select;
+       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 df37d7ec5ce6efcae37cdf8cdb9de49c94ee5898..0be4a783cfb196a8b8b66f43c00a91abca578c54 100644 (file)
--- a/write.c
+++ b/write.c
 #include "fd.h"
 #include "error.h"
 #include "version.h"
+#include "check_wav.h"
 
 INIT_WRITE_ERRLISTS;
 
-enum check_wav_state {
-       CWS_NEED_HEADER,
-       CWS_HAVE_HEADER,
-       CWS_NO_HEADER,
-};
-
-/* Information extracted from the wav header. */
-struct check_wav_task {
-       int state;
-       /** Number of channels. */
-       unsigned channels;
-       unsigned sample_format;
-       /** Sample rate specified in wav header given by \a buf. */
-       unsigned sample_rate;
-       /** The task structure used by the scheduler. */
-       struct task task;
-       struct btr_node *btrn;
-       size_t min_iqs;
-};
-
 static struct write_args_info conf;
 
 static struct stdin_task sit;
 
-/** Length of a standard wav header. */
-#define WAV_HEADER_LEN 44
-
-static void check_wav_pre_select(struct sched *s, struct task *t)
-{
-       struct check_wav_task *cwt = container_of(t, struct check_wav_task, task);
-       int ret;
-
-       ret = btr_node_status(cwt->btrn, cwt->min_iqs, BTR_NT_INTERNAL);
-       if (ret != 0)
-               sched_min_delay(s);
-}
-
-#define HANDLE_EXEC(_cmd) \
-       if (!strcmp(cmd, #_cmd)) { \
-               if (!conf._cmd ## _given && cwt->state == CWS_NEED_HEADER) \
-                       return -E_BTR_NAVAIL; \
-               *result = make_message("%d", cwt->state == CWS_NO_HEADER || conf._cmd ## _given? \
-                       conf._cmd ## _arg : cwt->_cmd); \
-               return 1; \
-       } \
-
-
-static int check_wav_exec(struct btr_node *btrn, const char *cmd, char **result)
-{
-       struct check_wav_task *cwt = btr_context(btrn);
-
-       HANDLE_EXEC(sample_rate);
-       HANDLE_EXEC(channels);
-       HANDLE_EXEC(sample_format);
-       return -ERRNO_TO_PARA_ERROR(ENOTSUP);
-}
-
-static void check_wav_post_select(__a_unused struct sched *s, struct task *t)
-{
-       struct check_wav_task *cwt = container_of(t, struct check_wav_task, task);
-       struct btr_node *btrn = cwt->btrn;
-       unsigned char *a;
-       size_t sz;
-       int ret;
-       uint16_t bps; /* bits per sample */
-       const char *sample_formats[] = {SAMPLE_FORMATS};
-
-       t->error = 0;
-       ret = btr_node_status(btrn, cwt->min_iqs, BTR_NT_INTERNAL);
-       if (ret <= 0)
-               goto out;
-       if (cwt->state != CWS_NEED_HEADER)
-               goto pushdown;
-       btr_merge(btrn, cwt->min_iqs);
-       sz = btr_next_buffer(btrn, (char **)&a);
-       if (sz < cwt->min_iqs) /* file size less than WAV_HEADER_SIZE */
-               goto pushdown;
-       cwt->min_iqs = 0;
-       /*
-        * The default byte ordering assumed for WAVE data files is
-        * little-endian. Files written using the big-endian byte ordering
-        * scheme have the identifier RIFX instead of RIFF.
-        */
-       if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' ||
-                       (a[3] != 'F' && a[3] != 'X')) {
-               PARA_NOTICE_LOG("wav header not found\n");
-               cwt->state = CWS_NO_HEADER;
-               sprintf(t->status, "check wav: no header");
-               goto out;
-       }
-       PARA_INFO_LOG("found wav header\n");
-       cwt->state = CWS_HAVE_HEADER;
-       sprintf(t->status, "check wav: have header");
-       cwt->channels = (unsigned) a[22];
-       cwt->sample_rate = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24);
-       bps = a[34] + ((unsigned)a[35] << 8);
-       if (bps != 8 && bps != 16) {
-               PARA_WARNING_LOG("%u bps not supported, assuming 16\n", bps);
-               bps = 16;
-       }
-       /*
-        * 8-bit samples are stored as unsigned bytes, ranging from 0 to 255.
-        * 16-bit samples are stored as 2's-complement signed integers, ranging
-        * from -32768 to 32767.
-        */
-       if (bps == 8)
-               cwt->sample_format = SF_U8;
-       else
-               cwt->sample_format = (a[3] == 'F')? SF_S16_LE : SF_S16_BE;
-       PARA_NOTICE_LOG("%dHz, %s, %s\n", cwt->sample_rate,
-               cwt->channels == 1? "mono" : "stereo",
-               sample_formats[cwt->sample_format]);
-       btr_consume(btrn, WAV_HEADER_LEN);
-pushdown:
-       btr_pushdown(btrn);
-out:
-       t->error = ret;
-       if (ret < 0)
-               btr_remove_node(&cwt->btrn);
-}
-
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel)
 
@@ -191,12 +75,37 @@ static void setup_writer_node(const char *arg, struct btr_node *parent,
        register_writer_node(wn, parent, s);
 }
 
+struct write_task {
+       struct task task;
+       struct check_wav_context *cwc;
+};
+
+static void write_pre_select(struct sched *s, struct task *t)
+{
+       struct write_task *wt = container_of(t, struct write_task, task);
+       check_wav_pre_select(s, wt->cwc);
+}
+
+static void write_post_select(__a_unused struct sched *s, struct task *t)
+{
+       struct write_task *wt = container_of(t, struct write_task, task);
+       t->error = check_wav_post_select(wt->cwc);
+}
+
 static int setup_and_schedule(void)
 {
        int i, ret;
-       struct check_wav_task _cwt, *cwt = &_cwt;
+       struct btr_node *cw_btrn;
        struct writer_node *wns;
        static struct sched s;
+       struct wav_params wp;
+       struct write_task wt = {
+               .task = {
+                       .pre_select = write_pre_select,
+                       .post_select = write_post_select,
+                       .status = "write task",
+               },
+       };
 
        loglevel = get_loglevel_by_name(conf.loglevel_arg);
        sit.btrn = btr_new_node(&(struct btr_node_description)
@@ -204,25 +113,17 @@ static int setup_and_schedule(void)
        stdin_set_defaults(&sit);
        register_task(&s, &sit.task);
 
-       cwt->state = CWS_NEED_HEADER;
-       cwt->min_iqs = WAV_HEADER_LEN;
-       cwt->btrn = btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = "check_wav", .parent = sit.btrn,
-               .handler = check_wav_exec, .context = cwt));
-       sprintf(cwt->task.status, "check_wav");
-       cwt->task.pre_select = check_wav_pre_select;
-       cwt->task.post_select = check_wav_post_select;
-       cwt->task.error = 0;
-       register_task(&s, &cwt->task);
-
+       COPY_WAV_PARMS(&wp, &conf);
+       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));
-               setup_writer_node(NULL, cwt->btrn, wns, &s);
+               setup_writer_node(NULL, cw_btrn, wns, &s);
                i = 1;
        } else {
                wns = para_calloc(conf.writer_given * sizeof(*wns));
                for (i = 0; i < conf.writer_given; i++)
-                       setup_writer_node(conf.writer_arg[i], cwt->btrn,
+                       setup_writer_node(conf.writer_arg[i], cw_btrn,
                                wns + i, &s);
        }
 
@@ -253,7 +154,7 @@ static int setup_and_schedule(void)
                free(wn->conf);
        }
        free(wns);
-       btr_remove_node(&cwt->btrn);
+       check_wav_shutdown(wt.cwc);
        return ret;
 }