#include <pulsecore/macro.h>
#include <pulsecore/strbuf.h>
#include <pulsecore/remap.h>
+#include <pulsecore/once.h>
#include <pulsecore/core-util.h>
#include "ffmpeg/avcodec.h"
pa_sample_spec i_ss, o_ss;
pa_channel_map i_cm, o_cm;
- size_t i_fz, o_fz, w_sz;
+ size_t i_fz, o_fz, w_fz, w_sz;
pa_mempool *mempool;
pa_memchunk to_work_format_buf;
pa_memchunk remap_buf;
pa_memchunk resample_buf;
pa_memchunk from_work_format_buf;
- unsigned to_work_format_buf_samples;
+ size_t to_work_format_buf_size;
size_t remap_buf_size;
- unsigned resample_buf_samples;
- unsigned from_work_format_buf_samples;
- bool remap_buf_contains_leftover_data;
+ size_t resample_buf_size;
+ size_t from_work_format_buf_size;
+
+ /* points to buffer before resampling stage, remap or to_work */
+ pa_memchunk *leftover_buf;
+ size_t *leftover_buf_size;
+
+ /* have_leftover points to leftover_in_remap or leftover_in_to_work */
+ bool *have_leftover;
+ bool leftover_in_remap;
+ bool leftover_in_to_work;
pa_sample_format_t work_format;
+ uint8_t work_channels;
pa_convert_func_t to_work_format_func;
pa_convert_func_t from_work_format_func;
pa_remap_t remap;
bool map_required;
- void (*impl_free)(pa_resampler *r);
- void (*impl_update_rates)(pa_resampler *r);
- void (*impl_resample)(pa_resampler *r, const pa_memchunk *in, unsigned in_samples, pa_memchunk *out, unsigned *out_samples);
- void (*impl_reset)(pa_resampler *r);
- void *impl_data;
+ pa_resampler_impl impl;
};
struct trivial_data { /* data specific to the trivial resampler */
int16_t max_i[PA_CHANNELS_MAX];
};
-#ifdef HAVE_LIBSAMPLERATE
-struct src_data { /* data specific to libsamplerate */
- SRC_STATE *state;
-};
-#endif
-
-#ifdef HAVE_SPEEX
-struct speex_data { /* data specific to speex */
- SpeexResamplerState* state;
-};
-#endif
-
struct ffmpeg_data { /* data specific to ffmpeg */
struct AVResampleContext *state;
- pa_memchunk buf[PA_CHANNELS_MAX];
};
static int copy_init(pa_resampler *r);
static int libsamplerate_init(pa_resampler*r);
#endif
-static void calc_map_table(pa_resampler *r);
+static void setup_remap(const pa_resampler *r, pa_remap_t *m);
+static void free_remap(pa_remap_t *m);
static int (* const init_table[])(pa_resampler*r) = {
#ifdef HAVE_LIBSAMPLERATE
[PA_RESAMPLER_PEAKS] = peaks_init,
};
-static pa_resample_method_t pa_resampler_fix_method(
+static bool speex_is_fixed_point(void);
+
+static pa_resample_method_t choose_auto_resampler(pa_resample_flags_t flags) {
+ pa_resample_method_t method;
+
+ if (pa_resample_method_supported(PA_RESAMPLER_SPEEX_FLOAT_BASE + 1))
+ method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
+ else if (flags & PA_RESAMPLER_VARIABLE_RATE)
+ method = PA_RESAMPLER_TRIVIAL;
+ else
+ method = PA_RESAMPLER_FFMPEG;
+
+ return method;
+}
+
+static pa_resample_method_t fix_method(
pa_resample_flags_t flags,
pa_resample_method_t method,
const uint32_t rate_a,
const uint32_t rate_b) {
- pa_assert(rate_a > 0 && rate_a <= PA_RATE_MAX);
- pa_assert(rate_b > 0 && rate_b <= PA_RATE_MAX);
+ pa_assert(pa_sample_rate_valid(rate_a));
+ pa_assert(pa_sample_rate_valid(rate_b));
pa_assert(method >= 0);
pa_assert(method < PA_RESAMPLER_MAX);
case PA_RESAMPLER_COPY:
if (rate_a != rate_b) {
pa_log_info("Resampler 'copy' cannot change sampling rate, reverting to resampler 'auto'.");
+ method = PA_RESAMPLER_AUTO;
break;
}
/* Else fall through */
break;
}
- if (method == PA_RESAMPLER_AUTO) {
-#ifdef HAVE_SPEEX
- method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
-#else
- if (flags & PA_RESAMPLER_VARIABLE_RATE)
- method = PA_RESAMPLER_TRIVIAL;
- else
- method = PA_RESAMPLER_FFMPEG;
-#endif
+ if (method == PA_RESAMPLER_AUTO)
+ method = choose_auto_resampler(flags);
+
+ /* At this point, method is supported in the sense that it
+ * has an init function and supports the required flags. However,
+ * speex-float implementation in PulseAudio relies on the
+ * assumption that is invalid if speex has been compiled with
+ * --enable-fixed-point. Besides, speex-fixed is more efficient
+ * in this configuration. So use it instead.
+ */
+ if (method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && method <= PA_RESAMPLER_SPEEX_FLOAT_MAX) {
+ if (speex_is_fixed_point()) {
+ pa_log_info("Speex appears to be compiled with --enable-fixed-point. "
+ "Switching to a fixed-point resampler because it should be faster.");
+ method = method - PA_RESAMPLER_SPEEX_FLOAT_BASE + PA_RESAMPLER_SPEEX_FIXED_BASE;
+ }
}
-
return method;
}
/* Return true if a is a more precise sample format than b, else return false */
static bool sample_format_more_precise(pa_sample_format_t a, pa_sample_format_t b) {
- pa_assert(a >= 0 && a < PA_SAMPLE_MAX);
- pa_assert(b >= 0 && b < PA_SAMPLE_MAX);
+ pa_assert(pa_sample_format_valid(a));
+ pa_assert(pa_sample_format_valid(b));
switch (a) {
case PA_SAMPLE_U8:
}
}
-static pa_sample_format_t pa_resampler_choose_work_format(
+static pa_sample_format_t choose_work_format(
pa_resample_method_t method,
pa_sample_format_t a,
pa_sample_format_t b,
bool map_required) {
pa_sample_format_t work_format;
- pa_assert(a >= 0 && a < PA_SAMPLE_MAX);
- pa_assert(b >= 0 && b < PA_SAMPLE_MAX);
+ pa_assert(pa_sample_format_valid(a));
+ pa_assert(pa_sample_format_valid(b));
pa_assert(method >= 0);
pa_assert(method < PA_RESAMPLER_MAX);
pa_assert(method >= 0);
pa_assert(method < PA_RESAMPLER_MAX);
- method = pa_resampler_fix_method(flags, method, a->rate, b->rate);
+ method = fix_method(flags, method, a->rate, b->rate);
r = pa_xnew0(pa_resampler, 1);
r->mempool = pool;
r->i_ss = *a;
r->o_ss = *b;
- /* set up the remap structure */
- r->remap.i_ss = &r->i_ss;
- r->remap.o_ss = &r->o_ss;
- r->remap.format = &r->work_format;
-
if (am)
r->i_cm = *am;
else if (!pa_channel_map_init_auto(&r->i_cm, r->i_ss.channels, PA_CHANNEL_MAP_DEFAULT))
r->i_fz = pa_frame_size(a);
r->o_fz = pa_frame_size(b);
- calc_map_table(r);
-
- pa_log_info("Using resampler '%s'", pa_resample_method_to_string(method));
-
- r->work_format = pa_resampler_choose_work_format(method, a->format, b->format, r->map_required);
-
- pa_log_info("Using %s as working format.", pa_sample_format_to_string(r->work_format));
+ r->map_required = (r->i_ss.channels != r->o_ss.channels || (!(r->flags & PA_RESAMPLER_NO_REMAP) &&
+ !pa_channel_map_equal(&r->i_cm, &r->o_cm)));
+ r->work_format = choose_work_format(method, a->format, b->format, r->map_required);
r->w_sz = pa_sample_size_of_format(r->work_format);
if (r->i_ss.format != r->work_format) {
}
}
+ if (r->o_ss.channels <= r->i_ss.channels) {
+ /* pipeline is: format conv. -> remap -> resample -> format conv. */
+ r->work_channels = r->o_ss.channels;
+
+ /* leftover buffer is remap output buffer (before resampling) */
+ r->leftover_buf = &r->remap_buf;
+ r->leftover_buf_size = &r->remap_buf_size;
+ r->have_leftover = &r->leftover_in_remap;
+ } else {
+ /* pipeline is: format conv. -> resample -> remap -> format conv. */
+ r->work_channels = r->i_ss.channels;
+
+ /* leftover buffer is to_work output buffer (before resampling) */
+ r->leftover_buf = &r->to_work_format_buf;
+ r->leftover_buf_size = &r->to_work_format_buf_size;
+ r->have_leftover = &r->leftover_in_to_work;
+ }
+ r->w_fz = pa_sample_size_of_format(r->work_format) * r->work_channels;
+
+ pa_log_debug("Resampler:");
+ pa_log_debug(" rate %d -> %d (method %s)", a->rate, b->rate, pa_resample_method_to_string(r->method));
+ pa_log_debug(" format %s -> %s (intermediate %s)", pa_sample_format_to_string(a->format),
+ pa_sample_format_to_string(b->format), pa_sample_format_to_string(r->work_format));
+ pa_log_debug(" channels %d -> %d (resampling %d)", a->channels, b->channels, r->work_channels);
+
+ /* set up the remap structure */
+ if (r->map_required)
+ setup_remap(r, &r->remap);
+
/* initialize implementation */
if (init_table[method](r) < 0)
goto fail;
void pa_resampler_free(pa_resampler *r) {
pa_assert(r);
- if (r->impl_free)
- r->impl_free(r);
+ if (r->impl.free)
+ r->impl.free(r);
+ else
+ pa_xfree(r->impl.data);
if (r->to_work_format_buf.memblock)
pa_memblock_unref(r->to_work_format_buf.memblock);
if (r->from_work_format_buf.memblock)
pa_memblock_unref(r->from_work_format_buf.memblock);
- pa_xfree(r->impl_data);
+ free_remap(&r->remap);
+
pa_xfree(r);
}
void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate) {
pa_assert(r);
pa_assert(rate > 0);
+ pa_assert(r->impl.update_rates);
if (r->i_ss.rate == rate)
return;
r->i_ss.rate = rate;
- r->impl_update_rates(r);
+ r->impl.update_rates(r);
}
void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) {
pa_assert(r);
pa_assert(rate > 0);
+ pa_assert(r->impl.update_rates);
if (r->o_ss.rate == rate)
return;
r->o_ss.rate = rate;
- r->impl_update_rates(r);
+ r->impl.update_rates(r);
}
size_t pa_resampler_request(pa_resampler *r, size_t out_length) {
* enough output buffer. */
frames = (in_length + r->i_fz - 1) / r->i_fz;
-
- if (r->remap_buf_contains_leftover_data)
- frames += r->remap_buf.length / (r->w_sz * r->o_ss.channels);
+ if (*r->have_leftover)
+ frames += r->leftover_buf->length / r->w_fz;
return (((uint64_t) frames * r->o_ss.rate + r->i_ss.rate - 1) / r->i_ss.rate) * r->o_fz;
}
max_fs = pa_frame_size(&max_ss);
frames = block_size_max / max_fs - EXTRA_FRAMES;
- if (r->remap_buf_contains_leftover_data)
- frames -= r->remap_buf.length / (r->w_sz * r->o_ss.channels);
+ pa_assert(frames >= (r->leftover_buf->length / r->w_fz));
+ if (*r->have_leftover)
+ frames -= r->leftover_buf->length / r->w_fz;
+
+ block_size_max = ((uint64_t) frames * r->i_ss.rate / max_ss.rate) * r->i_fz;
- return ((uint64_t) frames * r->i_ss.rate / max_ss.rate) * r->i_fz;
+ if (block_size_max > 0)
+ return block_size_max;
+ else
+ /* A single input frame may result in so much output that it doesn't
+ * fit in one standard memblock (e.g. converting 1 Hz to 44100 Hz). In
+ * this case the max block size will be set to one frame, and some
+ * memory will be probably be allocated with malloc() instead of using
+ * the memory pool.
+ *
+ * XXX: Should we support this case at all? We could also refuse to
+ * create resamplers whose max block size would exceed the memory pool
+ * block size. In this case also updating the resampler rate should
+ * fail if the new rate would cause an excessive max block size (in
+ * which case the stream would probably have to be killed). */
+ return r->i_fz;
}
void pa_resampler_reset(pa_resampler *r) {
pa_assert(r);
- if (r->impl_reset)
- r->impl_reset(r);
+ if (r->impl.reset)
+ r->impl.reset(r);
- r->remap_buf_contains_leftover_data = false;
+ *r->have_leftover = false;
}
pa_resample_method_t pa_resampler_get_method(pa_resampler *r) {
return ON_OTHER;
}
-static void calc_map_table(pa_resampler *r) {
+static void setup_remap(const pa_resampler *r, pa_remap_t *m) {
unsigned oc, ic;
unsigned n_oc, n_ic;
bool ic_connected[PA_CHANNELS_MAX];
bool remix;
pa_strbuf *s;
char *t;
- pa_remap_t *m;
pa_assert(r);
-
- if (!(r->map_required = (r->i_ss.channels != r->o_ss.channels || (!(r->flags & PA_RESAMPLER_NO_REMAP) && !pa_channel_map_equal(&r->i_cm, &r->o_cm)))))
- return;
-
- m = &r->remap;
+ pa_assert(m);
n_oc = r->o_ss.channels;
n_ic = r->i_ss.channels;
+ m->format = r->work_format;
+ m->i_ss = r->i_ss;
+ m->o_ss = r->o_ss;
+
memset(m->map_table_f, 0, sizeof(m->map_table_f));
memset(m->map_table_i, 0, sizeof(m->map_table_i));
pa_xfree(t);
/* initialize the remapping function */
- pa_init_remap(m);
+ pa_init_remap_func(m);
+}
+
+static void free_remap(pa_remap_t *m) {
+ pa_assert(m);
+
+ pa_xfree(m->state);
+}
+
+/* check if buf's memblock is large enough to hold 'len' bytes; create a
+ * new memblock if necessary and optionally preserve 'copy' data bytes */
+static void fit_buf(pa_resampler *r, pa_memchunk *buf, size_t len, size_t *size, size_t copy) {
+ pa_assert(size);
+
+ if (!buf->memblock || len > *size) {
+ pa_memblock *new_block = pa_memblock_new(r->mempool, len);
+
+ if (buf->memblock) {
+ if (copy > 0) {
+ void *src = pa_memblock_acquire(buf->memblock);
+ void *dst = pa_memblock_acquire(new_block);
+ pa_assert(copy <= len);
+ memcpy(dst, src, copy);
+ pa_memblock_release(new_block);
+ pa_memblock_release(buf->memblock);
+ }
+
+ pa_memblock_unref(buf->memblock);
+ }
+
+ buf->memblock = new_block;
+ *size = len;
+ }
+
+ buf->length = len;
}
static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) {
- unsigned n_samples;
+ unsigned in_n_samples, out_n_samples;
void *src, *dst;
+ bool have_leftover;
+ size_t leftover_length = 0;
pa_assert(r);
pa_assert(input);
pa_assert(input->memblock);
/* Convert the incoming sample into the work sample format and place them
- * in to_work_format_buf. */
-
- if (!r->to_work_format_func || !input->length)
- return input;
+ * in to_work_format_buf. The leftover data is already converted, so it's
+ * part of the output buffer. */
- n_samples = (unsigned) ((input->length / r->i_fz) * r->i_ss.channels);
+ have_leftover = r->leftover_in_to_work;
+ r->leftover_in_to_work = false;
- r->to_work_format_buf.index = 0;
- r->to_work_format_buf.length = r->w_sz * n_samples;
+ if (!have_leftover && (!r->to_work_format_func || !input->length))
+ return input;
+ else if (input->length <= 0)
+ return &r->to_work_format_buf;
- if (!r->to_work_format_buf.memblock || r->to_work_format_buf_samples < n_samples) {
- if (r->to_work_format_buf.memblock)
- pa_memblock_unref(r->to_work_format_buf.memblock);
+ in_n_samples = out_n_samples = (unsigned) ((input->length / r->i_fz) * r->i_ss.channels);
- r->to_work_format_buf_samples = n_samples;
- r->to_work_format_buf.memblock = pa_memblock_new(r->mempool, r->to_work_format_buf.length);
+ if (have_leftover) {
+ leftover_length = r->to_work_format_buf.length;
+ out_n_samples += (unsigned) (leftover_length / r->w_sz);
}
+ fit_buf(r, &r->to_work_format_buf, r->w_sz * out_n_samples, &r->to_work_format_buf_size, leftover_length);
+
src = pa_memblock_acquire_chunk(input);
- dst = pa_memblock_acquire(r->to_work_format_buf.memblock);
+ dst = (uint8_t *) pa_memblock_acquire(r->to_work_format_buf.memblock) + leftover_length;
- r->to_work_format_func(n_samples, src, dst);
+ if (r->to_work_format_func)
+ r->to_work_format_func(in_n_samples, src, dst);
+ else
+ memcpy(dst, src, input->length);
pa_memblock_release(input->memblock);
pa_memblock_release(r->to_work_format_buf.memblock);
* data in the beginning of remap_buf. The leftover data is already
* remapped, so it's not part of the input, it's part of the output. */
- have_leftover = r->remap_buf_contains_leftover_data;
- r->remap_buf_contains_leftover_data = false;
+ have_leftover = r->leftover_in_remap;
+ r->leftover_in_remap = false;
if (!have_leftover && (!r->map_required || input->length <= 0))
return input;
if (have_leftover) {
leftover_length = r->remap_buf.length;
- out_n_frames += leftover_length / (r->w_sz * r->o_ss.channels);
+ out_n_frames += leftover_length / r->w_fz;
}
out_n_samples = out_n_frames * r->o_ss.channels;
- r->remap_buf.length = out_n_samples * r->w_sz;
-
- if (have_leftover) {
- if (r->remap_buf_size < r->remap_buf.length) {
- pa_memblock *new_block = pa_memblock_new(r->mempool, r->remap_buf.length);
-
- src = pa_memblock_acquire(r->remap_buf.memblock);
- dst = pa_memblock_acquire(new_block);
- memcpy(dst, src, leftover_length);
- pa_memblock_release(r->remap_buf.memblock);
- pa_memblock_release(new_block);
-
- pa_memblock_unref(r->remap_buf.memblock);
- r->remap_buf.memblock = new_block;
- r->remap_buf_size = r->remap_buf.length;
- }
-
- } else {
- if (!r->remap_buf.memblock || r->remap_buf_size < r->remap_buf.length) {
- if (r->remap_buf.memblock)
- pa_memblock_unref(r->remap_buf.memblock);
-
- r->remap_buf_size = r->remap_buf.length;
- r->remap_buf.memblock = pa_memblock_new(r->mempool, r->remap_buf.length);
- }
- }
+ fit_buf(r, &r->remap_buf, out_n_samples * r->w_sz, &r->remap_buf_size, leftover_length);
src = pa_memblock_acquire_chunk(input);
dst = (uint8_t *) pa_memblock_acquire(r->remap_buf.memblock) + leftover_length;
return &r->remap_buf;
}
+static void save_leftover(pa_resampler *r, void *buf, size_t len) {
+ void *dst;
+
+ pa_assert(r);
+ pa_assert(buf);
+ pa_assert(len > 0);
+
+ /* Store the leftover data. */
+ fit_buf(r, r->leftover_buf, len, r->leftover_buf_size, 0);
+ *r->have_leftover = true;
+
+ dst = pa_memblock_acquire(r->leftover_buf->memblock);
+ memmove(dst, buf, len);
+ pa_memblock_release(r->leftover_buf->memblock);
+}
+
static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) {
- unsigned in_n_frames, in_n_samples;
- unsigned out_n_frames, out_n_samples;
+ unsigned in_n_frames, out_n_frames, leftover_n_frames;
pa_assert(r);
pa_assert(input);
/* Resample the data and place the result in resample_buf. */
- if (!r->impl_resample || !input->length)
+ if (!r->impl.resample || !input->length)
return input;
- in_n_samples = (unsigned) (input->length / r->w_sz);
- in_n_frames = (unsigned) (in_n_samples / r->o_ss.channels);
+ in_n_frames = (unsigned) (input->length / r->w_fz);
out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_FRAMES;
- out_n_samples = out_n_frames * r->o_ss.channels;
-
- r->resample_buf.index = 0;
- r->resample_buf.length = r->w_sz * out_n_samples;
+ fit_buf(r, &r->resample_buf, r->w_fz * out_n_frames, &r->resample_buf_size, 0);
- if (!r->resample_buf.memblock || r->resample_buf_samples < out_n_samples) {
- if (r->resample_buf.memblock)
- pa_memblock_unref(r->resample_buf.memblock);
+ leftover_n_frames = r->impl.resample(r, input, in_n_frames, &r->resample_buf, &out_n_frames);
- r->resample_buf_samples = out_n_samples;
- r->resample_buf.memblock = pa_memblock_new(r->mempool, r->resample_buf.length);
+ if (leftover_n_frames > 0) {
+ void *leftover_data = (uint8_t *) pa_memblock_acquire_chunk(input) + (in_n_frames - leftover_n_frames) * r->w_fz;
+ save_leftover(r, leftover_data, leftover_n_frames * r->w_fz);
+ pa_memblock_release(input->memblock);
}
- r->impl_resample(r, input, in_n_frames, &r->resample_buf, &out_n_frames);
- r->resample_buf.length = out_n_frames * r->w_sz * r->o_ss.channels;
+ r->resample_buf.length = out_n_frames * r->w_fz;
return &r->resample_buf;
}
n_samples = (unsigned) (input->length / r->w_sz);
n_frames = n_samples / r->o_ss.channels;
-
- r->from_work_format_buf.index = 0;
- r->from_work_format_buf.length = r->o_fz * n_frames;
-
- if (!r->from_work_format_buf.memblock || r->from_work_format_buf_samples < n_samples) {
- if (r->from_work_format_buf.memblock)
- pa_memblock_unref(r->from_work_format_buf.memblock);
-
- r->from_work_format_buf_samples = n_samples;
- r->from_work_format_buf.memblock = pa_memblock_new(r->mempool, r->from_work_format_buf.length);
- }
+ fit_buf(r, &r->from_work_format_buf, r->o_fz * n_frames, &r->from_work_format_buf_size, 0);
src = pa_memblock_acquire_chunk(input);
dst = pa_memblock_acquire(r->from_work_format_buf.memblock);
buf = (pa_memchunk*) in;
buf = convert_to_work_format(r, buf);
- buf = remap_channels(r, buf);
- buf = resample(r, buf);
+
+ /* Try to save resampling effort: if we have more output channels than
+ * input channels, do resampling first, then remapping. */
+ if (r->o_ss.channels <= r->i_ss.channels) {
+ buf = remap_channels(r, buf);
+ buf = resample(r, buf);
+ } else {
+ buf = resample(r, buf);
+ buf = remap_channels(r, buf);
+ }
if (buf->length) {
buf = convert_from_work_format(r, buf);
pa_memchunk_reset(out);
}
-static void save_leftover(pa_resampler *r, void *buf, size_t len) {
- void *dst;
-
- pa_assert(r);
- pa_assert(buf);
- pa_assert(len > 0);
-
- /* Store the leftover to remap_buf. */
-
- r->remap_buf.length = len;
-
- if (!r->remap_buf.memblock || r->remap_buf_size < r->remap_buf.length) {
- if (r->remap_buf.memblock)
- pa_memblock_unref(r->remap_buf.memblock);
-
- r->remap_buf_size = r->remap_buf.length;
- r->remap_buf.memblock = pa_memblock_new(r->mempool, r->remap_buf.length);
- }
-
- dst = pa_memblock_acquire(r->remap_buf.memblock);
- memcpy(dst, buf, r->remap_buf.length);
- pa_memblock_release(r->remap_buf.memblock);
-
- r->remap_buf_contains_leftover_data = true;
-}
-
/*** libsamplerate based implementation ***/
#ifdef HAVE_LIBSAMPLERATE
-static void libsamplerate_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+static unsigned libsamplerate_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
SRC_DATA data;
- struct src_data *libsamplerate_data;
+ SRC_STATE *state;
pa_assert(r);
pa_assert(input);
pa_assert(output);
pa_assert(out_n_frames);
- libsamplerate_data = r->impl_data;
+ state = r->impl.data;
memset(&data, 0, sizeof(data));
data.data_in = pa_memblock_acquire_chunk(input);
data.src_ratio = (double) r->o_ss.rate / r->i_ss.rate;
data.end_of_input = 0;
- pa_assert_se(src_process(libsamplerate_data->state, &data) == 0);
-
- if (data.input_frames_used < in_n_frames) {
- void *leftover_data = data.data_in + data.input_frames_used * r->o_ss.channels;
- size_t leftover_length = (in_n_frames - data.input_frames_used) * sizeof(float) * r->o_ss.channels;
-
- save_leftover(r, leftover_data, leftover_length);
- }
+ pa_assert_se(src_process(state, &data) == 0);
pa_memblock_release(input->memblock);
pa_memblock_release(output->memblock);
*out_n_frames = (unsigned) data.output_frames_gen;
+
+ return in_n_frames - data.input_frames_used;
}
static void libsamplerate_update_rates(pa_resampler *r) {
- struct src_data *libsamplerate_data;
+ SRC_STATE *state;
pa_assert(r);
- libsamplerate_data = r->impl_data;
- pa_assert_se(src_set_ratio(libsamplerate_data->state, (double) r->o_ss.rate / r->i_ss.rate) == 0);
+ state = r->impl.data;
+ pa_assert_se(src_set_ratio(state, (double) r->o_ss.rate / r->i_ss.rate) == 0);
}
static void libsamplerate_reset(pa_resampler *r) {
- struct src_data *libsamplerate_data;
+ SRC_STATE *state;
pa_assert(r);
- libsamplerate_data = r->impl_data;
- pa_assert_se(src_reset(libsamplerate_data->state) == 0);
+ state = r->impl.data;
+ pa_assert_se(src_reset(state) == 0);
}
static void libsamplerate_free(pa_resampler *r) {
- struct src_data *libsamplerate_data;
+ SRC_STATE *state;
pa_assert(r);
- libsamplerate_data = r->impl_data;
- if (libsamplerate_data->state)
- src_delete(libsamplerate_data->state);
+ state = r->impl.data;
+ if (state)
+ src_delete(state);
}
static int libsamplerate_init(pa_resampler *r) {
int err;
- struct src_data *libsamplerate_data;
+ SRC_STATE *state;
pa_assert(r);
- libsamplerate_data = pa_xnew(struct src_data, 1);
-
- if (!(libsamplerate_data->state = src_new(r->method, r->o_ss.channels, &err)))
+ if (!(state = src_new(r->method, r->work_channels, &err)))
return -1;
- r->impl_free = libsamplerate_free;
- r->impl_update_rates = libsamplerate_update_rates;
- r->impl_resample = libsamplerate_resample;
- r->impl_reset = libsamplerate_reset;
- r->impl_data = libsamplerate_data;
+ r->impl.free = libsamplerate_free;
+ r->impl.update_rates = libsamplerate_update_rates;
+ r->impl.resample = libsamplerate_resample;
+ r->impl.reset = libsamplerate_reset;
+ r->impl.data = state;
return 0;
}
#endif
-#ifdef HAVE_SPEEX
/*** speex based implementation ***/
-static void speex_resample_float(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+static bool speex_is_fixed_point(void) {
+ static bool result = false;
+#ifdef HAVE_SPEEX
+ PA_ONCE_BEGIN {
+ float f_out = -1.0f, f_in = 1.0f;
+ spx_uint32_t in_len = 1, out_len = 1;
+ SpeexResamplerState *s;
+
+ pa_assert_se(s = speex_resampler_init(1, 1, 1,
+ SPEEX_RESAMPLER_QUALITY_MIN, NULL));
+
+ /* feed one sample that is too soft for fixed-point speex */
+ pa_assert_se(speex_resampler_process_float(s, 0, &f_in, &in_len,
+ &f_out, &out_len) == RESAMPLER_ERR_SUCCESS);
+
+ /* expecting sample has been processed, one sample output */
+ pa_assert_se(in_len == 1 && out_len == 1);
+
+ /* speex compiled with --enable-fixed-point will output 0.0 due to insufficient precision */
+ if (fabsf(f_out) < 0.00001f)
+ result = true;
+
+ speex_resampler_destroy(s);
+ } PA_ONCE_END;
+#endif
+ return result;
+}
+
+#ifdef HAVE_SPEEX
+static unsigned speex_resample_float(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
float *in, *out;
uint32_t inf = in_n_frames, outf = *out_n_frames;
- struct speex_data *speex_data;
+ SpeexResamplerState *state;
pa_assert(r);
pa_assert(input);
pa_assert(output);
pa_assert(out_n_frames);
- speex_data = r->impl_data;
+ state = r->impl.data;
in = pa_memblock_acquire_chunk(input);
out = pa_memblock_acquire_chunk(output);
- pa_assert_se(speex_resampler_process_interleaved_float(speex_data->state, in, &inf, out, &outf) == 0);
+ /* Strictly speaking, speex resampler expects its input
+ * to be normalized to the [-32768.0 .. 32767.0] range.
+ * This matters if speex has been compiled with --enable-fixed-point,
+ * because such speex will round the samples to the nearest
+ * integer. speex with --enable-fixed-point is therefore incompatible
+ * with PulseAudio's floating-point sample range [-1 .. 1]. speex
+ * without --enable-fixed-point works fine with this range.
+ * Care has been taken to call speex_resample_float() only
+ * for speex compiled without --enable-fixed-point.
+ */
+ pa_assert_se(speex_resampler_process_interleaved_float(state, in, &inf, out, &outf) == 0);
pa_memblock_release(input->memblock);
pa_memblock_release(output->memblock);
pa_assert(inf == in_n_frames);
*out_n_frames = outf;
+
+ return 0;
}
-static void speex_resample_int(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+static unsigned speex_resample_int(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
int16_t *in, *out;
uint32_t inf = in_n_frames, outf = *out_n_frames;
- struct speex_data *speex_data;
+ SpeexResamplerState *state;
pa_assert(r);
pa_assert(input);
pa_assert(output);
pa_assert(out_n_frames);
- speex_data = r->impl_data;
+ state = r->impl.data;
in = pa_memblock_acquire_chunk(input);
out = pa_memblock_acquire_chunk(output);
- pa_assert_se(speex_resampler_process_interleaved_int(speex_data->state, in, &inf, out, &outf) == 0);
+ pa_assert_se(speex_resampler_process_interleaved_int(state, in, &inf, out, &outf) == 0);
pa_memblock_release(input->memblock);
pa_memblock_release(output->memblock);
pa_assert(inf == in_n_frames);
*out_n_frames = outf;
+
+ return 0;
}
static void speex_update_rates(pa_resampler *r) {
- struct speex_data *speex_data;
+ SpeexResamplerState *state;
pa_assert(r);
- speex_data = r->impl_data;
+ state = r->impl.data;
- pa_assert_se(speex_resampler_set_rate(speex_data->state, r->i_ss.rate, r->o_ss.rate) == 0);
+ pa_assert_se(speex_resampler_set_rate(state, r->i_ss.rate, r->o_ss.rate) == 0);
}
static void speex_reset(pa_resampler *r) {
- struct speex_data *speex_data;
+ SpeexResamplerState *state;
pa_assert(r);
- speex_data = r->impl_data;
+ state = r->impl.data;
- pa_assert_se(speex_resampler_reset_mem(speex_data->state) == 0);
+ pa_assert_se(speex_resampler_reset_mem(state) == 0);
}
static void speex_free(pa_resampler *r) {
- struct speex_data *speex_data;
+ SpeexResamplerState *state;
pa_assert(r);
- speex_data = r->impl_data;
- if (!speex_data->state)
+ state = r->impl.data;
+ if (!state)
return;
- speex_resampler_destroy(speex_data->state);
+ speex_resampler_destroy(state);
}
static int speex_init(pa_resampler *r) {
int q, err;
- struct speex_data *speex_data;
+ SpeexResamplerState *state;
pa_assert(r);
- speex_data = pa_xnew(struct speex_data, 1);
-
- r->impl_free = speex_free;
- r->impl_update_rates = speex_update_rates;
- r->impl_reset = speex_reset;
- r->impl_data = speex_data;
+ r->impl.free = speex_free;
+ r->impl.update_rates = speex_update_rates;
+ r->impl.reset = speex_reset;
if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) {
q = r->method - PA_RESAMPLER_SPEEX_FIXED_BASE;
- r->impl_resample = speex_resample_int;
+ r->impl.resample = speex_resample_int;
} else {
pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX);
q = r->method - PA_RESAMPLER_SPEEX_FLOAT_BASE;
- r->impl_resample = speex_resample_float;
+ r->impl.resample = speex_resample_float;
}
pa_log_info("Choosing speex quality setting %i.", q);
- if (!(speex_data->state = speex_resampler_init(r->o_ss.channels, r->i_ss.rate, r->o_ss.rate, q, &err)))
+ if (!(state = speex_resampler_init(r->work_channels, r->i_ss.rate, r->o_ss.rate, q, &err)))
return -1;
+ r->impl.data = state;
+
return 0;
}
#endif
/* Trivial implementation */
-static void trivial_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
- size_t fz;
+static unsigned trivial_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
unsigned i_index, o_index;
void *src, *dst;
struct trivial_data *trivial_data;
pa_assert(output);
pa_assert(out_n_frames);
- trivial_data = r->impl_data;
- fz = r->w_sz * r->o_ss.channels;
+ trivial_data = r->impl.data;
src = pa_memblock_acquire_chunk(input);
dst = pa_memblock_acquire_chunk(output);
if (i_index >= in_n_frames)
break;
- pa_assert_fp(o_index * fz < pa_memblock_get_length(output->memblock));
+ pa_assert_fp(o_index * r->w_fz < pa_memblock_get_length(output->memblock));
- memcpy((uint8_t*) dst + fz * o_index, (uint8_t*) src + fz * i_index, (int) fz);
+ memcpy((uint8_t*) dst + r->w_fz * o_index, (uint8_t*) src + r->w_fz * i_index, (int) r->w_fz);
}
pa_memblock_release(input->memblock);
trivial_data->i_counter -= r->i_ss.rate;
trivial_data->o_counter -= r->o_ss.rate;
}
+
+ return 0;
}
static void trivial_update_rates_or_reset(pa_resampler *r) {
struct trivial_data *trivial_data;
pa_assert(r);
- trivial_data = r->impl_data;
+ trivial_data = r->impl.data;
trivial_data->i_counter = 0;
trivial_data->o_counter = 0;
trivial_data = pa_xnew0(struct trivial_data, 1);
- r->impl_resample = trivial_resample;
- r->impl_update_rates = trivial_update_rates_or_reset;
- r->impl_reset = trivial_update_rates_or_reset;
- r->impl_data = trivial_data;
+ r->impl.resample = trivial_resample;
+ r->impl.update_rates = trivial_update_rates_or_reset;
+ r->impl.reset = trivial_update_rates_or_reset;
+ r->impl.data = trivial_data;
return 0;
}
/* Peak finder implementation */
-static void peaks_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+static unsigned peaks_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
unsigned c, o_index = 0;
unsigned i, i_end = 0;
void *src, *dst;
pa_assert(output);
pa_assert(out_n_frames);
- peaks_data = r->impl_data;
+ peaks_data = r->impl.data;
src = pa_memblock_acquire_chunk(input);
dst = pa_memblock_acquire_chunk(output);
i_end = ((uint64_t) (peaks_data->o_counter + 1) * r->i_ss.rate) / r->o_ss.rate;
i_end = i_end > peaks_data->i_counter ? i_end - peaks_data->i_counter : 0;
- pa_assert_fp(o_index * r->w_sz * r->o_ss.channels < pa_memblock_get_length(output->memblock));
+ pa_assert_fp(o_index * r->w_fz < pa_memblock_get_length(output->memblock));
/* 1ch float is treated separately, because that is the common case */
- if (r->o_ss.channels == 1 && r->work_format == PA_SAMPLE_FLOAT32NE) {
+ if (r->work_channels == 1 && r->work_format == PA_SAMPLE_FLOAT32NE) {
float *s = (float*) src + i;
float *d = (float*) dst + o_index;
o_index++, peaks_data->o_counter++;
}
} else if (r->work_format == PA_SAMPLE_S16NE) {
- int16_t *s = (int16_t*) src + r->o_ss.channels * i;
- int16_t *d = (int16_t*) dst + r->o_ss.channels * o_index;
+ int16_t *s = (int16_t*) src + r->work_channels * i;
+ int16_t *d = (int16_t*) dst + r->work_channels * o_index;
for (; i < i_end && i < in_n_frames; i++)
- for (c = 0; c < r->o_ss.channels; c++) {
+ for (c = 0; c < r->work_channels; c++) {
int16_t n = abs(*s++);
if (n > peaks_data->max_i[c])
}
if (i == i_end) {
- for (c = 0; c < r->o_ss.channels; c++, d++) {
+ for (c = 0; c < r->work_channels; c++, d++) {
*d = peaks_data->max_i[c];
peaks_data->max_i[c] = 0;
}
o_index++, peaks_data->o_counter++;
}
} else {
- float *s = (float*) src + r->o_ss.channels * i;
- float *d = (float*) dst + r->o_ss.channels * o_index;
+ float *s = (float*) src + r->work_channels * i;
+ float *d = (float*) dst + r->work_channels * o_index;
for (; i < i_end && i < in_n_frames; i++)
- for (c = 0; c < r->o_ss.channels; c++) {
+ for (c = 0; c < r->work_channels; c++) {
float n = fabsf(*s++);
if (n > peaks_data->max_f[c])
}
if (i == i_end) {
- for (c = 0; c < r->o_ss.channels; c++, d++) {
+ for (c = 0; c < r->work_channels; c++, d++) {
*d = peaks_data->max_f[c];
peaks_data->max_f[c] = 0;
}
peaks_data->i_counter -= r->i_ss.rate;
peaks_data->o_counter -= r->o_ss.rate;
}
+
+ return 0;
}
static void peaks_update_rates_or_reset(pa_resampler *r) {
struct peaks_data *peaks_data;
pa_assert(r);
- peaks_data = r->impl_data;
+ peaks_data = r->impl.data;
peaks_data->i_counter = 0;
peaks_data->o_counter = 0;
peaks_data = pa_xnew0(struct peaks_data, 1);
- r->impl_resample = peaks_resample;
- r->impl_update_rates = peaks_update_rates_or_reset;
- r->impl_reset = peaks_update_rates_or_reset;
- r->impl_data = peaks_data;
+ r->impl.resample = peaks_resample;
+ r->impl.update_rates = peaks_update_rates_or_reset;
+ r->impl.reset = peaks_update_rates_or_reset;
+ r->impl.data = peaks_data;
return 0;
}
/*** ffmpeg based implementation ***/
-static void ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+static unsigned ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
unsigned used_frames = 0, c;
int previous_consumed_frames = -1;
struct ffmpeg_data *ffmpeg_data;
pa_assert(output);
pa_assert(out_n_frames);
- ffmpeg_data = r->impl_data;
+ ffmpeg_data = r->impl.data;
- for (c = 0; c < r->o_ss.channels; c++) {
+ for (c = 0; c < r->work_channels; c++) {
unsigned u;
pa_memblock *b, *w;
int16_t *p, *t, *k, *q, *s;
int consumed_frames;
/* Allocate a new block */
- b = pa_memblock_new(r->mempool, ffmpeg_data->buf[c].length + in_n_frames * sizeof(int16_t));
+ b = pa_memblock_new(r->mempool, in_n_frames * sizeof(int16_t));
p = pa_memblock_acquire(b);
/* Now copy the input data, splitting up channels */
k = p;
for (u = 0; u < in_n_frames; u++) {
*k = *t;
- t += r->o_ss.channels;
+ t += r->work_channels;
k ++;
}
pa_memblock_release(input->memblock);
q, p,
&consumed_frames,
(int) in_n_frames, (int) *out_n_frames,
- c >= (unsigned) (r->o_ss.channels-1));
+ c >= (unsigned) (r->work_channels-1));
pa_memblock_release(b);
pa_memblock_unref(b);
for (u = 0; u < used_frames; u++) {
*s = *q;
q++;
- s += r->o_ss.channels;
+ s += r->work_channels;
}
pa_memblock_release(output->memblock);
pa_memblock_release(w);
pa_memblock_unref(w);
}
- if (previous_consumed_frames < (int) in_n_frames) {
- void *leftover_data = (int16_t *) pa_memblock_acquire_chunk(input) + previous_consumed_frames * r->o_ss.channels;
- size_t leftover_length = (in_n_frames - previous_consumed_frames) * r->o_ss.channels * sizeof(int16_t);
-
- save_leftover(r, leftover_data, leftover_length);
- pa_memblock_release(input->memblock);
- }
-
*out_n_frames = used_frames;
+
+ return in_n_frames - previous_consumed_frames;
}
static void ffmpeg_free(pa_resampler *r) {
- unsigned c;
struct ffmpeg_data *ffmpeg_data;
pa_assert(r);
- ffmpeg_data = r->impl_data;
+ ffmpeg_data = r->impl.data;
if (ffmpeg_data->state)
av_resample_close(ffmpeg_data->state);
-
- for (c = 0; c < PA_ELEMENTSOF(ffmpeg_data->buf); c++)
- if (ffmpeg_data->buf[c].memblock)
- pa_memblock_unref(ffmpeg_data->buf[c].memblock);
}
static int ffmpeg_init(pa_resampler *r) {
- unsigned c;
struct ffmpeg_data *ffmpeg_data;
pa_assert(r);
if (!(ffmpeg_data->state = av_resample_init((int) r->o_ss.rate, (int) r->i_ss.rate, 16, 10, 0, 0.8)))
return -1;
- r->impl_free = ffmpeg_free;
- r->impl_resample = ffmpeg_resample;
- r->impl_data = (void *) ffmpeg_data;
-
- for (c = 0; c < PA_ELEMENTSOF(ffmpeg_data->buf); c++)
- pa_memchunk_reset(&ffmpeg_data->buf[c]);
+ r->impl.free = ffmpeg_free;
+ r->impl.resample = ffmpeg_resample;
+ r->impl.data = (void *) ffmpeg_data;
return 0;
}