summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Chini <georg@chini.tk>2021-01-01 00:22:11 +0100
committerPulseAudio Marge Bot <pulseaudio-maintainers@lists.freedesktop.org>2021-11-03 18:37:31 +0000
commit656179a8fe16126a17b8dc48ae44f97bfcfb26a6 (patch)
treeec174774bc0e6295f4c394a6cbbaad2a134d9215
parent1bfabd6512cf83a0c3ecadea3942f38ad0cb786a (diff)
resampler: Add pa_resampler_prepare() and pa_resampler_get_delay() functions
The pa_resampler_get_delay() function allows to retrieve the current resampler delay in input samples for all supported resamplers. The return value is a double to maintain precision when using variable rate resamplers. Because in many places the delay is needed in usec, pa_resampler_get_delay_usec() was also supplied. The speex resampler now skips leading zero samples to provide meaningful delay values. In the next patch, the pa_resampler_prepare() function will be used to train the resampler after a rewind. It takes data from a history memblockq and runs it through the resampler. The output data is discarded. To make this logic possible, the soxr resampler had to be converted to use variable rate. The fixed rate version has a variable delay, therefore the logic above could not be applied. Additionally, with fixed rate, the delay is larger than 150ms in some situations, while with variable rate the delay is fixed and comparable to the other resamplers. Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/120>
-rw-r--r--src/pulsecore/resampler.c116
-rw-r--r--src/pulsecore/resampler.h14
-rw-r--r--src/pulsecore/resampler/soxr.c31
-rw-r--r--src/pulsecore/resampler/speex.c3
-rw-r--r--src/pulsecore/sink-input.c3
-rw-r--r--src/pulsecore/source-output.c3
6 files changed, 142 insertions, 28 deletions
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index 9c39a0241..0d3ac407e 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -25,6 +25,7 @@
#include <math.h>
#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/strbuf.h>
@@ -164,9 +165,6 @@ static pa_resample_method_t fix_method(
}
/* Else fall through */
case PA_RESAMPLER_FFMPEG:
- case PA_RESAMPLER_SOXR_MQ:
- case PA_RESAMPLER_SOXR_HQ:
- case PA_RESAMPLER_SOXR_VHQ:
if (flags & PA_RESAMPLER_VARIABLE_RATE) {
pa_log_info("Resampler '%s' cannot do variable rate, reverting to resampler 'auto'.", pa_resample_method_to_string(method));
method = PA_RESAMPLER_AUTO;
@@ -350,6 +348,8 @@ pa_resampler* pa_resampler_new(
r->mempool = pool;
r->method = method;
r->flags = flags;
+ r->in_frames = 0;
+ r->out_frames = 0;
/* Fill sample specs */
r->i_ss = *a;
@@ -480,6 +480,10 @@ void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate) {
if (r->i_ss.rate == rate)
return;
+ /* Recalculate delay counters */
+ r->in_frames = pa_resampler_get_delay(r, false);
+ r->out_frames = 0;
+
r->i_ss.rate = rate;
r->impl.update_rates(r);
@@ -493,6 +497,10 @@ void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) {
if (r->o_ss.rate == rate)
return;
+ /* Recalculate delay counters */
+ r->in_frames = pa_resampler_get_delay(r, false);
+ r->out_frames = 0;
+
r->o_ss.rate = rate;
r->impl.update_rates(r);
@@ -626,20 +634,89 @@ void pa_resampler_reset(pa_resampler *r) {
pa_lfe_filter_reset(r->lfe_filter);
*r->have_leftover = false;
+
+ r->in_frames = 0;
+ r->out_frames = 0;
}
-void pa_resampler_rewind(pa_resampler *r, size_t out_frames) {
+/* This function runs amount bytes of data from the history queue through the
+ * resampler and discards the result. The history queue is unchanged after the
+ * call. This is used to preload a resampler after a reset. Returns the number
+ * of frames produced by the resampler. */
+size_t pa_resampler_prepare(pa_resampler *r, pa_memblockq *history_queue, size_t amount) {
+ size_t history_bytes, max_block_size, out_size;
+ int64_t to_run;
+
pa_assert(r);
- /* For now, we don't have any rewindable resamplers, so we just
- reset the resampler instead (and hope that nobody hears the difference). */
- if (r->impl.reset)
+ if (!history_queue || amount == 0)
+ return 0;
+
+ /* Rewind the LFE filter by the amount of history data. */
+ history_bytes = pa_resampler_result(r, amount);
+ if (r->lfe_filter)
+ pa_lfe_filter_rewind(r->lfe_filter, history_bytes);
+
+ pa_memblockq_rewind(history_queue, amount);
+ max_block_size = pa_resampler_max_block_size(r);
+ to_run = amount;
+ out_size = 0;
+
+ while (to_run > 0) {
+ pa_memchunk in_chunk, out_chunk;
+ size_t current;
+
+ current = PA_MIN(to_run, (int64_t) max_block_size);
+
+ /* Get data from memblockq */
+ if (pa_memblockq_peek_fixed_size(history_queue, current, &in_chunk) < 0) {
+ pa_log_warn("Could not read history data for resampler.");
+
+ /* Restore queue to original state and reset resampler */
+ pa_memblockq_drop(history_queue, to_run);
+ pa_resampler_reset(r);
+ return out_size;
+ }
+
+ /* Run the resampler */
+ pa_resampler_run(r, &in_chunk, &out_chunk);
+
+ /* Discard result */
+ if (out_chunk.length != 0) {
+ out_size += out_chunk.length;
+ pa_memblock_unref(out_chunk.memblock);
+ }
+
+ pa_memblock_unref(in_chunk.memblock);
+ pa_memblockq_drop(history_queue, current);
+ to_run -= current;
+ }
+
+ return out_size;
+}
+
+size_t pa_resampler_rewind(pa_resampler *r, size_t out_bytes, pa_memblockq *history_queue, size_t amount) {
+ pa_assert(r);
+
+ /* For now, we don't have any rewindable resamplers, so we just reset
+ * the resampler if we cannot rewind using pa_resampler_prepare(). */
+ if (r->impl.reset && !history_queue)
r->impl.reset(r);
if (r->lfe_filter)
- pa_lfe_filter_rewind(r->lfe_filter, out_frames);
+ pa_lfe_filter_rewind(r->lfe_filter, out_bytes);
- *r->have_leftover = false;
+ if (!history_queue) {
+ *r->have_leftover = false;
+
+ r->in_frames = 0;
+ r->out_frames = 0;
+ }
+
+ if (history_queue && amount > 0)
+ return pa_resampler_prepare(r, history_queue, amount);
+
+ return 0;
}
pa_resample_method_t pa_resampler_get_method(pa_resampler *r) {
@@ -1509,6 +1586,7 @@ void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out)
pa_assert(in->length % r->i_fz == 0);
buf = (pa_memchunk*) in;
+ r->in_frames += buf->length / r->i_fz;
buf = convert_to_work_format(r, buf);
/* Try to save resampling effort: if we have more output channels than
@@ -1527,6 +1605,7 @@ void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out)
if (buf->length) {
buf = convert_from_work_format(r, buf);
*out = *buf;
+ r->out_frames += buf->length / r->o_fz;
if (buf == in)
pa_memblock_ref(buf->memblock);
@@ -1536,6 +1615,25 @@ void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out)
pa_memchunk_reset(out);
}
+/* Get delay in input frames. Some resamplers may have negative delay. */
+double pa_resampler_get_delay(pa_resampler *r, bool allow_negative) {
+ double frames;
+
+ frames = r->out_frames * r->i_ss.rate / r->o_ss.rate;
+ if (frames >= r->in_frames && !allow_negative)
+ return 0;
+ return r->in_frames - frames;
+}
+
+/* Get delay in usec */
+pa_usec_t pa_resampler_get_delay_usec(pa_resampler *r) {
+
+ if (!r)
+ return 0;
+
+ return (pa_usec_t) (pa_resampler_get_delay(r, false) * PA_USEC_PER_SEC / r->i_ss.rate);
+}
+
/*** copy (noop) implementation ***/
static int copy_init(pa_resampler *r) {
diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h
index 783f69999..e98146d85 100644
--- a/src/pulsecore/resampler.h
+++ b/src/pulsecore/resampler.h
@@ -111,6 +111,9 @@ struct pa_resampler {
pa_remap_t remap;
bool map_required;
+ double in_frames;
+ double out_frames;
+
pa_lfe_filter_t *lfe_filter;
pa_resampler_impl impl;
@@ -149,8 +152,11 @@ void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate);
/* Reinitialize state of the resampler, possibly due to seeking or other discontinuities */
void pa_resampler_reset(pa_resampler *r);
+/* Prepare resampler for use by running some old data through it. */
+size_t pa_resampler_prepare(pa_resampler *r, pa_memblockq *history_queue, size_t amount);
+
/* Rewind resampler */
-void pa_resampler_rewind(pa_resampler *r, size_t out_frames);
+size_t pa_resampler_rewind(pa_resampler *r, size_t out_bytes, pa_memblockq *history_queue, size_t amount);
/* Return the resampling method of the resampler object */
pa_resample_method_t pa_resampler_get_method(pa_resampler *r);
@@ -164,6 +170,12 @@ const char *pa_resample_method_to_string(pa_resample_method_t m);
/* Return 1 when the specified resampling method is supported */
int pa_resample_method_supported(pa_resample_method_t m);
+/* Get delay of the resampler in input frames */
+double pa_resampler_get_delay(pa_resampler *r, bool allow_negative);
+
+/* Get delay of the resampler in usec */
+pa_usec_t pa_resampler_get_delay_usec(pa_resampler *r);
+
const pa_channel_map* pa_resampler_input_channel_map(pa_resampler *r);
const pa_sample_spec* pa_resampler_input_sample_spec(pa_resampler *r);
const pa_channel_map* pa_resampler_output_channel_map(pa_resampler *r);
diff --git a/src/pulsecore/resampler/soxr.c b/src/pulsecore/resampler/soxr.c
index b1b2e19dc..1865a0341 100644
--- a/src/pulsecore/resampler/soxr.c
+++ b/src/pulsecore/resampler/soxr.c
@@ -65,9 +65,14 @@ static void resampler_soxr_free(pa_resampler *r) {
static void resampler_soxr_reset(pa_resampler *r) {
#if SOXR_THIS_VERSION >= SOXR_VERSION(0, 1, 2)
+ double ratio;
+
pa_assert(r);
soxr_clear(r->impl.data);
+
+ ratio = (double)r->i_ss.rate / (double)r->o_ss.rate;
+ soxr_set_io_ratio(r->impl.data, ratio, 0);
#else
/* With libsoxr prior to 0.1.2 soxr_clear() makes soxr_process() crash afterwards,
* so don't use this function and re-create the context instead. */
@@ -89,23 +94,12 @@ static void resampler_soxr_reset(pa_resampler *r) {
}
static void resampler_soxr_update_rates(pa_resampler *r) {
- soxr_t old_state;
+ double ratio;
pa_assert(r);
- /* There is no update method in libsoxr,
- * so just re-create the resampler context */
-
- old_state = r->impl.data;
- r->impl.data = NULL;
-
- if (pa_resampler_soxr_init(r) == 0) {
- if (old_state)
- soxr_delete(old_state);
- } else {
- r->impl.data = old_state;
- pa_log_error("Failed to update libsoxr sample rates");
- }
+ ratio = (double)r->i_ss.rate / (double)r->o_ss.rate;
+ soxr_set_io_ratio(r->impl.data, ratio, 0);
}
int pa_resampler_soxr_init(pa_resampler *r) {
@@ -116,6 +110,7 @@ int pa_resampler_soxr_init(pa_resampler *r) {
unsigned long quality_recipe;
soxr_quality_spec_t quality;
soxr_error_t err = NULL;
+ double ratio;
pa_assert(r);
@@ -150,14 +145,18 @@ int pa_resampler_soxr_init(pa_resampler *r) {
pa_assert_not_reached();
}
- quality = soxr_quality_spec(quality_recipe, 0);
+ quality = soxr_quality_spec(quality_recipe, SOXR_VR);
- state = soxr_create(r->i_ss.rate, r->o_ss.rate, r->work_channels, &err, &io_spec, &quality, &runtime_spec);
+ /* Maximum resample ratio is 100:1 */
+ state = soxr_create(100, 1, r->work_channels, &err, &io_spec, &quality, &runtime_spec);
if (!state) {
pa_log_error("Failed to create libsoxr resampler context: %s.", (err ? err : "[unknown error]"));
return -1;
}
+ ratio = (double)r->i_ss.rate / (double)r->o_ss.rate;
+ soxr_set_io_ratio(state, ratio, 0);
+
r->impl.free = resampler_soxr_free;
r->impl.reset = resampler_soxr_reset;
r->impl.update_rates = resampler_soxr_update_rates;
diff --git a/src/pulsecore/resampler/speex.c b/src/pulsecore/resampler/speex.c
index 66387e5bd..24011cb0d 100644
--- a/src/pulsecore/resampler/speex.c
+++ b/src/pulsecore/resampler/speex.c
@@ -132,6 +132,7 @@ static void speex_reset(pa_resampler *r) {
state = r->impl.data;
pa_assert_se(speex_resampler_reset_mem(state) == 0);
+ speex_resampler_skip_zeros(state);
}
static void speex_free(pa_resampler *r) {
@@ -172,6 +173,8 @@ int pa_resampler_speex_init(pa_resampler *r) {
if (!(state = speex_resampler_init(r->work_channels, r->i_ss.rate, r->o_ss.rate, q, &err)))
return -1;
+ speex_resampler_skip_zeros(state);
+
r->impl.data = state;
return 0;
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index 31dae6a78..29c6a795b 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -1183,7 +1183,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
/* And rewind the resampler */
if (i->thread_info.resampler)
- pa_resampler_rewind(i->thread_info.resampler, amount);
+ pa_resampler_rewind(i->thread_info.resampler, amount, NULL, 0);
if (i->thread_info.rewrite_flush) {
pa_memblockq_silence(i->thread_info.render_memblockq);
@@ -2167,6 +2167,7 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
pa_usec_t *r = userdata;
r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
+ r[0] += pa_resampler_get_delay_usec(i->thread_info.resampler);
r[1] += pa_sink_get_latency_within_thread(i->sink, false);
return 0;
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index bee9241b9..cf16c400b 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -876,7 +876,7 @@ void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in so
o->process_rewind(o, nbytes);
if (o->thread_info.resampler)
- pa_resampler_rewind(o->thread_info.resampler, nbytes);
+ pa_resampler_rewind(o->thread_info.resampler, nbytes, NULL, 0);
} else
pa_memblockq_seek(o->thread_info.delay_memblockq, - ((int64_t) nbytes), PA_SEEK_RELATIVE, true);
@@ -1701,6 +1701,7 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int
pa_usec_t *r = userdata;
r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec);
+ r[0] += pa_resampler_get_delay_usec(o->thread_info.resampler);
r[1] += pa_source_get_latency_within_thread(o->source, false);
return 0;