mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-20 23:40:18 +00:00
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.
424 lines
18 KiB
C++
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;
|
|
|