pulseaudio/src/tests/resampler-test.c

480 lines
14 KiB
C
Raw Normal View History

/***
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>
2011-04-19 13:29:19 +03:00
#include <getopt.h>
#include <locale.h>
2011-04-19 13:29:19 +03:00
#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/sample-util.h>
2011-04-19 13:29:19 +03:00
#include <pulsecore/core-util.h>
static void dump_block(const char *label, const pa_sample_spec *ss, const pa_memchunk *chunk) {
void *d;
unsigned i;
if (getenv("MAKE_CHECK"))
return;
printf("%s: \t", label);
d = pa_memblock_acquire(chunk->memblock);
switch (ss->format) {
case PA_SAMPLE_U8:
case PA_SAMPLE_ULAW:
case PA_SAMPLE_ALAW: {
uint8_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
printf(" 0x%02x ", *(u++));
break;
}
case PA_SAMPLE_S16NE:
case PA_SAMPLE_S16RE: {
uint16_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
printf(" 0x%04x ", *(u++));
break;
}
case PA_SAMPLE_S32NE:
case PA_SAMPLE_S32RE: {
uint32_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
printf("0x%08x ", *(u++));
break;
}
case PA_SAMPLE_S24_32NE:
case PA_SAMPLE_S24_32RE: {
uint32_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
printf("0x%08x ", *(u++));
break;
}
case PA_SAMPLE_FLOAT32NE:
case PA_SAMPLE_FLOAT32RE: {
float *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
endianmacros: Replace borked PA_FLOAT32_SWAP() with PA_READ_FLOAT32RE() / PA_WRITE_FLOAT32RE() building PA with -O0 leads to test failure in mix-test on i386 issue reported by Felipe, see http://lists.freedesktop.org/archives/pulseaudio-discuss/2014-August/021406.html the problem is the value 0xbeffbd7f: when byte-swapped it becomes 0x7fbdffbe and according to IEEE-754 represents a signalling NaN (starting with s111 1111 10, see http://en.wikipedia.org/wiki/NaN) when this value is assigned to a floating point register, it becomes 0x7ffdffbe, representing a quiet NaN (starting with s111 1111 11) -- a signalling NaN is turned into a quiet NaN! so PA_FLOAT32_SWAP(PA_FLOAT32_SWAP(x)) != x for certain values, uhuh! the following test code can be used; due to volatile, it will always demonstrate the issue; without volatile, it depends on the optimization level (i386, 32-bit, gcc 4.9): // snip static inline float PA_FLOAT32_SWAP(float x) { union { float f; uint32_t u; } t; t.f = x; t.u = bswap_32(t.u); return t.f; } int main() { unsigned x = 0xbeffbd7f; volatile float f = PA_FLOAT32_SWAP(*(float *)&x); printf("%08x %08x %08x %f\n", 0xbeffbd7f, *(unsigned *)&f, bswap_32(*(unsigned *)&f), f); } // snip the problem goes away with optimization when no temporary floating point registers are used the proposed solution is to avoid passing swapped floating point data in a float; this is done with new functions PA_READ_FLOAT32RE() and PA_WRITE_FLOAT32RE() which use uint32_t to dereference a pointer and byte-swap the data, hence no temporary float variable is used also delete PA_FLOAT32_TO_LE()/_BE(), not used Signed-off-by: Peter Meerwald <pmeerw@pmeerw.net> Reported-by: Felipe Sateler <fsateler@debian.org>
2014-09-02 23:53:09 +02:00
printf("%4.3g ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : PA_READ_FLOAT32RE(u));
u++;
}
break;
}
case PA_SAMPLE_S24LE:
case PA_SAMPLE_S24BE: {
uint8_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
printf(" 0x%06x ", PA_READ24NE(u));
u += pa_frame_size(ss);
}
break;
}
default:
pa_assert_not_reached();
}
printf("\n");
pa_memblock_release(chunk->memblock);
}
static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) {
pa_memblock *r;
void *d;
unsigned i;
pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * 10));
d = pa_memblock_acquire(r);
switch (ss->format) {
case PA_SAMPLE_U8:
case PA_SAMPLE_ULAW:
case PA_SAMPLE_ALAW: {
uint8_t *u = d;
u[0] = 0x00;
u[1] = 0xFF;
u[2] = 0x7F;
u[3] = 0x80;
u[4] = 0x9f;
u[5] = 0x3f;
u[6] = 0x1;
u[7] = 0xF0;
u[8] = 0x20;
u[9] = 0x21;
break;
}
case PA_SAMPLE_S16NE:
case PA_SAMPLE_S16RE: {
uint16_t *u = d;
u[0] = 0x0000;
u[1] = 0xFFFF;
u[2] = 0x7FFF;
u[3] = 0x8000;
u[4] = 0x9fff;
u[5] = 0x3fff;
u[6] = 0x1;
u[7] = 0xF000;
u[8] = 0x20;
u[9] = 0x21;
break;
}
case PA_SAMPLE_S32NE:
case PA_SAMPLE_S32RE: {
uint32_t *u = d;
u[0] = 0x00000001;
u[1] = 0xFFFF0002;
u[2] = 0x7FFF0003;
u[3] = 0x80000004;
u[4] = 0x9fff0005;
u[5] = 0x3fff0006;
u[6] = 0x10007;
u[7] = 0xF0000008;
u[8] = 0x200009;
u[9] = 0x21000A;
break;
}
case PA_SAMPLE_S24_32NE:
case PA_SAMPLE_S24_32RE: {
uint32_t *u = d;
u[0] = 0x000001;
u[1] = 0xFF0002;
u[2] = 0x7F0003;
u[3] = 0x800004;
u[4] = 0x9f0005;
u[5] = 0x3f0006;
u[6] = 0x107;
u[7] = 0xF00008;
u[8] = 0x2009;
u[9] = 0x210A;
break;
}
case PA_SAMPLE_FLOAT32NE:
case PA_SAMPLE_FLOAT32RE: {
float *u = d;
u[0] = 0.0f;
u[1] = -1.0f;
u[2] = 1.0f;
u[3] = 4711.0f;
u[4] = 0.222f;
u[5] = 0.33f;
u[6] = -.3f;
u[7] = 99.0f;
u[8] = -0.555f;
u[9] = -.123f;
if (ss->format == PA_SAMPLE_FLOAT32RE)
for (i = 0; i < 10; i++)
endianmacros: Replace borked PA_FLOAT32_SWAP() with PA_READ_FLOAT32RE() / PA_WRITE_FLOAT32RE() building PA with -O0 leads to test failure in mix-test on i386 issue reported by Felipe, see http://lists.freedesktop.org/archives/pulseaudio-discuss/2014-August/021406.html the problem is the value 0xbeffbd7f: when byte-swapped it becomes 0x7fbdffbe and according to IEEE-754 represents a signalling NaN (starting with s111 1111 10, see http://en.wikipedia.org/wiki/NaN) when this value is assigned to a floating point register, it becomes 0x7ffdffbe, representing a quiet NaN (starting with s111 1111 11) -- a signalling NaN is turned into a quiet NaN! so PA_FLOAT32_SWAP(PA_FLOAT32_SWAP(x)) != x for certain values, uhuh! the following test code can be used; due to volatile, it will always demonstrate the issue; without volatile, it depends on the optimization level (i386, 32-bit, gcc 4.9): // snip static inline float PA_FLOAT32_SWAP(float x) { union { float f; uint32_t u; } t; t.f = x; t.u = bswap_32(t.u); return t.f; } int main() { unsigned x = 0xbeffbd7f; volatile float f = PA_FLOAT32_SWAP(*(float *)&x); printf("%08x %08x %08x %f\n", 0xbeffbd7f, *(unsigned *)&f, bswap_32(*(unsigned *)&f), f); } // snip the problem goes away with optimization when no temporary floating point registers are used the proposed solution is to avoid passing swapped floating point data in a float; this is done with new functions PA_READ_FLOAT32RE() and PA_WRITE_FLOAT32RE() which use uint32_t to dereference a pointer and byte-swap the data, hence no temporary float variable is used also delete PA_FLOAT32_TO_LE()/_BE(), not used Signed-off-by: Peter Meerwald <pmeerw@pmeerw.net> Reported-by: Felipe Sateler <fsateler@debian.org>
2014-09-02 23:53:09 +02:00
PA_WRITE_FLOAT32RE(&u[i], u[i]);
break;
}
case PA_SAMPLE_S24NE:
case PA_SAMPLE_S24RE: {
uint8_t *u = d;
PA_WRITE24NE(u, 0x000001);
PA_WRITE24NE(u+3, 0xFF0002);
PA_WRITE24NE(u+6, 0x7F0003);
PA_WRITE24NE(u+9, 0x800004);
PA_WRITE24NE(u+12, 0x9f0005);
PA_WRITE24NE(u+15, 0x3f0006);
PA_WRITE24NE(u+18, 0x107);
PA_WRITE24NE(u+21, 0xF00008);
PA_WRITE24NE(u+24, 0x2009);
PA_WRITE24NE(u+27, 0x210A);
break;
}
default:
pa_assert_not_reached();
}
pa_memblock_release(r);
return r;
}
2011-04-19 13:29:19 +03:00
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"
" --from-format=SAMPLEFORMAT From sample type (defaults to s16le)\n"
" --from-channels=CHANNELS From number of channels (defaults to 1)\n"
" --to-rate=SAMPLERATE To sample rate in Hz (defaults to 44100)\n"
" --to-format=SAMPLEFORMAT To sample type (defaults to s16le)\n"
" --to-channels=CHANNELS To number of channels (defaults to 1)\n"
" --resample-method=METHOD Resample method (defaults to auto)\n"
" --seconds=SECONDS From stream duration (defaults to 60)\n"
"\n"
"If the formats are not specified, the test performs all formats combinations,\n"
"back and forth.\n"
"\n"
"Sample type must be one of s16le, s16be, u8, float32le, float32be, ulaw, alaw,\n"
"s24le, s24be, s24-32le, s24-32be, s32le, s32be (defaults to s16ne)\n"
"\n"
"See --dump-resample-methods for possible values of resample methods.\n",
argv0);
2011-04-19 13:29:19 +03:00
}
enum {
ARG_VERSION = 256,
ARG_FROM_SAMPLERATE,
ARG_FROM_SAMPLEFORMAT,
ARG_FROM_CHANNELS,
ARG_TO_SAMPLERATE,
ARG_TO_SAMPLEFORMAT,
ARG_TO_CHANNELS,
ARG_SECONDS,
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[]) {
2011-04-19 13:29:19 +03:00
pa_mempool *pool = NULL;
pa_sample_spec a, b;
int ret = 1, c;
bool all_formats = true;
2011-04-19 13:29:19 +03:00
pa_resample_method_t method;
int seconds;
unsigned crossover_freq = 120;
2011-04-19 13:29:19 +03:00
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},
{"from-format", 1, NULL, ARG_FROM_SAMPLEFORMAT},
{"from-channels", 1, NULL, ARG_FROM_CHANNELS},
{"to-rate", 1, NULL, ARG_TO_SAMPLERATE},
{"to-format", 1, NULL, ARG_TO_SAMPLEFORMAT},
{"to-channels", 1, NULL, ARG_TO_CHANNELS},
{"seconds", 1, NULL, ARG_SECONDS},
{"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
2011-04-19 13:29:19 +03:00
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 = b.rate = 44100;
2011-04-19 13:29:19 +03:00
a.format = b.format = PA_SAMPLE_S16LE;
2011-04-19 13:29:19 +03:00
method = PA_RESAMPLER_AUTO;
seconds = 60;
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);
2011-04-19 13:29:19 +03:00
ret = 0;
goto quit;
case ARG_DUMP_RESAMPLE_METHODS:
dump_resample_methods();
ret = 0;
goto quit;
case ARG_FROM_CHANNELS:
a.channels = (uint8_t) atoi(optarg);
break;
case ARG_FROM_SAMPLEFORMAT:
a.format = pa_parse_sample_format(optarg);
all_formats = false;
2011-04-19 13:29:19 +03:00
break;
case ARG_FROM_SAMPLERATE:
a.rate = (uint32_t) atoi(optarg);
break;
case ARG_TO_CHANNELS:
b.channels = (uint8_t) atoi(optarg);
break;
case ARG_TO_SAMPLEFORMAT:
b.format = pa_parse_sample_format(optarg);
all_formats = false;
2011-04-19 13:29:19 +03:00
break;
case ARG_TO_SAMPLERATE:
b.rate = (uint32_t) atoi(optarg);
break;
case ARG_SECONDS:
seconds = 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;
}
}
ret = 0;
pa_assert_se(pool = pa_mempool_new(false, 0));
2011-04-19 13:29:19 +03:00
if (!all_formats) {
pa_resampler *resampler;
pa_memchunk i, j;
pa_usec_t ts;
pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS);
pa_log_debug("=== %d seconds: %d Hz %d ch (%s) -> %d Hz %d ch (%s)", seconds,
2011-04-19 13:29:19 +03:00
a.rate, a.channels, pa_sample_format_to_string(a.format),
b.rate, b.channels, pa_sample_format_to_string(b.format));
ts = pa_rtclock_now();
pa_assert_se(resampler = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
pa_log_info("init: %llu", (long long unsigned)(pa_rtclock_now() - ts));
2011-04-19 13:29:19 +03:00
i.memblock = pa_memblock_new(pool, pa_usec_to_bytes(1*PA_USEC_PER_SEC, &a));
2011-04-19 13:29:19 +03:00
ts = pa_rtclock_now();
i.length = pa_memblock_get_length(i.memblock);
i.index = 0;
while (seconds--) {
pa_resampler_run(resampler, &i, &j);
if (j.memblock)
pa_memblock_unref(j.memblock);
2011-04-19 13:29:19 +03:00
}
pa_log_info("resampling: %llu", (long long unsigned)(pa_rtclock_now() - ts));
2011-04-19 13:29:19 +03:00
pa_memblock_unref(i.memblock);
pa_resampler_free(resampler);
goto quit;
}
for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) {
for (b.format = 0; b.format < PA_SAMPLE_MAX; b.format ++) {
pa_resampler *forth, *back;
pa_memchunk i, j, k;
pa_log_debug("=== %s -> %s -> %s -> /2",
2011-04-19 13:29:19 +03:00
pa_sample_format_to_string(a.format),
pa_sample_format_to_string(b.format),
pa_sample_format_to_string(a.format));
pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, crossover_freq, method, 0));
i.memblock = generate_block(pool, &a);
i.length = pa_memblock_get_length(i.memblock);
i.index = 0;
pa_resampler_run(forth, &i, &j);
pa_resampler_run(back, &j, &k);
dump_block("before", &a, &i);
dump_block("after", &b, &j);
dump_block("reverse", &a, &k);
pa_memblock_unref(i.memblock);
pa_memblock_unref(j.memblock);
pa_memblock_unref(k.memblock);
pa_resampler_free(forth);
pa_resampler_free(back);
}
}
2011-04-19 13:29:19 +03:00
quit:
if (pool)
pa_mempool_free(pool);
2011-04-19 13:29:19 +03:00
return ret;
}