Files
UltraGrid/src/audio/resampler.cpp
Ben Roeder a62fe80c3f Fix spelling errors throughout codebase
Corrected various spelling mistakes in comments, documentation, and
variable names across the project. Changes include:
- Documentation files (CONTRIBUTING.md, README.md, etc.)
- Source code comments in C/C++ files
- Function parameter names and descriptions

No functional changes were made.
2025-06-01 18:03:40 +01:00

424 lines
18 KiB
C++

/**
* @file audio/resampler.cpp
* @author Martin Pulec <pulec@cesnet.cz>
* @author Andrew Walker <andrew.walker@sohonet.com>
*/
/*
* Copyright (c) 2011-2022 CESNET, z. s. p. o.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of CESNET nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#endif // HAVE_CONFIG_H
#include "audio/resampler.hpp"
#include "audio/types.h"
#include "audio/utils.h"
#include "debug.h"
#include "host.h"
#include "ug_runtime_error.hpp"
#include "utils/macros.h"
#include "utils/string_view_utils.hpp"
#include "utils/worker.h"
#ifdef HAVE_SPEEXDSP
#include <speex/speex_resampler.h>
#endif // HAVE_SPEEXDSP
#ifdef HAVE_SOXR
#include <soxr.h>
#endif // HAVE_SOXR
#define DEFAULT_SPEEX_RESAMPLE_QUALITY 10 // in range [0,10] - 10 best
#define MOD_NAME "[audio_resampler] "
using namespace std;
class audio_frame2_resampler::impl {
public:
virtual std::tuple<bool, audio_frame2> resample(audio_frame2 &a, std::vector<audio_frame2::channel> &out, int new_sample_rate_num, int new_sample_rate_den) = 0;
/// @returns 0-terminated C array of supported BPS in _ascending_ (!) order
virtual const int *get_supported_bps() = 0;
virtual ~impl() {}
};
tuple<bool, audio_frame2> audio_frame2_resampler::resample(audio_frame2 &a, vector<audio_frame2::channel> &out, int new_sample_rate_num, int new_sample_rate_den)
{
if (!m_impl) {
LOG(LOG_LEVEL_ERROR) << "Audio frame resampler: cannot resample, Soxr/SpeexDSP was not compiled in!\n";
return { false, audio_frame2{} };
}
return m_impl->resample(a, out, new_sample_rate_num, new_sample_rate_den);
}
struct resample_prop {
unsigned rate_from{0};
unsigned rate_to_num{0};
unsigned rate_to_den{1};
unsigned ch_count{0};
unsigned bps{0};
};
#ifdef HAVE_SOXR
class soxr_resampler : public audio_frame2_resampler::impl {
public:
tuple<bool, audio_frame2> resample(audio_frame2 &a, vector<audio_frame2::channel> &new_channels, int new_sample_rate_num, int new_sample_rate_den) override;
const int *get_supported_bps() override {
static const int ret[] = { 2, 4, 0 };
return ret;
}
~soxr_resampler() {
if (resampler) {
soxr_delete(resampler);
}
}
private:
bool check_reconfigure(uint32_t original_sample_rate, uint32_t new_sample_rate_num, uint32_t new_sample_rate_den, size_t nb_channels, unsigned bps);
soxr_t resampler{nullptr};
struct resample_prop prop;
};
/**
* @brief This function will create (and destroy) a new resampler if needed.
*
* @param original_sample_rate The original sample rate in Hz
* @param new_sample_rate_num The numerator of the new sample rate
* @param new_sample_rate_den The denominator of the new sample rate
* @param nb_channels The number of channels that will be resampled
* @param bps The bit rate (in bytes) of the incoming audio
*
* @return true Successfully created the resampler
* @return false Initialisation of the resampler failed
*/
bool soxr_resampler::check_reconfigure(uint32_t original_sample_rate, uint32_t new_sample_rate_num, uint32_t new_sample_rate_den, size_t nb_channels, unsigned bps) {
if (resampler != nullptr && nb_channels == prop.ch_count && bps == prop.bps) {
if (original_sample_rate != prop.rate_from
|| new_sample_rate_num != prop.rate_to_num
|| new_sample_rate_den != prop.rate_to_den) {
// Update the resampler numerator and denomintors
prop.rate_from = original_sample_rate;
prop.rate_to_num = new_sample_rate_num;
prop.rate_to_den = new_sample_rate_den;
soxr_set_io_ratio(resampler, ((double)prop.rate_from / ((double)new_sample_rate_num / (double)new_sample_rate_den)), 0);
}
return true;
}
if (this->resampler) {
soxr_delete((soxr_t)this->resampler);
}
this->resampler = nullptr;
/* When creating a var-rate resampler, q_spec must be set as follows: */
soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_HQ, SOXR_VR);
soxr_runtime_spec_t const runtime_spec = soxr_runtime_spec(1);
soxr_io_spec_t io_spec;
if(bps == 2) {
io_spec = soxr_io_spec(SOXR_INT16_S, SOXR_INT16_S);
}
else if (bps == 4) {
io_spec = soxr_io_spec(SOXR_INT32_S, SOXR_INT32_S);
}
else {
LOG(LOG_LEVEL_ERROR) << "[audio_frame2_resampler] Unsupported BPS of: " << bps << "\n";
return false;
}
soxr_error_t error;
/* The ratio of the given input rate and output rates must equate to the
* maximum I/O ratio that will be used. A resample rate of 2 to 1 would be excessive,
but provides a sensible ceiling */
this->resampler = soxr_create(2, 1, nb_channels, &error, &io_spec, &q_spec, &runtime_spec);
if (error) {
LOG(LOG_LEVEL_ERROR) << "[audio_frame2_resampler] Cannot initialize resampler: " << soxr_strerror(error) << "\n";
return false;
}
// Immediately change the resample rate to be the correct value for the audio frame
soxr_set_io_ratio((soxr_t)this->resampler, ((double)original_sample_rate / ((double)new_sample_rate_num / (double)new_sample_rate_den)), 0);
// Setup resampler values
this->prop.rate_from = original_sample_rate;
this->prop.rate_to_num = new_sample_rate_num;
this->prop.rate_to_den = new_sample_rate_den;
this->prop.ch_count = nb_channels;
this->prop.bps = bps;
LOG(LOG_LEVEL_VERBOSE) << MOD_NAME "Soxr resampler (re)made at " << new_sample_rate_num / new_sample_rate_den << "\n";
return true;
}
tuple<bool, audio_frame2> soxr_resampler::resample(audio_frame2 &a, vector<audio_frame2::channel> &new_channels, int new_sample_rate_num, int new_sample_rate_den) {
std::chrono::high_resolution_clock::time_point funcBegin = std::chrono::high_resolution_clock::now();
bool ret = check_reconfigure(a.get_sample_rate(), new_sample_rate_num, new_sample_rate_den, a.get_channel_count(), a.get_bps());
if (!ret) {
return {false, audio_frame2{}};
}
// Initialise the new channels that the resampler is going to write into
void * * const obuf_ptrs = (void * *) malloc(sizeof(void *) * a.get_channel_count());
void * * ibuf_ptrs = (void * *) malloc(sizeof(void *) * a.get_channel_count());
for (size_t i = 0; i < new_channels.size(); i++) {
// Setup the buffers
obuf_ptrs[i] = new_channels[i].data.get();
ibuf_ptrs[i] = a.get_data(i);
}
size_t inlen = a.get_data_len(0) / a.get_bps();
size_t outlen = new_channels[0].len / a.get_bps();
size_t odone = 0;
soxr_error_t error;
error = soxr_process(resampler, ibuf_ptrs, inlen, NULL, obuf_ptrs, outlen, &odone);
if (error) {
LOG(LOG_LEVEL_ERROR) << "[audio_frame2_resampler] resampler failed: " << soxr_strerror(error) << "\n";
return {false, audio_frame2{}};
}
for (unsigned int i = 0; i < new_channels.size(); i++) {
new_channels[i].len = odone * a.get_bps();
}
free(obuf_ptrs); free(ibuf_ptrs);
std::chrono::high_resolution_clock::time_point funcEnd = std::chrono::high_resolution_clock::now();
long long resamplerDuration = std::chrono::duration_cast<std::chrono::milliseconds>(funcEnd - funcBegin).count();
LOG(LOG_LEVEL_DEBUG) << "[audio_frame2_resampler] resampler_duration " << resamplerDuration << "\n";
// Remainders aren't as relevant when using SOXR
audio_frame2 remainder = {};
return {true, std::move(remainder)};
}
#endif
#ifdef HAVE_SPEEXDSP
class speex_resampler : public audio_frame2_resampler::impl {
public:
tuple<bool, audio_frame2> resample(audio_frame2 &a, vector<audio_frame2::channel> &new_channels, int new_sample_rate_num, int new_sample_rate_den) override;
speex_resampler(int q) : quality(q) {}
const int *get_supported_bps() override {
static const int ret[] = { 2, 4, 0 };
return ret;
}
~speex_resampler() {
if (state) {
speex_resampler_destroy(state);
}
}
private:
bool check_reconfigure(unsigned original_sample_rate, unsigned new_sample_rate_num, unsigned new_sample_rate_den, unsigned channel_size, unsigned bps);
int quality;
SpeexResamplerState *state{nullptr};
struct resample_prop prop;
};
bool speex_resampler::check_reconfigure(unsigned original_sample_rate, unsigned new_sample_rate_num, unsigned new_sample_rate_den, unsigned nb_channels, unsigned bps) {
if (state != nullptr && original_sample_rate == prop.rate_from
&& new_sample_rate_num == prop.rate_to_num
&& new_sample_rate_den == prop.rate_to_den
&& nb_channels == prop.ch_count
&& bps == prop.bps) {
return true;
}
if (bps != 2 && bps != 4) {
throw logic_error("Only 16 or 32 bits per sample are supported for resampling!");
}
if (state) {
speex_resampler_destroy(state);
}
state = nullptr;
int err = 0;
state = speex_resampler_init_frac(nb_channels, original_sample_rate * new_sample_rate_den,
new_sample_rate_num, original_sample_rate, new_sample_rate_num, quality, &err);
if (err) {
LOG(LOG_LEVEL_ERROR) << MOD_NAME "Cannot initialize SpeexDSP resampler: " << speex_resampler_strerror(err) << "\n";
return false;
}
prop.rate_from = original_sample_rate;
prop.rate_to_num = new_sample_rate_num;
prop.rate_to_den = new_sample_rate_den;
prop.ch_count = nb_channels;
prop.bps = bps;
LOG(LOG_LEVEL_VERBOSE) << MOD_NAME "SpeexDSP resampler (re)made at " << new_sample_rate_num / new_sample_rate_den << "\n";
return true;
}
struct speex_process_channel_data {
SpeexResamplerState *state;
int channel_idx;
void *in;
void *out;
uint32_t in_frames;
uint32_t in_frames_orig;
uint32_t write_frames;
};
static void *speex_process_channel_short(void *arg) {
auto *d = (speex_process_channel_data *) arg;
speex_resampler_process_int(d->state,
d->channel_idx,
(spx_int16_t *) d->in, &d->in_frames,
(spx_int16_t *) d->out, &d->write_frames);
return NULL;
}
static void *speex_process_channel_int(void *arg) {
auto *d = (speex_process_channel_data *) arg;
int2float((char *) d->in, (char *) d->in, sizeof(float) * d->in_frames);
speex_resampler_process_float(d->state,
d->channel_idx,
(float *) d->in, &d->in_frames,
(float *) d->out, &d->write_frames);
float2int((char *) d->out, (char *) d->out, sizeof(float) * d->write_frames);
return NULL;
}
tuple<bool, audio_frame2> speex_resampler::resample(audio_frame2 &a, vector<audio_frame2::channel> &new_channels, int new_sample_rate_num, int new_sample_rate_den) {
bool ret = check_reconfigure(a.get_sample_rate(), new_sample_rate_num, new_sample_rate_den, a.get_channel_count(), a.get_bps());
if (!ret) {
return {false, audio_frame2{}};
}
audio_frame2 remainder;
remainder.init(new_channels.size(), AC_PCM, prop.bps, prop.rate_from);
vector <speex_process_channel_data> speex_worker_data(new_channels.size());
for (size_t i = 0; i < new_channels.size(); i++) {
speex_worker_data.at(i).state = state;
speex_worker_data.at(i).channel_idx = i;
speex_worker_data.at(i).in = (void *) a.get_data(i);
speex_worker_data.at(i).out = (void *) new_channels[i].data.get();
speex_worker_data.at(i).in_frames_orig =
speex_worker_data.at(i).in_frames = a.get_data_len(i) / a.get_bps();
speex_worker_data.at(i).write_frames = new_channels[i].len / a.get_bps();
}
if (a.get_bps() == 2) {
task_run_parallel(speex_process_channel_short, new_channels.size(), speex_worker_data.data(), sizeof speex_worker_data[0], NULL);
} else {
task_run_parallel(speex_process_channel_int, new_channels.size(), speex_worker_data.data(), sizeof speex_worker_data[0], NULL);
}
for (size_t i = 0; i < new_channels.size(); i++) {
if (speex_worker_data.at(i).in_frames != speex_worker_data.at(i).in_frames_orig) {
remainder.append(i, a.get_data(i) + speex_worker_data.at(i).in_frames * a.get_bps(),
speex_worker_data.at(i).in_frames_orig - speex_worker_data.at(i).in_frames);
}
new_channels[i].len = speex_worker_data.at(i).write_frames * a.get_bps();
}
if (remainder.get_data_len() == 0) {
remainder = {};
}
return {true, std::move(remainder)};
}
#endif // defined HAVE_SPEEXDSP
ADD_TO_PARAM("resampler", "* resampler=[speex|soxr][[:]quality=[0-10]]\n"
" Select resampler; set quality for Speex in range 0 (worst) and 10 (best), default " TOSTRING(DEFAULT_SPEEX_RESAMPLE_QUALITY) "\n");
audio_frame2_resampler::audio_frame2_resampler()
{
enum { RESAMPLER_DEFAULT, RESAMPLER_SPEEX, RESAMPLER_SOXR } resampler_type = RESAMPLER_DEFAULT;
const char *cfg_c = get_commandline_param("resampler");
std::string_view sv = cfg_c ? cfg_c : "";
int quality = DEFAULT_SPEEX_RESAMPLE_QUALITY;
while (!sv.empty()) {
const auto &tok = tokenize(sv, ':');
if (tok == "speex") {
resampler_type = RESAMPLER_SPEEX;
} else if (tok == "soxr") {
resampler_type = RESAMPLER_SOXR;
} else if (tok.compare(0, "quality="sv.length(), "quality=") == 0) {
quality = stoi(string(tok.substr("quality="sv.length())));
if (quality < 0 || quality > 10) {
throw ug_runtime_error("Quality " + to_string(quality) + " out of range 0-10"s);
}
} else {
throw ug_runtime_error("Unknown resampler option: "s + string(tok));
}
}
switch (resampler_type) {
case RESAMPLER_DEFAULT:
#ifdef HAVE_SPEEXDSP
m_impl = unique_ptr<audio_frame2_resampler::impl>(new speex_resampler(quality));
#elif defined HAVE_SOXR
m_impl = unique_ptr<audio_frame2_resampler::impl>(new soxr_resampler());
#endif
break;
case RESAMPLER_SPEEX:
#ifdef HAVE_SPEEXDSP
m_impl = unique_ptr<audio_frame2_resampler::impl>(new speex_resampler(quality));
#else
throw ug_runtime_error("SpeexDSP not compiled in!");
#endif
break;
case RESAMPLER_SOXR:
#if defined HAVE_SOXR
m_impl = unique_ptr<audio_frame2_resampler::impl>(new soxr_resampler());
break;
#else
throw ug_runtime_error("Soxr not compiled in!");
#endif
}
}
/**
* @returns the orig (if supported) or nearest higher of resampler supported BPS (highest if orig is higher)
*/
int audio_frame2_resampler::align_bps(int orig) {
if (!m_impl) {
LOG(LOG_LEVEL_ERROR) << "Audio frame resampler: cannot resample, Soxr/SpeexDSP was not compiled in!\n";
return 0;
}
const int *sup = m_impl->get_supported_bps();
int last = 0;
while (*sup != 0) {
if (orig <= *sup) {
return *sup;
}
last = *sup++;
}
return last;
}
audio_frame2_resampler::~audio_frame2_resampler() = default;
audio_frame2_resampler::audio_frame2_resampler(audio_frame2_resampler&&) = default;
audio_frame2_resampler& audio_frame2_resampler::operator=(audio_frame2_resampler&&) = default;