mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
resampler: Precompute some common filter coefficients
While this is quite fast on x86 (order of a few microseconds), the computation can take a few milliseconds on ARM (measured at 1.9ms (32000 -> 48000) and 3.3ms (32000 -> 44100) on a Cortex A53). Let's precompute some common rates so that we can avoid this overhead on each stream (or any other audioconvert) instantiation. The approach taken here is to write a little program to create the resampler instance, and run that on the host at compile-time to generate some common rate conversions.
This commit is contained in:
parent
46f89d8009
commit
70a7bae5d7
5 changed files with 260 additions and 1 deletions
|
|
@ -50,6 +50,7 @@ pipewire_confdatadir = pipewire_datadir / 'pipewire'
|
|||
modules_install_dir = pipewire_libdir / pipewire_name
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
cc_native = meson.get_compiler('c', native: true)
|
||||
|
||||
if cc.has_header('features.h') and cc.get_define('__GLIBC__', prefix: '#include <features.h>') != ''
|
||||
# glibc ld.so interprets ${LIB} in a library loading path with an
|
||||
|
|
@ -112,6 +113,8 @@ cc_flags = common_flags + [
|
|||
]
|
||||
add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c')
|
||||
|
||||
cc_flags_native = cc_native.get_supported_arguments(cc_flags)
|
||||
|
||||
have_cpp = add_languages('cpp', native: false, required : false)
|
||||
|
||||
if have_cpp
|
||||
|
|
@ -276,6 +279,7 @@ configure_file(input : 'Makefile.in',
|
|||
|
||||
# Find dependencies
|
||||
mathlib = cc.find_library('m', required : false)
|
||||
mathlib_native = cc_native.find_library('m', required : false)
|
||||
rt_lib = cc.find_library('rt', required : false) # clock_gettime
|
||||
dl_lib = cc.find_library('dl', required : false)
|
||||
pthread_lib = dependency('threads')
|
||||
|
|
|
|||
|
|
@ -84,6 +84,10 @@ option('audioconvert',
|
|||
description: 'Enable audioconvert spa plugin integration',
|
||||
type: 'feature',
|
||||
value: 'enabled')
|
||||
option('resampler-precomp-tuples',
|
||||
description: 'Array of "inrate,outrate[,quality]" tuples to precompute resampler coefficients for',
|
||||
type: 'array',
|
||||
value: [ '32000,44100', '32000,48000', '48000,44100', '44100,48000' ])
|
||||
option('bluez5',
|
||||
description: 'Enable bluez5 spa plugin integration',
|
||||
type: 'feature',
|
||||
|
|
|
|||
|
|
@ -105,10 +105,40 @@ if have_neon
|
|||
simd_dependencies += audioconvert_neon
|
||||
endif
|
||||
|
||||
sparesampledumpcoeffs_sources = [
|
||||
'resample-native.c',
|
||||
'resample-native-c.c',
|
||||
'spa-resample-dump-coeffs.c',
|
||||
]
|
||||
|
||||
sparesampledumpcoeffs = executable(
|
||||
'spa-resample-dump-coeffs',
|
||||
sparesampledumpcoeffs_sources,
|
||||
c_args : [ cc_flags_native, '-DRESAMPLE_DISABLE_PRECOMP' ],
|
||||
dependencies : [ spa_dep, mathlib_native ],
|
||||
install : false,
|
||||
native : true,
|
||||
)
|
||||
|
||||
precomptuples = []
|
||||
foreach tuple : get_option('resampler-precomp-tuples')
|
||||
precomptuples += '-t ' + tuple
|
||||
endforeach
|
||||
|
||||
resample_native_precomp_h = custom_target(
|
||||
'resample-native-precomp.h',
|
||||
output : 'resample-native-precomp.h',
|
||||
capture : true,
|
||||
command : [
|
||||
sparesampledumpcoeffs,
|
||||
] + precomptuples
|
||||
)
|
||||
|
||||
audioconvert_lib = static_library('audioconvert',
|
||||
['fmt-ops.c',
|
||||
'channelmix-ops.c',
|
||||
'peaks-ops.c',
|
||||
resample_native_precomp_h,
|
||||
'resample-native.c',
|
||||
'resample-peaks.c',
|
||||
'wavfile.c',
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
#include <spa/param/audio/format.h>
|
||||
|
||||
#include "resample-native-impl.h"
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
#include "resample-native-precomp.h"
|
||||
#endif
|
||||
|
||||
struct quality {
|
||||
uint32_t n_taps;
|
||||
|
|
@ -375,7 +378,25 @@ int resample_native_init(struct resample *r)
|
|||
for (c = 0; c < r->channels; c++)
|
||||
d->history[c] = SPA_PTROFF(d->hist_mem, c * history_stride, float);
|
||||
|
||||
build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale);
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
/* See if we have precomputed coefficients */
|
||||
for (c = 0; precomp_coeffs[c].filter; c++) {
|
||||
if (precomp_coeffs[c].in_rate == r->i_rate &&
|
||||
precomp_coeffs[c].out_rate == r->o_rate &&
|
||||
precomp_coeffs[c].quality == r->quality)
|
||||
break;
|
||||
}
|
||||
|
||||
if (precomp_coeffs[c].filter) {
|
||||
spa_log_debug(r->log, "using precomputed filter for %u->%u(%u)",
|
||||
r->i_rate, r->o_rate, r->quality);
|
||||
spa_memcpy(d->filter, precomp_coeffs[c].filter, filter_size);
|
||||
} else {
|
||||
#endif
|
||||
build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale);
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
}
|
||||
#endif
|
||||
|
||||
d->info = find_resample_info(SPA_AUDIO_FORMAT_F32, r->cpu_flags);
|
||||
if (SPA_UNLIKELY(d->info == NULL)) {
|
||||
|
|
|
|||
200
spa/plugins/audioconvert/spa-resample-dump-coeffs.c
Normal file
200
spa/plugins/audioconvert/spa-resample-dump-coeffs.c
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/* Spa */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2024 Arun Raghavan <arun@asymptotic.io> */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include <spa/support/log-impl.h>
|
||||
#include <spa/utils/result.h>
|
||||
#include <spa/utils/string.h>
|
||||
|
||||
SPA_LOG_IMPL(logger);
|
||||
|
||||
#include "resample.h"
|
||||
#include "resample-native-impl.h"
|
||||
|
||||
#define OPTIONS "ht:"
|
||||
static const struct option long_options[] = {
|
||||
{ "help", no_argument, NULL, 'h'},
|
||||
|
||||
{ "tuple", required_argument, NULL, 't' },
|
||||
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void show_usage(const char *name, bool is_error)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = is_error ? stderr : stdout;
|
||||
|
||||
fprintf(fp, "%s [options]\n", name);
|
||||
fprintf(fp,
|
||||
" -h, --help Show this help\n"
|
||||
"\n"
|
||||
" -t --tuple Sample rate tuple (as \"in_rate,out_rate[,quality]\")\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
static void parse_tuple(const char *arg, int *in, int *out, int *quality)
|
||||
{
|
||||
char tuple[256];
|
||||
char *token;
|
||||
|
||||
strncpy(tuple, arg, sizeof(tuple) - 1);
|
||||
*in = 0;
|
||||
*out = 0;
|
||||
|
||||
token = strtok(tuple, ",");
|
||||
if (!token || !spa_atoi32(token, in, 10))
|
||||
return;
|
||||
|
||||
token = strtok(NULL, ",");
|
||||
if (!token || !spa_atoi32(token, out, 10))
|
||||
return;
|
||||
|
||||
token = strtok(NULL, ",");
|
||||
if (!token) {
|
||||
*quality = RESAMPLE_DEFAULT_QUALITY;
|
||||
} else if (!spa_atoi32(token, quality, 10)) {
|
||||
*quality = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* first, second now contain zeroes on error, or the numbers on success,
|
||||
* third contains a quality or -1 on error, default value if unspecified */
|
||||
}
|
||||
|
||||
#define PREFIX "__precomp_coeff"
|
||||
|
||||
static void dump_header(void)
|
||||
{
|
||||
printf("/* This is a generated file, see spa-resample-dump-coeffs.c */");
|
||||
printf("\n#include <stdint.h>\n");
|
||||
printf("\n#include <stdlib.h>\n");
|
||||
printf("\n");
|
||||
printf("struct resample_coeffs {\n");
|
||||
printf("\tuint32_t in_rate;\n");
|
||||
printf("\tuint32_t out_rate;\n");
|
||||
printf("\tint quality;\n");
|
||||
printf("\tconst float *filter;\n");
|
||||
printf("};\n");
|
||||
}
|
||||
|
||||
static void dump_footer(const uint32_t *ins, const uint32_t *outs, const int *qualities)
|
||||
{
|
||||
printf("\n");
|
||||
printf("static const struct resample_coeffs precomp_coeffs[] = {\n");
|
||||
while (*ins && *outs) {
|
||||
printf("\t{ .in_rate = %u, .out_rate = %u, .quality = %u, "
|
||||
".filter = %s_%u_%u_%u },\n",
|
||||
*ins, *outs, *qualities, PREFIX, *ins, *outs, *qualities);
|
||||
ins++;
|
||||
outs++;
|
||||
qualities++;
|
||||
}
|
||||
printf("\t{ .in_rate = 0, .out_rate = 0, .quality = 0, .filter = NULL },\n");
|
||||
printf("};\n");
|
||||
}
|
||||
|
||||
static void dump_coeffs(unsigned int in_rate, unsigned int out_rate, int quality)
|
||||
{
|
||||
struct resample r = { 0, };
|
||||
struct native_data *d;
|
||||
unsigned int i, filter_size;
|
||||
int ret;
|
||||
|
||||
r.log = &logger.log;
|
||||
r.i_rate = in_rate;
|
||||
r.o_rate = out_rate;
|
||||
r.quality = quality;
|
||||
r.channels = 1; /* irrelevant for generated taps */
|
||||
|
||||
if ((ret = resample_native_init(&r)) < 0) {
|
||||
fprintf(stderr, "can't init converter: %s\n", spa_strerror(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
d = r.data;
|
||||
filter_size = d->filter_stride * (d->n_phases + 1);
|
||||
|
||||
printf("\n");
|
||||
printf("static const float %s_%u_%u_%u[] = {", PREFIX, in_rate, out_rate, quality);
|
||||
for (i = 0; i < filter_size; i++) {
|
||||
printf("%a", d->filter[i]);
|
||||
if (i != filter_size - 1)
|
||||
printf(",");
|
||||
}
|
||||
printf("};\n");
|
||||
|
||||
if (r.free)
|
||||
r.free(&r);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
unsigned int ins[256] = { 0, }, outs[256] = { 0, };
|
||||
int qualities[256] = { 0, };
|
||||
int in_rate = 0, out_rate = 0, quality = 0;
|
||||
int c, longopt_index = 0, i = 0;
|
||||
|
||||
while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
show_usage(argv[0], false);
|
||||
return EXIT_SUCCESS;
|
||||
case 't':
|
||||
parse_tuple(optarg, &in_rate, &out_rate, &quality);
|
||||
if (in_rate <= 0) {
|
||||
fprintf(stderr, "error: bad input rate %d\n", in_rate);
|
||||
goto error;
|
||||
}
|
||||
if (out_rate <= 0) {
|
||||
fprintf(stderr, "error: bad output rate %d\n", out_rate);
|
||||
goto error;
|
||||
}
|
||||
if (quality < 0 || quality > 14) {
|
||||
fprintf(stderr, "error: bad quality value %s\n", optarg);
|
||||
goto error;
|
||||
}
|
||||
ins[i] = in_rate;
|
||||
outs[i] = out_rate;
|
||||
qualities[i] = quality;
|
||||
i++;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "error: unknown option\n");
|
||||
goto error_usage;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc) {
|
||||
fprintf(stderr, "error: got %d extra argument(s))\n",
|
||||
optind - argc);
|
||||
goto error_usage;
|
||||
}
|
||||
if (in_rate == 0) {
|
||||
fprintf(stderr, "error: input rate must be specified\n");
|
||||
goto error;
|
||||
}
|
||||
if (out_rate == 0) {
|
||||
fprintf(stderr, "error: input rate must be specified\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
dump_header();
|
||||
while (i--) {
|
||||
dump_coeffs(ins[i], outs[i], qualities[i]);
|
||||
}
|
||||
dump_footer(ins, outs, qualities);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
error_usage:
|
||||
show_usage(argv[0], true);
|
||||
error:
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue