summaryrefslogtreecommitdiffstats
path: root/src/converter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/converter.c')
-rw-r--r--src/converter.c743
1 files changed, 743 insertions, 0 deletions
diff --git a/src/converter.c b/src/converter.c
new file mode 100644
index 0000000..3b146ce
--- /dev/null
+++ b/src/converter.c
@@ -0,0 +1,743 @@
+#include <math.h>
+#include <string.h>
+
+#include "speex/speex_resampler.h"
+
+#include "converter.h"
+#include "sydney.h"
+#include "macro.h"
+#include "common.h"
+#include "format.h"
+#include "volscale.h"
+#include "byteswap.h"
+#include "zero.h"
+#include "add.h"
+#include "bbuffer.h"
+#include "continued-fraction.h"
+#include "malloc.h"
+#include "resample.h"
+
+/* Steps: byteswap -> convert -> volscale -> remap -> resample -> convert -> byteswap -> interleave */
+
+/* Sample formats we know to process natively */
+static int native_pcm_format_process(sa_pcm_format_t f) {
+ return
+ f == SA_PCM_FORMAT_U8 ||
+ f == SA_PCM_FORMAT_S16_NE ||
+ f == SA_PCM_FORMAT_S32_NE ||
+ f == SA_PCM_FORMAT_FLOAT32_NE;
+}
+
+/* Sample formats we know to resample natively */
+static int native_pcm_format_resample(sa_pcm_format_t f) {
+ return
+ f == SA_PCM_FORMAT_S16_NE ||
+ f == SA_PCM_FORMAT_FLOAT32_NE;
+}
+
+static sa_pcm_format_t byteswap_fix(sa_pcm_format_t f) {
+
+ switch (f) {
+ case SA_PCM_FORMAT_U8:
+ case SA_PCM_FORMAT_ULAW:
+ case SA_PCM_FORMAT_ALAW:
+ return f;
+
+ case SA_PCM_FORMAT_S16_NE:
+ case SA_PCM_FORMAT_S16_RE:
+ return SA_PCM_FORMAT_S16_NE;
+
+ case SA_PCM_FORMAT_S24_NE:
+ case SA_PCM_FORMAT_S24_RE:
+ return SA_PCM_FORMAT_S24_NE;
+
+ case SA_PCM_FORMAT_S32_NE:
+ case SA_PCM_FORMAT_S32_RE:
+ return SA_PCM_FORMAT_S32_NE;
+
+ case SA_PCM_FORMAT_FLOAT32_NE:
+ case SA_PCM_FORMAT_FLOAT32_RE:
+ return SA_PCM_FORMAT_FLOAT32_NE;
+
+ case _SA_PCM_FORMAT_MAX:
+ ;
+ }
+
+ sa_assert_not_reached();
+}
+
+static sa_pcm_format_t get_work_format(sa_pcm_format_t a, sa_pcm_format_t b) {
+
+ switch (a) {
+ case SA_PCM_FORMAT_U8:
+ case SA_PCM_FORMAT_ULAW:
+ case SA_PCM_FORMAT_ALAW:
+
+ switch (b) {
+ case SA_PCM_FORMAT_U8:
+ return SA_PCM_FORMAT_U8;
+
+ case SA_PCM_FORMAT_ULAW:
+ case SA_PCM_FORMAT_ALAW:
+ case SA_PCM_FORMAT_S16_LE:
+ case SA_PCM_FORMAT_S16_BE:
+ return SA_PCM_FORMAT_S16_NE;
+
+ case SA_PCM_FORMAT_S24_LE:
+ case SA_PCM_FORMAT_S24_BE:
+ case SA_PCM_FORMAT_S32_LE:
+ case SA_PCM_FORMAT_S32_BE:
+ return SA_PCM_FORMAT_S32_NE;
+
+ case SA_PCM_FORMAT_FLOAT32_LE:
+ case SA_PCM_FORMAT_FLOAT32_BE:
+ return SA_PCM_FORMAT_FLOAT32_NE;
+
+ case _SA_PCM_FORMAT_MAX:
+ ;
+ }
+
+ break;
+
+ case SA_PCM_FORMAT_S16_LE:
+ case SA_PCM_FORMAT_S16_BE:
+
+ switch (b) {
+ case SA_PCM_FORMAT_U8:
+ case SA_PCM_FORMAT_ULAW:
+ case SA_PCM_FORMAT_ALAW:
+ case SA_PCM_FORMAT_S16_LE:
+ case SA_PCM_FORMAT_S16_BE:
+ return SA_PCM_FORMAT_S16_NE;
+
+ case SA_PCM_FORMAT_S24_LE:
+ case SA_PCM_FORMAT_S24_BE:
+ case SA_PCM_FORMAT_S32_LE:
+ case SA_PCM_FORMAT_S32_BE:
+ return SA_PCM_FORMAT_S32_NE;
+
+ case SA_PCM_FORMAT_FLOAT32_LE:
+ case SA_PCM_FORMAT_FLOAT32_BE:
+ return SA_PCM_FORMAT_FLOAT32_NE;
+
+ case _SA_PCM_FORMAT_MAX:
+ ;
+ }
+ break;
+
+ case SA_PCM_FORMAT_S24_LE:
+ case SA_PCM_FORMAT_S24_BE:
+ case SA_PCM_FORMAT_S32_LE:
+ case SA_PCM_FORMAT_S32_BE:
+
+ switch (b) {
+ case SA_PCM_FORMAT_U8:
+ case SA_PCM_FORMAT_ULAW:
+ case SA_PCM_FORMAT_ALAW:
+ case SA_PCM_FORMAT_S16_LE:
+ case SA_PCM_FORMAT_S16_BE:
+ case SA_PCM_FORMAT_S24_LE:
+ case SA_PCM_FORMAT_S24_BE:
+ case SA_PCM_FORMAT_S32_LE:
+ case SA_PCM_FORMAT_S32_BE:
+ return SA_PCM_FORMAT_S32_NE;
+
+ case SA_PCM_FORMAT_FLOAT32_LE:
+ case SA_PCM_FORMAT_FLOAT32_BE:
+ return SA_PCM_FORMAT_FLOAT32_NE;
+
+ case _SA_PCM_FORMAT_MAX:
+ ;
+ }
+ break;
+
+ case SA_PCM_FORMAT_FLOAT32_LE:
+ case SA_PCM_FORMAT_FLOAT32_BE:
+ return SA_PCM_FORMAT_FLOAT32_NE;
+
+ case _SA_PCM_FORMAT_MAX:
+ ;
+ }
+
+ sa_assert_not_reached();
+}
+
+static sa_pcm_format_t fix_work_format_for_resample(sa_pcm_format_t f) {
+
+ switch (f) {
+ case SA_PCM_FORMAT_U8:
+ case SA_PCM_FORMAT_S16_NE:
+ return SA_PCM_FORMAT_S16_NE;
+ break;
+
+ case SA_PCM_FORMAT_S32_NE:
+ case SA_PCM_FORMAT_FLOAT32_NE:
+ return SA_PCM_FORMAT_FLOAT32_NE;
+ break;
+
+ default:
+ ;
+ }
+
+ sa_assert_not_reached();
+}
+
+int sa_converter_init(
+ sa_converter_t *c,
+ const pcm_attrs_t *from,
+ const pcm_attrs_t *to,
+ int dynamic_rate_enabled) {
+
+ unsigned u, t;
+ int resample_required;
+
+ sa_assert(c);
+ sa_assert(from);
+ sa_assert(to);
+
+ memset(c, 0, sizeof(*c));
+
+ c->from_pcm_format = from->format;
+ c->to_pcm_format = to->format;
+
+ c->from_rate = from->rate;
+ c->to_rate = to->rate;
+
+ c->from_nchannels = from->nchannels;
+ c->to_nchannels = to->nchannels;
+
+ if (!(c->channel_map_table = sa_new(int, (from->nchannels+1) * to->nchannels)))
+ goto fail;
+
+ for (u = 0; u < to->nchannels; u++) {
+ unsigned k = 0;
+
+ for (t = 0; t < from->nchannels; t++) {
+
+ if (from->channel_map[u] == to->channel_map[t]) {
+ if (u != t)
+ c->remap_required = 1;
+
+ c->channel_map_table[u * (from->nchannels+1) + k++] += t;
+ }
+ }
+
+ if (k > 1)
+ c->sum_required = 1;
+
+ c->channel_map_table[k] = (unsigned) -1;
+ }
+
+ if (c->from_nchannels != c->to_nchannels)
+ c->remap_required = 1;
+
+ resample_required =
+ from->rate != to->rate ||
+ dynamic_rate_enabled;
+
+ c->work_pcm_format = get_work_format(from->format, to->format);
+ if (resample_required)
+ c->work_pcm_format = fix_work_format_for_resample(c->work_pcm_format);
+
+ sa_assert(native_pcm_format_process(c->work_pcm_format));
+ sa_assert(!resample_required || native_pcm_format_resample(c->work_pcm_format));
+
+ c->from_sample_size = sa_get_pcm_sample_size(c->from_pcm_format);
+ c->work_sample_size = sa_get_pcm_sample_size(c->work_pcm_format);
+ c->to_sample_size = sa_get_pcm_sample_size(c->to_pcm_format);
+
+ /* Get function pointers */
+ c->pre_byteswap_func = sa_get_byteswap_func(from->format);
+
+ if (byteswap_fix(from->format) != c->work_pcm_format) {
+ c->pre_format_func = sa_get_format_func(byteswap_fix(from->format), c->work_pcm_format);
+ sa_assert(c->pre_format_func);
+ }
+
+ c->volscale_func = sa_get_volscale_func(c->work_pcm_format);
+ sa_assert(c->volscale_func);
+
+ c->zero_func = sa_get_zero_func(c->work_pcm_format);
+ sa_assert(c->zero_func);
+
+ c->add_func = sa_get_add_func(c->work_pcm_format);
+ sa_assert(c->add_func);
+
+ if (resample_required) {
+ c->resample_func = sa_get_resample_func(c->work_pcm_format);
+ sa_assert(c->resample_func);
+ }
+
+ if (c->work_pcm_format != byteswap_fix(to->format)) {
+ c->post_format_func = sa_get_format_func(c->work_pcm_format, byteswap_fix(to->format));
+ sa_assert(c->post_format_func);
+ }
+
+ c->post_byteswap_func = sa_get_byteswap_func(to->format);
+
+ c->interleave_func = sa_get_interleave_func(to->format);
+ sa_assert(c->interleave_func);
+
+ /* Initialize resampler */
+
+ if (resample_required) {
+ if (!(c->speex = speex_resampler_init(c->to_nchannels, c->from_rate, c->to_rate, SPEEX_RESAMPLER_QUALITY_DEFAULT, NULL)))
+ goto fail;
+ }
+
+ /* Initialize processing variables */
+
+ if (!(c->from_process_data = sa_new0(void*, c->from_nchannels)))
+ goto fail;
+
+ if (!(c->from_stride = sa_new0(size_t, c->from_nchannels)))
+ goto fail;
+
+ if (!(c->to_process_data = sa_new0(void*, c->to_nchannels)))
+ goto fail;
+
+ if (!(c->to_stride = sa_new0(size_t, c->to_nchannels)))
+ goto fail;
+
+ /* Initialize volume stuff */
+ if (!(c->volume_factor = sa_new(int32_t, c->from_nchannels)))
+ goto fail;
+
+ if (!(c->volume_divisor = sa_new(int32_t, c->from_nchannels)))
+ goto fail;
+
+ c->no_volume = 1;
+
+ /* Initialize bounce buffers */
+
+ if (sa_bbuffer_init(&c->bb_pre_byteswap, c->from_nchannels, c->from_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_pre_format, c->from_nchannels, c->work_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_volscale, c->from_nchannels, c->work_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_remap, c->to_nchannels, c->work_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_resample, c->to_nchannels, c->work_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_post_format, c->to_nchannels, c->to_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_post_byteswap, c->to_nchannels, c->to_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_interleave, c->to_nchannels, c->to_sample_size) < 0)
+ goto fail;
+ if (sa_bbuffer_init(&c->bb_tmp, 1, 1) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ sa_converter_done(c);
+
+ return SA_ERROR_OOM;
+}
+
+void sa_converter_done(sa_converter_t *c) {
+ sa_assert(c);
+
+ sa_free(c->channel_map_table);
+
+ sa_free(c->from_process_data);
+ sa_free(c->to_process_data);
+ sa_free(c->from_stride);
+ sa_free(c->to_stride);
+
+ sa_bbuffer_done(&c->bb_pre_byteswap);
+ sa_bbuffer_done(&c->bb_pre_format);
+ sa_bbuffer_done(&c->bb_volscale);
+ sa_bbuffer_done(&c->bb_remap);
+ sa_bbuffer_done(&c->bb_resample);
+ sa_bbuffer_done(&c->bb_post_format);
+ sa_bbuffer_done(&c->bb_post_byteswap);
+ sa_bbuffer_done(&c->bb_interleave);
+ sa_bbuffer_done(&c->bb_tmp);
+
+ if (c->speex)
+ speex_resampler_destroy(c->speex);
+
+ sa_free(c->zero_buffer);
+
+ sa_free(c->volume_divisor);
+ sa_free(c->volume_factor);
+ memset(c, 0, sizeof(*c));
+}
+
+void* sa_converter_get_zero_buffer(sa_converter_t *c, size_t size) {
+ void *b;
+
+ sa_assert(c);
+ sa_assert(size > 0);
+
+ if (c->zero_buffer && c->zero_size >= size)
+ return c->zero_buffer;
+
+ sa_free(c->zero_buffer);
+
+ if (!(c->zero_buffer = sa_malloc(size)))
+ return NULL;
+
+ c->zero_func(b, c->work_sample_size, size);
+ return c->zero_func;
+}
+
+int sa_converter_go(
+ sa_converter_t *c,
+ const void *const src[], const size_t sstr[], int sinterleave,
+ void **dst[], size_t *dstr[], int dinterleave,
+ size_t *size) {
+
+ size_t* stride;
+ void** process_data;
+ int is_bounce;
+ int interleave;
+ unsigned i;
+
+ sa_assert(c);
+
+ is_bounce = 0;
+ stride = (size_t*) sstr;
+ process_data = (void**) src;
+ interleave = !!sinterleave;
+ dinterleave = !!dinterleave;
+
+ if (c->no_volume &&
+ !c->remap_required &&
+ !c->resample_func &&
+ c->from_pcm_format == c->to_pcm_format) {
+
+ /* We can shortcut this, since we don't need to do any real work.*/
+
+ goto do_interleave;
+ }
+
+ if (c->pre_byteswap_func) {
+ size_t k;
+
+ k = dinterleave ? c->from_sample_size*c->from_nchannels : c->from_sample_size;
+
+ for (i = 0; i < c->from_nchannels; i++) {
+ void *b;
+
+ if (!(b = sa_bbuffer_get(&c->bb_pre_byteswap, i, *size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->pre_byteswap_func(b, k, process_data[i], stride[i], *size);
+ c->from_process_data[i] = b;
+ c->from_stride[i] = k;
+ }
+
+ process_data = c->from_process_data;
+ stride = c->from_stride;
+ interleave = dinterleave;
+ is_bounce = 1;
+ }
+
+ if (c->pre_format_func) {
+
+ if (is_bounce && c->from_sample_size == c->to_sample_size) {
+
+ /* The data to process is already in a bounce buffer, we
+ * can do this in-place */
+
+ for (i = 0; i < c->from_nchannels; i++)
+ c->pre_format_func(&c->bb_tmp, process_data[i], stride[i], process_data[i], stride[i], *size);
+
+ } else {
+ size_t k, new_size;
+
+ new_size = *size / c->from_sample_size * c->work_sample_size;
+ k = dinterleave ? c->work_sample_size*c->from_nchannels : c->work_sample_size;
+
+ for (i = 0; i < c->from_nchannels; i++) {
+ void *b;
+
+ if (!(b = sa_bbuffer_get(&c->bb_pre_format, i, new_size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->pre_format_func(&c->bb_tmp, b, k, process_data[i], stride[i], *size);
+ c->from_process_data[i] = b;
+ c->from_stride[i] = k;
+ }
+
+ process_data = c->from_process_data;
+ stride = c->from_stride;
+ *size = new_size;
+ interleave = dinterleave;
+ is_bounce = 1;
+ }
+ }
+
+ if (!c->no_volume) {
+
+ sa_assert(c->volscale_func);
+
+ if (is_bounce) {
+
+ /* The data to process is already in a bounce buffer, we
+ * can do this in-place */
+
+ for (i = 0; i < c->from_nchannels; i++);
+ c->volscale_func(process_data[i], stride[i], process_data[i], stride[i], c->volume_factor[i], c->volume_divisor[i], *size);
+
+ } else {
+ size_t k;
+
+ k = dinterleave ? c->work_sample_size*c->from_nchannels : c->work_sample_size;
+
+ for (i = 0; i < c->from_nchannels; i++) {
+ void *b;
+
+ if (!(b = sa_bbuffer_get(&c->bb_volscale, i, *size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->volscale_func(b, k, process_data[i], stride[i], c->volume_factor[i], c->volume_divisor[i], *size);
+ c->from_process_data[i] = b;
+ c->from_stride[i] = k;
+ }
+
+ process_data = c->from_process_data;
+ stride = c->from_stride;
+ interleave = dinterleave;
+ is_bounce = 1;
+ }
+ }
+
+ if (c->remap_required) {
+ size_t k;
+ int need_proper_interleave = 0;
+
+ k = dinterleave ? c->work_sample_size*c->to_nchannels : c->work_sample_size;
+
+ for (i = 0; i < c->to_nchannels; i++) {
+ void *b;
+ int *p = &c->channel_map_table[i * (c->from_nchannels+1)];
+
+ if (p[0] == -1) {
+ /* We have to write silence to this channel */
+
+ if (!(b = sa_converter_get_zero_buffer(c, *size)))
+ return SA_ERROR_OOM;
+
+ c->to_process_data[i] = b;
+ c->to_stride[i] = c->work_sample_size;
+
+ need_proper_interleave = 1;
+ } else if (p[1] == -1) {
+ /* Just one channel, nothing to mix */
+
+ c->to_process_data[i] = process_data[p[0]];
+ c->to_stride[i] = stride[p[0]];
+
+ need_proper_interleave = 1;
+ } else {
+ int j;
+
+ /* We have to mix two or more channels */
+
+ if (!(b = sa_bbuffer_get(&c->bb_remap, i, *size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->add_func(b, k, process_data[p[0]], stride[p[0]], process_data[p[1]], stride[p[1]], *size);
+
+ for (j = 2; p[j] != -1; j++)
+ c->add_func(b, k, b, k, process_data[p[j]], stride[p[j]], *size);
+
+ c->to_process_data[i] = b;
+ c->to_stride[i] = k;
+ }
+ }
+
+ process_data = c->to_process_data;
+ stride = c->to_stride;
+ interleave = need_proper_interleave ? -1 : dinterleave;
+ is_bounce = 1;
+ }
+
+ if (c->resample_func) {
+ size_t k;
+ size_t new_size;
+
+ k = dinterleave ? c->work_sample_size*c->to_nchannels : c->work_sample_size;
+
+ new_size = (size_t) (((((uint64_t) *size+c->work_sample_size-1)/c->work_sample_size)*c->to_rate)/c->from_rate+1)*c->work_sample_size; /* FIXME */
+
+ for (i = 0; i < c->to_nchannels; i++) {
+ void *b;
+
+ if (!(b = sa_bbuffer_get(&c->bb_resample, i, new_size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->resample_func(c->speex, i, b, k, process_data[i], stride[i], *size, &new_size);
+ c->to_process_data[i] = b;
+ c->to_stride[i] = k;
+ }
+
+ process_data = c->to_process_data;
+ stride = c->to_stride;
+ *size = new_size;
+ interleave = dinterleave;
+ is_bounce = 1;
+ }
+
+ if (c->post_format_func) {
+
+ if (is_bounce && c->work_sample_size == c->to_sample_size) {
+
+ /* The data to process is already in a bounce buffer, we
+ * can do this in-place */
+
+ for (i = 0; i < c->to_nchannels; i++)
+ c->post_format_func(&c->bb_tmp, process_data[i], stride[i], process_data[i], stride[i], *size);
+
+ } else {
+ size_t k, new_size;
+
+ k = dinterleave ? c->to_sample_size*c->to_nchannels : c->to_sample_size;
+ new_size = *size / c->work_sample_size * c->to_sample_size;
+
+ for (i = 0; i < c->to_nchannels; i++) {
+ void *b;
+
+ if (!(b = sa_bbuffer_get(&c->bb_post_format, i, new_size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->post_format_func(&c->bb_tmp, b, k, process_data[i], stride[i], *size);
+ c->to_process_data[i] = b;
+ c->to_stride[i] = k;
+ }
+
+ process_data = c->to_process_data;
+ stride = c->to_stride;
+ *size = new_size;
+ interleave = dinterleave;
+ is_bounce = 1;
+ }
+ }
+
+ if (c->post_byteswap_func) {
+
+ if (is_bounce) {
+
+ /* The data to process is already in a bounce buffer, we
+ * can do this in-place */
+
+ for (i = 0; i < c->to_nchannels; i++)
+ c->post_byteswap_func(process_data[i], stride[i], process_data[i], stride[i], *size);
+
+ } else {
+ size_t k;
+
+ k = dinterleave ? c->to_sample_size*c->to_nchannels : c->to_sample_size;
+
+ for (i = 0; i < c->to_nchannels; i++) {
+ void *b;
+
+ if (!(b = sa_bbuffer_get(&c->bb_post_byteswap, i, *size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->post_byteswap_func(b, k, process_data[i], stride[i], *size);
+
+ c->to_process_data[i] = b;
+ c->to_stride[i] = k;
+ }
+
+ process_data = c->to_process_data;
+ stride = c->to_stride;
+ interleave = dinterleave;
+ is_bounce = 1;
+ }
+ }
+
+do_interleave:
+
+ if (interleave != dinterleave) {
+ size_t k;
+
+ k = dinterleave ? c->to_sample_size*c->to_nchannels : c->to_sample_size;
+
+ for (i = 0; i < c->to_nchannels; i++) {
+ void *b;
+
+ if (!(b = sa_bbuffer_get(&c->bb_interleave, i, *size, dinterleave)))
+ return SA_ERROR_OOM;
+
+ c->interleave_func(b, k, process_data[i], stride[i], *size);
+
+ c->to_process_data[i] = b;
+ c->to_stride[i] = k;
+ }
+
+ process_data = c->to_process_data;
+ stride = c->to_stride;
+ interleave = dinterleave;
+ is_bounce = 1;
+ }
+
+ *dstr = stride;
+ *dst = process_data;
+
+ return SA_SUCCESS;
+}
+
+void sa_converter_set_volume(sa_converter_t *c, const int32_t vol[]) {
+ unsigned i;
+ int no_volume = 1;
+
+ sa_assert(c);
+
+ for (i = 0; i < c->from_nchannels; i++) {
+ int num, denom;
+
+ if (vol[i] == 0) {
+ c->volume_factor[i] = 1;
+ c->volume_divisor[i] = 1;
+ } else if (vol[i] <= SA_VOLUME_MUTED) {
+ c->volume_factor[i] = 0;
+ c->volume_divisor[i] = 1;
+ no_volume = 0;
+ } else {
+ float f = powf(10.0, (float) vol[i] / 2000);
+
+ sa_continued_fraction(f, 0x7FFF, &num, &denom);
+
+ if (num != 1 || denom != 1)
+ no_volume = 0;
+
+ c->volume_factor[i] = (int32_t) num;
+ c->volume_divisor[i] = (int32_t) denom;
+ }
+ }
+
+ c->no_volume = no_volume;
+}
+
+int sa_converter_go_interleaved(
+ sa_converter_t *c,
+ const void *const data,
+ void **dst[], size_t *dstr[], int dinterleave,
+ size_t *size) {
+
+ unsigned i;
+ const uint8_t *d = data;
+ unsigned stride = c->from_nchannels * c->from_sample_size;
+
+ for (i = 0; i < c->from_nchannels; i++) {
+ c->from_process_data[i] = (void*) data;
+ d += c->from_sample_size;
+ c->from_stride[i] = stride;
+ }
+
+ return sa_converter_go(c, (const void *const*) c->from_process_data, c->from_stride, 1, dst, dstr, dinterleave, size);
+}
+
+void sa_converter_set_ratio(sa_converter_t *c, unsigned rate1, unsigned rate2) {
+ assert(c);
+ assert(c->speex);
+
+ speex_resampler_set_rate(c->speex, rate1, rate2);
+}