mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2026-02-21 01:40:30 -05:00
438 lines
15 KiB
C
438 lines
15 KiB
C
|
|
/***
|
||
|
|
This file is part of PulseAudio.
|
||
|
|
|
||
|
|
PulseAudio is free software; you can redistribute it and/or modify
|
||
|
|
it under the terms of the GNU Lesser General Public License as published
|
||
|
|
by the Free Software Foundation; either version 2.1 of the License,
|
||
|
|
or (at your option) any later version.
|
||
|
|
|
||
|
|
PulseAudio is distributed in the hope that it will be useful, but
|
||
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
General Public License for more details.
|
||
|
|
|
||
|
|
You should have received a copy of the GNU Lesser General Public License
|
||
|
|
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
||
|
|
***/
|
||
|
|
|
||
|
|
#ifdef HAVE_CONFIG_H
|
||
|
|
#include <config.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <getopt.h>
|
||
|
|
#include <locale.h>
|
||
|
|
#include <math.h>
|
||
|
|
|
||
|
|
#include <pulse/pulseaudio.h>
|
||
|
|
|
||
|
|
#include <pulse/rtclock.h>
|
||
|
|
#include <pulse/sample.h>
|
||
|
|
#include <pulse/volume.h>
|
||
|
|
|
||
|
|
#include <pulsecore/i18n.h>
|
||
|
|
#include <pulsecore/log.h>
|
||
|
|
#include <pulsecore/resampler.h>
|
||
|
|
#include <pulsecore/macro.h>
|
||
|
|
#include <pulsecore/endianmacros.h>
|
||
|
|
#include <pulsecore/memblock.h>
|
||
|
|
#include <pulsecore/memblockq.h>
|
||
|
|
#include <pulsecore/sample-util.h>
|
||
|
|
#include <pulsecore/core-util.h>
|
||
|
|
|
||
|
|
#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
|
||
|
|
#define PA_SILENCE_MAX (pa_page_size()*16)
|
||
|
|
#define MAX_MATCHING_PERIOD 500
|
||
|
|
|
||
|
|
static pa_memblock *silence_memblock_new(pa_mempool *pool, uint8_t c) {
|
||
|
|
pa_memblock *b;
|
||
|
|
size_t length;
|
||
|
|
void *data;
|
||
|
|
|
||
|
|
pa_assert(pool);
|
||
|
|
|
||
|
|
length = PA_MIN(pa_mempool_block_size_max(pool), PA_SILENCE_MAX);
|
||
|
|
|
||
|
|
b = pa_memblock_new(pool, length);
|
||
|
|
|
||
|
|
data = pa_memblock_acquire(b);
|
||
|
|
memset(data, c, length);
|
||
|
|
pa_memblock_release(b);
|
||
|
|
|
||
|
|
pa_memblock_set_is_silence(b, true);
|
||
|
|
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Calculate number of history bytes needed for the rewind */
|
||
|
|
static size_t calculate_resampler_history_bytes(pa_resampler *r, size_t in_rewind_frames) {
|
||
|
|
size_t history_frames, history_max, matching_period, total_frames, remainder;
|
||
|
|
double delay;
|
||
|
|
|
||
|
|
if (!r)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
/* Initialize some variables, cut off full seconds from the rewind */
|
||
|
|
total_frames = 0;
|
||
|
|
in_rewind_frames = in_rewind_frames % r->i_ss.rate;
|
||
|
|
history_max = (uint64_t) PA_RESAMPLER_MAX_DELAY_USEC * r->i_ss.rate * 3 / PA_USEC_PER_SEC / 2;
|
||
|
|
|
||
|
|
/* Get the current internal delay of the resampler */
|
||
|
|
delay = pa_resampler_get_delay(r, false);
|
||
|
|
|
||
|
|
/* Calculate the matchiung period */
|
||
|
|
matching_period = r->i_ss.rate / pa_resampler_get_gcd(r);
|
||
|
|
pa_log_debug("Integral period length is %lu input frames", matching_period);
|
||
|
|
|
||
|
|
/* If the delay is larger than the length of the history queue, we can only
|
||
|
|
* replay as much as we have. */
|
||
|
|
if ((size_t)delay >= history_max) {
|
||
|
|
history_frames = history_max;
|
||
|
|
return history_frames * r->i_fz;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Initially set the history to 3 times the resampler delay. Use at least 2 ms. */
|
||
|
|
history_frames = (size_t)(delay * 3.0);
|
||
|
|
history_frames = PA_MAX(history_frames, r->i_ss.rate / 500);
|
||
|
|
|
||
|
|
/* Check how the rewind fits into multiples of the matching period. */
|
||
|
|
remainder = (in_rewind_frames + history_frames) % matching_period;
|
||
|
|
|
||
|
|
/* If possible, use between 2 and 3 times the resampler delay */
|
||
|
|
if (remainder < (size_t)delay && history_frames - remainder <= history_max)
|
||
|
|
total_frames = in_rewind_frames + history_frames - remainder;
|
||
|
|
|
||
|
|
/* Else, try above 3 times the delay */
|
||
|
|
else if (history_frames + matching_period - remainder <= history_max)
|
||
|
|
total_frames = in_rewind_frames + history_frames + matching_period - remainder;
|
||
|
|
|
||
|
|
if (total_frames != 0)
|
||
|
|
/* We found a perfect match. */
|
||
|
|
history_frames = total_frames - in_rewind_frames;
|
||
|
|
else {
|
||
|
|
/* Try to use 2.5 times the delay. */
|
||
|
|
history_frames = PA_MIN((size_t)(delay * 2.5), history_max);
|
||
|
|
pa_log_debug("No usable integral matching period");
|
||
|
|
}
|
||
|
|
|
||
|
|
return history_frames * r->i_fz;
|
||
|
|
}
|
||
|
|
|
||
|
|
static float compare_blocks(const pa_sample_spec *ss, const pa_memchunk *chunk_a, const pa_memchunk *chunk_b) {
|
||
|
|
float *a, *b, max_diff = 0;
|
||
|
|
unsigned i;
|
||
|
|
|
||
|
|
a = pa_memblock_acquire(chunk_a->memblock);
|
||
|
|
b = pa_memblock_acquire(chunk_b->memblock);
|
||
|
|
a += chunk_a->index / pa_frame_size(ss);
|
||
|
|
b += chunk_b->index / pa_frame_size(ss);
|
||
|
|
|
||
|
|
for (i = 0; i < chunk_a->length / pa_frame_size(ss); i++) {
|
||
|
|
if (fabs(a[i] - b[i]) > max_diff)
|
||
|
|
max_diff = fabs(a[i] - b[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
pa_memblock_release(chunk_a->memblock);
|
||
|
|
pa_memblock_release(chunk_b->memblock);
|
||
|
|
|
||
|
|
return max_diff;
|
||
|
|
}
|
||
|
|
|
||
|
|
static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss, unsigned frequency, double amplitude, size_t nr_of_samples) {
|
||
|
|
pa_memblock *r;
|
||
|
|
float *d;
|
||
|
|
float val;
|
||
|
|
unsigned i;
|
||
|
|
int n;
|
||
|
|
float t, dt, dt_period;
|
||
|
|
|
||
|
|
pa_assert(frequency);
|
||
|
|
pa_assert(nr_of_samples);
|
||
|
|
pa_assert(ss->channels == 1);
|
||
|
|
pa_assert(ss->format == PA_SAMPLE_FLOAT32NE);
|
||
|
|
|
||
|
|
pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * nr_of_samples));
|
||
|
|
d = pa_memblock_acquire(r);
|
||
|
|
|
||
|
|
/* Generate square wave with given length, frequency and sample rate. */
|
||
|
|
val = amplitude;
|
||
|
|
t = 0;
|
||
|
|
n = 1;
|
||
|
|
dt = 1.0 / ss->rate;
|
||
|
|
dt_period = 1.0 / frequency;
|
||
|
|
for (i=0; i < nr_of_samples; i++) {
|
||
|
|
d[i] = val;
|
||
|
|
|
||
|
|
if ((int)(2 * t / dt_period) >= n) {
|
||
|
|
n++;
|
||
|
|
if (val >= amplitude)
|
||
|
|
val = - amplitude;
|
||
|
|
else
|
||
|
|
val = amplitude;
|
||
|
|
}
|
||
|
|
|
||
|
|
t += dt;
|
||
|
|
}
|
||
|
|
|
||
|
|
pa_memblock_release(r);
|
||
|
|
|
||
|
|
return r;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void help(const char *argv0) {
|
||
|
|
printf("%s [options]\n\n"
|
||
|
|
"-h, --help Show this help\n"
|
||
|
|
"-v, --verbose Print debug messages\n"
|
||
|
|
" --from-rate=SAMPLERATE From sample rate in Hz (defaults to 44100)\n"
|
||
|
|
" --to-rate=SAMPLERATE To sample rate in Hz (defaults to 44100)\n"
|
||
|
|
" --resample-method=METHOD Resample method (defaults to auto)\n"
|
||
|
|
" --frequency=unsigned Frequency of square wave\n"
|
||
|
|
" --samples=unsigned Number of samples for square wave\n"
|
||
|
|
" --rewind=unsigned Number of output samples to rewind\n"
|
||
|
|
"\n"
|
||
|
|
"This test generates samples for a square wave of given frequency, number of samples\n"
|
||
|
|
"and input sample rate. Then this input data is resampled to the output rate, rewound\n"
|
||
|
|
"by rewind samples and the rewound part is processed again. Then output is compared to\n"
|
||
|
|
"the result of the first pass.\n"
|
||
|
|
"\n"
|
||
|
|
"See --dump-resample-methods for possible values of resample methods.\n",
|
||
|
|
argv0);
|
||
|
|
}
|
||
|
|
|
||
|
|
enum {
|
||
|
|
ARG_VERSION = 256,
|
||
|
|
ARG_FROM_SAMPLERATE,
|
||
|
|
ARG_TO_SAMPLERATE,
|
||
|
|
ARG_FREQUENCY,
|
||
|
|
ARG_SAMPLES,
|
||
|
|
ARG_REWIND,
|
||
|
|
ARG_RESAMPLE_METHOD,
|
||
|
|
ARG_DUMP_RESAMPLE_METHODS
|
||
|
|
};
|
||
|
|
|
||
|
|
static void dump_resample_methods(void) {
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; i < PA_RESAMPLER_MAX; i++)
|
||
|
|
if (pa_resample_method_supported(i))
|
||
|
|
printf("%s\n", pa_resample_method_to_string(i));
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(int argc, char *argv[]) {
|
||
|
|
pa_mempool *pool = NULL;
|
||
|
|
pa_sample_spec a, b;
|
||
|
|
pa_resample_method_t method;
|
||
|
|
int ret = 1, c;
|
||
|
|
unsigned samples, frequency, rewind;
|
||
|
|
unsigned crossover_freq = 120;
|
||
|
|
pa_resampler *resampler;
|
||
|
|
pa_memchunk in_chunk, out_chunk, rewound_chunk, silence_chunk;
|
||
|
|
pa_usec_t ts;
|
||
|
|
pa_memblockq *history_queue = NULL;
|
||
|
|
size_t in_rewind_size, in_frame_size, history_size, out_rewind_size, old_length, in_resampler_buffer, n_out_expected;
|
||
|
|
float max_diff;
|
||
|
|
double delay_before, delay_after, delay_expected;
|
||
|
|
|
||
|
|
static const struct option long_options[] = {
|
||
|
|
{"help", 0, NULL, 'h'},
|
||
|
|
{"verbose", 0, NULL, 'v'},
|
||
|
|
{"version", 0, NULL, ARG_VERSION},
|
||
|
|
{"from-rate", 1, NULL, ARG_FROM_SAMPLERATE},
|
||
|
|
{"to-rate", 1, NULL, ARG_TO_SAMPLERATE},
|
||
|
|
{"frequency", 1, NULL, ARG_FREQUENCY},
|
||
|
|
{"samples", 1, NULL, ARG_SAMPLES},
|
||
|
|
{"rewind", 1, NULL, ARG_REWIND},
|
||
|
|
{"resample-method", 1, NULL, ARG_RESAMPLE_METHOD},
|
||
|
|
{"dump-resample-methods", 0, NULL, ARG_DUMP_RESAMPLE_METHODS},
|
||
|
|
{NULL, 0, NULL, 0}
|
||
|
|
};
|
||
|
|
|
||
|
|
setlocale(LC_ALL, "");
|
||
|
|
#ifdef ENABLE_NLS
|
||
|
|
bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
pa_log_set_level(PA_LOG_WARN);
|
||
|
|
if (!getenv("MAKE_CHECK"))
|
||
|
|
pa_log_set_level(PA_LOG_INFO);
|
||
|
|
|
||
|
|
a.channels = b.channels = 1;
|
||
|
|
a.rate = 48000;
|
||
|
|
b.rate = 44100;
|
||
|
|
a.format = b.format = PA_SAMPLE_FLOAT32NE;
|
||
|
|
|
||
|
|
method = PA_RESAMPLER_AUTO;
|
||
|
|
frequency = 1000;
|
||
|
|
samples = 5000;
|
||
|
|
rewind = 2500;
|
||
|
|
|
||
|
|
while ((c = getopt_long(argc, argv, "hv", long_options, NULL)) != -1) {
|
||
|
|
|
||
|
|
switch (c) {
|
||
|
|
case 'h' :
|
||
|
|
help(argv[0]);
|
||
|
|
ret = 0;
|
||
|
|
goto quit;
|
||
|
|
|
||
|
|
case 'v':
|
||
|
|
pa_log_set_level(PA_LOG_DEBUG);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case ARG_VERSION:
|
||
|
|
printf("%s %s\n", argv[0], PACKAGE_VERSION);
|
||
|
|
ret = 0;
|
||
|
|
goto quit;
|
||
|
|
|
||
|
|
case ARG_DUMP_RESAMPLE_METHODS:
|
||
|
|
dump_resample_methods();
|
||
|
|
ret = 0;
|
||
|
|
goto quit;
|
||
|
|
|
||
|
|
case ARG_FROM_SAMPLERATE:
|
||
|
|
a.rate = (uint32_t) atoi(optarg);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case ARG_TO_SAMPLERATE:
|
||
|
|
b.rate = (uint32_t) atoi(optarg);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case ARG_FREQUENCY:
|
||
|
|
frequency = (unsigned) atoi(optarg);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case ARG_SAMPLES:
|
||
|
|
samples = (unsigned) atoi(optarg);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case ARG_REWIND:
|
||
|
|
rewind = (unsigned) atoi(optarg);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case ARG_RESAMPLE_METHOD:
|
||
|
|
if (*optarg == '\0' || pa_streq(optarg, "help")) {
|
||
|
|
dump_resample_methods();
|
||
|
|
ret = 0;
|
||
|
|
goto quit;
|
||
|
|
}
|
||
|
|
method = pa_parse_resample_method(optarg);
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
goto quit;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pa_log_info("=== Square wave %u Hz, %u samples. Resampling using %s from %u Hz to %u Hz, rewinding %u output samples.", frequency,
|
||
|
|
samples, pa_resample_method_to_string(method), a.rate, b.rate, rewind);
|
||
|
|
|
||
|
|
ret = 0;
|
||
|
|
pa_assert_se(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true));
|
||
|
|
|
||
|
|
pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS);
|
||
|
|
|
||
|
|
/* Setup resampler */
|
||
|
|
ts = pa_rtclock_now();
|
||
|
|
pa_assert_se(resampler = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
|
||
|
|
pa_log_info("Init took %llu usec", (long long unsigned)(pa_rtclock_now() - ts));
|
||
|
|
|
||
|
|
/* Generate input data */
|
||
|
|
in_chunk.memblock = generate_block(pool, &a, frequency, 0.5, samples);
|
||
|
|
in_chunk.length = pa_memblock_get_length(in_chunk.memblock);
|
||
|
|
in_chunk.index = 0;
|
||
|
|
in_frame_size = pa_frame_size(&a);
|
||
|
|
|
||
|
|
/* First, resample the full block */
|
||
|
|
ts = pa_rtclock_now();
|
||
|
|
pa_resampler_run(resampler, &in_chunk, &out_chunk);
|
||
|
|
if (!out_chunk.memblock) {
|
||
|
|
pa_memblock_unref(in_chunk.memblock);
|
||
|
|
pa_log_warn("Resampling did not return any output data");
|
||
|
|
ret = 1;
|
||
|
|
goto quit;
|
||
|
|
}
|
||
|
|
|
||
|
|
pa_log_info("resampling took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
|
||
|
|
if (rewind > out_chunk.length / pa_frame_size(&b)) {
|
||
|
|
pa_log_warn("Specified number of frames to rewind (%u) larger than number of output frames (%lu), aborting.", rewind, out_chunk.length / pa_frame_size(&b));
|
||
|
|
ret = 1;
|
||
|
|
goto quit1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Get delay after first resampling pass */
|
||
|
|
delay_before = pa_resampler_get_delay(resampler, true);
|
||
|
|
|
||
|
|
/* Create and prepare history queue */
|
||
|
|
silence_chunk.memblock = silence_memblock_new(pool, 0);
|
||
|
|
silence_chunk.length = pa_frame_align(pa_memblock_get_length(silence_chunk.memblock), &a);
|
||
|
|
silence_chunk.index = 0;
|
||
|
|
history_queue = pa_memblockq_new("Test-Queue", 0, MEMBLOCKQ_MAXLENGTH, 0, &a, 0, 1, samples * in_frame_size, &silence_chunk);
|
||
|
|
pa_memblock_unref(silence_chunk.memblock);
|
||
|
|
|
||
|
|
pa_memblockq_push(history_queue, &in_chunk);
|
||
|
|
pa_memblockq_drop(history_queue, samples * in_frame_size);
|
||
|
|
|
||
|
|
in_rewind_size = pa_resampler_request(resampler, rewind * pa_frame_size(&b));
|
||
|
|
out_rewind_size = rewind * pa_frame_size(&b);
|
||
|
|
pa_log_debug("Have to rewind %lu input frames", in_rewind_size / in_frame_size);
|
||
|
|
ts = pa_rtclock_now();
|
||
|
|
|
||
|
|
/* Now rewind the resampler */
|
||
|
|
pa_memblockq_rewind(history_queue, in_rewind_size);
|
||
|
|
history_size = calculate_resampler_history_bytes(resampler, in_rewind_size / in_frame_size);
|
||
|
|
pa_log_debug("History is %lu frames.", history_size / in_frame_size);
|
||
|
|
pa_resampler_rewind(resampler, out_rewind_size, history_queue, history_size);
|
||
|
|
|
||
|
|
pa_log_info("Rewind took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
|
||
|
|
ts = pa_rtclock_now();
|
||
|
|
|
||
|
|
/* Re-run the resampler */
|
||
|
|
old_length = in_chunk.length;
|
||
|
|
in_chunk.length = in_rewind_size;
|
||
|
|
in_chunk.index = old_length - in_chunk.length;
|
||
|
|
pa_resampler_run(resampler, &in_chunk, &rewound_chunk);
|
||
|
|
if (!rewound_chunk.memblock) {
|
||
|
|
pa_log_warn("Resampler did not return output data for rewind");
|
||
|
|
ret = 1;
|
||
|
|
goto quit1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Get delay after rewind */
|
||
|
|
delay_after = pa_resampler_get_delay(resampler, true);
|
||
|
|
|
||
|
|
/* Calculate expected delay */
|
||
|
|
n_out_expected = pa_resampler_result(resampler, in_rewind_size + history_size) / pa_frame_size(&b);
|
||
|
|
delay_expected = delay_before + (double)(in_rewind_size + history_size) / (double)in_frame_size - n_out_expected * (double)a.rate / (double)b.rate;
|
||
|
|
|
||
|
|
/* Check for leftover samples in the resampler buffer */
|
||
|
|
in_resampler_buffer = lround((delay_after - delay_expected) * (double)b.rate / (double)a.rate);
|
||
|
|
if (in_resampler_buffer != 0) {
|
||
|
|
pa_log_debug("%li output frames still in resampler buffer", in_resampler_buffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
pa_log_info("Second resampler run took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
|
||
|
|
pa_log_debug("Got %lu output frames", rewound_chunk.length / pa_frame_size(&b));
|
||
|
|
old_length = out_chunk.length;
|
||
|
|
out_chunk.length = rewound_chunk.length;
|
||
|
|
out_chunk.index = old_length - out_chunk.length;
|
||
|
|
|
||
|
|
max_diff = compare_blocks(&b, &out_chunk, &rewound_chunk);
|
||
|
|
pa_log_info("Maximum difference is %.*g", 6, max_diff);
|
||
|
|
|
||
|
|
pa_memblock_unref(rewound_chunk.memblock);
|
||
|
|
|
||
|
|
quit1:
|
||
|
|
pa_memblock_unref(in_chunk.memblock);
|
||
|
|
pa_memblock_unref(out_chunk.memblock);
|
||
|
|
|
||
|
|
pa_resampler_free(resampler);
|
||
|
|
if (history_queue)
|
||
|
|
pa_memblockq_free(history_queue);
|
||
|
|
|
||
|
|
quit:
|
||
|
|
if (pool)
|
||
|
|
pa_mempool_unref(pool);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|