mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-31 22:25:38 -04:00
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.
200 lines
4.8 KiB
C
200 lines
4.8 KiB
C
/* 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;
|
|
}
|