/* Spa * * Copyright © 2020 Wim Taymans * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); #include "resample.h" #define DEFAULT_QUALITY RESAMPLE_DEFAULT_QUALITY #define MAX_SAMPLES 4096u struct data { bool verbose; int rate; int format; int quality; int cpu_flags; const char *iname; SF_INFO iinfo; SNDFILE *ifile; const char *oname; SF_INFO oinfo; SNDFILE *ofile; }; #define STR_FMTS "(s8|s16|s32|f32|f64)" #define OPTIONS "hvr:f:q:c:" static const struct option long_options[] = { { "help", no_argument, NULL, 'h'}, { "verbose", no_argument, NULL, 'v'}, { "rate", required_argument, NULL, 'r' }, { "format", required_argument, NULL, 'f' }, { "quality", required_argument, NULL, 'q' }, { "cpuflags", required_argument, NULL, 'c' }, { 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" " -v --verbose Be verbose\n" "\n"); fprintf(fp, " -r --rate Output sample rate (default as input)\n" " -f --format Output sample format %s (default as input)\n" " -q --quality Resampler quality (default %u)\n" " -c --cpuflags CPU flags (default 0)\n" "\n", STR_FMTS, DEFAULT_QUALITY); } static inline const char * sf_fmt_to_str(int fmt) { switch(fmt & SF_FORMAT_SUBMASK) { case SF_FORMAT_PCM_S8: return "s8"; case SF_FORMAT_PCM_16: return "s16"; case SF_FORMAT_PCM_24: return "s24"; case SF_FORMAT_PCM_32: return "s32"; case SF_FORMAT_FLOAT: return "f32"; case SF_FORMAT_DOUBLE: return "f64"; default: return "unknown"; } } static inline int sf_str_to_fmt(const char *str) { if (!str) return -1; if (spa_streq(str, "s8")) return SF_FORMAT_PCM_S8; if (spa_streq(str, "s16")) return SF_FORMAT_PCM_16; if (spa_streq(str, "s24")) return SF_FORMAT_PCM_24; if (spa_streq(str, "s32")) return SF_FORMAT_PCM_32; if (spa_streq(str, "f32")) return SF_FORMAT_FLOAT; if (spa_streq(str, "f64")) return SF_FORMAT_DOUBLE; return -1; } static int open_files(struct data *d) { d->ifile = sf_open(d->iname, SFM_READ, &d->iinfo); if (d->ifile == NULL) { fprintf(stderr, "error: failed to open input file \"%s\": %s\n", d->iname, sf_strerror(NULL)); return -EIO; } d->oinfo.channels = d->iinfo.channels; d->oinfo.samplerate = d->rate > 0 ? d->rate : d->iinfo.samplerate; d->oinfo.format = d->format > 0 ? d->format : d->iinfo.format; d->oinfo.format |= SF_FORMAT_WAV; d->ofile = sf_open(d->oname, SFM_WRITE, &d->oinfo); if (d->ofile == NULL) { fprintf(stderr, "error: failed to open output file \"%s\": %s\n", d->oname, sf_strerror(NULL)); return -EIO; } if (d->verbose) { fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n", d->iname, d->iinfo.channels, d->iinfo.samplerate, sf_fmt_to_str(d->iinfo.format)); fprintf(stdout, "output '%s': channels:%d rate:%d format:%s\n", d->oname, d->oinfo.channels, d->oinfo.samplerate, sf_fmt_to_str(d->oinfo.format)); } return 0; } static int close_files(struct data *d) { if (d->ifile) sf_close(d->ifile); if (d->ofile) sf_close(d->ofile); return 0; } static int do_conversion(struct data *d) { struct resample r; int channels = d->iinfo.channels; float in[MAX_SAMPLES * channels]; float out[MAX_SAMPLES * channels]; float ibuf[MAX_SAMPLES * channels]; float obuf[MAX_SAMPLES * channels]; uint32_t in_len, out_len, queued; uint32_t pin_len, pout_len; size_t read, written; const void *src[channels]; void *dst[channels]; uint32_t i; int res, j, k; uint32_t flushing = UINT32_MAX; spa_zero(r); r.cpu_flags = d->cpu_flags; r.log = &logger.log; r.channels = channels; r.i_rate = d->iinfo.samplerate; r.o_rate = d->oinfo.samplerate; r.quality = d->quality < 0 ? DEFAULT_QUALITY : d->quality; if ((res = resample_native_init(&r)) < 0) { fprintf(stderr, "can't init converter: %s\n", spa_strerror(res)); return res; } for (j = 0; j < channels; j++) src[j] = &in[MAX_SAMPLES * j]; for (j = 0; j < channels; j++) dst[j] = &out[MAX_SAMPLES * j]; read = written = queued = 0; while (true) { pout_len = out_len = MAX_SAMPLES; in_len = SPA_MIN(MAX_SAMPLES, resample_in_len(&r, out_len)); in_len -= SPA_MIN(queued, in_len); if (in_len > 0) { pin_len = in_len = sf_readf_float(d->ifile, &ibuf[queued * channels], in_len); read += pin_len; if (pin_len == 0) { if (flushing == 0) break; if (flushing == UINT32_MAX) flushing = resample_delay(&r); pin_len = in_len = SPA_MIN(MAX_SAMPLES, flushing); flushing -= in_len; for (k = 0, i = 0; i < pin_len; i++) { for (j = 0; j < channels; j++) ibuf[k++] = 0.0; } } } in_len += queued; pin_len = in_len; for (k = 0, i = 0; i < pin_len; i++) { for (j = 0; j < channels; j++) { in[MAX_SAMPLES * j + i] = ibuf[k++]; } } resample_process(&r, src, &pin_len, dst, &pout_len); queued = in_len - pin_len; if (queued) memmove(ibuf, &ibuf[pin_len * channels], queued * channels * sizeof(float)); if (pout_len > 0) { for (k = 0, i = 0; i < pout_len; i++) { for (j = 0; j < channels; j++) { obuf[k++] = out[MAX_SAMPLES * j + i]; } } pout_len = sf_writef_float(d->ofile, obuf, pout_len); written += pout_len; } } if (d->verbose) fprintf(stdout, "read %zu samples, wrote %zu samples\n", read, written); return 0; } int main(int argc, char *argv[]) { int c; int longopt_index = 0, ret; struct data data; spa_zero(data); logger.log.level = SPA_LOG_LEVEL_DEBUG; data.quality = -1; 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 'v': data.verbose = true; break; case 'r': ret = atoi(optarg); if (ret <= 0) { fprintf(stderr, "error: bad rate %s\n", optarg); goto error_usage; } data.rate = ret; break; case 'f': ret = sf_str_to_fmt(optarg); if (ret < 0) { fprintf(stderr, "error: bad format %s\n", optarg); goto error_usage; } data.format = ret; break; case 'q': ret = atoi(optarg); if (ret < 0) { fprintf(stderr, "error: bad quality %s\n", optarg); goto error_usage; } data.quality = ret; break; case 'c': data.cpu_flags = strtol(optarg, NULL, 0); break; default: fprintf(stderr, "error: unknown option '%c'\n", c); goto error_usage; } } if (optind + 1 >= argc) { fprintf(stderr, "error: filename arguments missing (%d %d)\n", optind, argc); goto error_usage; } data.iname = argv[optind++]; data.oname = argv[optind++]; if (open_files(&data) < 0) return EXIT_FAILURE; do_conversion(&data); close_files(&data); return 0; error_usage: show_usage(argv[0], true); return EXIT_FAILURE; }