Skip to content

Instantly share code, notes, and snippets.

@roxlu
Created May 6, 2016 20:05
Show Gist options
  • Save roxlu/9d2ed4d16908144394c43cd4dd054b25 to your computer and use it in GitHub Desktop.
Save roxlu/9d2ed4d16908144394c43cd4dd054b25 to your computer and use it in GitHub Desktop.
Example implementation of using the amazing b8brain audio samplerate converter, https://github.com/avaneev/r8brain-free-src This was used for a real time audio stream converter.
#include <poly/AudioConverterBrain.h>
#include <poly/AudioDataConverter.h>
namespace poly {
AudioConverterBrain::AudioConverterBrain()
:listener(NULL)
{
}
AudioConverterBrain::~AudioConverterBrain() {
shutdown();
}
/*
Initialize.
We allocate our destination buffers here. The size that we use
(expected_nframes) is based on the default frame sizes that we get
on Mac and Windows when capturing audio; actually it's 512 frames,
but we allocate a bit more just to be safe. I'm not sure how this
class could know the number of frames; maybe I can pass them into
this function as a member of `from`.
My current understanding is that r8brain works with doubles
(and ints) but not with floats, and only with deinterleaved
samples. Therefore I use the `in_deinterleaved` member that we
use to store deinterleaved data.
For now we only work with F32, interleaved data. We create
`CDSPResampler24` objects on the heap; similar to the exmaple.cpp
file from the b8brain repository. The `CDSPResampler24` is the
type that can be used for float conversions as stated in the
documentatin, see the doxygen documentation.
*/
int AudioConverterBrain::init(AudioSettings from,
AudioSettings to,
AudioConverterListener* lis)
{
if (NULL == lis) {
SX_ERROR("Given listener is NULL.");
return -1;
}
if (from.samplerate == to.samplerate) {
SX_ERROR("Input samplerate is same as output.");
return -1;
}
if (from.mode != to.mode) {
SX_ERROR("We don't support audio mode conversion.");
return -2;
}
if (AUDIO_BITSIZE_F32 != from.bitsize
|| AUDIO_BITSIZE_F32 != to.bitsize)
{
SX_ERROR("Currently we only support AUDIO_BITSIZE_F32");
return -3;
}
if (AUDIO_MODE_STEREO != from.mode
|| AUDIO_MODE_STEREO != to.mode)
{
SX_ERROR("Currently we only support stereo interleaved.");
return -3;
}
if (false == from.is_interleaved
|| false == to.is_interleaved)
{
SX_ERROR("We only support interleaved.");
return -4;
}
listener = lis;
convert_to.samplerate = to.samplerate;
convert_to.bitsize = to.bitsize;
convert_to.nchannels = to.mode;
convert_from.samplerate = from.samplerate;
convert_from.bitsize = from.bitsize;
convert_from.nchannels = from.mode;
in_deinterleaved.resize((size_t)to.mode);
/* Prepare our buffer. We allocate some size that we think is usable. */
size_t expected_nframes = 1024;
for (int i = 0; i < convert_from.nchannels; ++i) {
in_deinterleaved[i].resize(expected_nframes, 0.0);
}
out_resampled.resize(expected_nframes * convert_from.nchannels);
/* Create the resampler objects. */
CDSPResampler24* resampler = NULL;
for (int i = 0; i < convert_from.nchannels; ++i) {
resampler = new CDSPResampler24((double)from.samplerate,
(double)to.samplerate,
expected_nframes
);
resamplers.push_back(resampler);
}
out_pointers.resize(resamplers.size(), NULL);
return 0;
}
int AudioConverterBrain::shutdown() {
listener = NULL;
in_deinterleaved.clear();
out_resampled.clear();
out_pointers.clear();
for (size_t i = 0; i < resamplers.size(); ++i) {
delete resamplers[i];
resamplers[i] = NULL;
}
return 0;
}
/*
Convert audio data. Converting 512, 2 channels float, 32Khz
into 44.1Khz takes about 0.1-0.4ms on a intel i5 3.2Ghz
*/
int AudioConverterBrain::convert(void* data, size_t nbytes, size_t nframes) {
if (NULL == data) {
SX_ERROR("Given data is NULL.");
return -1;
}
if (0 == nbytes) {
SX_ERROR("nbytes is 0.");
return -2;
}
if (0 == nframes) {
SX_ERROR("nframes is 0.");
return -3;
}
if (AUDIO_BITSIZE_F32 != convert_from.bitsize) {
SX_ERROR("Currently we only support AUDIO_BITSIZE_F32");
return -4;
}
if (2 != convert_from.nchannels) {
SX_ERROR("Currently we only support interleaved 2 channel input data.");
return -5;
}
if (NULL == listener) {
SX_ERROR("Cannot convert audio data; listener is not set.");
return -6;
}
/* Make sure our deinterleaved buffers are big enough. */
for (size_t i = 0; i < convert_from.nchannels; ++i) {
if (in_deinterleaved[i].size() < nframes) {
in_deinterleaved[i].resize(nframes, 0.0);
}
}
/* We assume input data to be interleaved; here we deinterleave. */
float* src_ptr = (float*)data;
double* channel_ptrs[] = { &in_deinterleaved[0][0], &in_deinterleaved[1][0] };
poly_deinterleave<float, double>(src_ptr, channel_ptrs, nframes, resamplers.size());
/* Perform the resampling. */
int frames_generated = 0;
for (size_t k = 0; k < resamplers.size(); ++k) {
frames_generated = resamplers[k]->process(&in_deinterleaved[k][0], nframes, out_pointers[k]);
}
size_t out_elements_needed = resamplers.size() * frames_generated;
if (out_resampled.size() < out_elements_needed) {
out_resampled.resize(out_elements_needed, 0.0);
}
if (2 == convert_from.nchannels) {
poly_interleave<double, float>(&out_pointers[0], &out_resampled[0], frames_generated, 2);
}
listener->onAudioConverterData((void*)&out_resampled[0],
frames_generated * sizeof(float) * convert_from.nchannels,
frames_generated
);
return 0;
}
} /* namespace poly */
/*
Audio Converter using b8brain
==============================
GENERAL INFO:
This sample rate converter uses the awesome [r8brain-free-rc][0]
library to convert samplerates. At the time of writing we can
convert between any samplerate that is supported by r8brain but
we're limited to 2-channel, interleaved input data.
@todo When converting data, the first couple of output samples result
in a glitch; this is not a bug; it's described in the
documentation. There is also a way to work around this, see
the documentation.
LICENSE:
The r8brain repository is MIT licensed.
REFERENCES:
[0]: https://github.com/avaneev/r8brain-free-src "r8brain"
*/
#ifndef POLY_AUDIO_CONVERTER_BRAIN_H
#define POLY_AUDIO_CONVERTER_BRAIN_H
#include <poly/AudioTypes.h>
#include <poly/AudioSettings.h>
#include <poly/AudioConverterListener.h>
#include <CDSPResampler.h>
using namespace r8b;
namespace poly {
/* --------------------------------------------------------------------------- */
class AudioConverterBrain {
public:
AudioConverterBrain(); /* Sets default members. */
~AudioConverterBrain(); /* Calls shutdown to cleanup. */
int init(AudioSettings convertFrom, AudioSettings convertTo, AudioConverterListener* lis); /* Initialize. */
int shutdown(); /* Cleans up; resets state as it was before calling init(). */
int convert(void* data, size_t nbytes, size_t nframes); /* Convert the given data; make sure that it has the same format as the `convertFrom` parameter that you used when initializing. */
private:
AudioConverterListener* listener; /* The listener that we call when we have (samplerate) converted data. */
std::vector<std::vector<double> > in_deinterleaved; /* The b8brain library that we use, needs deinterleaved data; we've only worked with interleaved data so far so we expect that we need to deinterleave (which we do). */
std::vector<float> out_resampled; /* After resampling using the b8brain library we have deinterleaved, resampled data, we use this array to write the interleaved data (with converted samplerate) into. */
std::vector<double*> out_pointers; /* The b8brain samplerate converter outputs pointers to the converted audio data when we call it's process() function; we use these to interleave the result again. */
std::vector<CDSPResampler24*> resamplers; /* The instances, for each input channel of the resamplers. */
AudioConvertData convert_from; /* We convert from this format into `convert_to`. */
AudioConvertData convert_to; /* We convert into this format. */
};
/* --------------------------------------------------------------------------- */
} /* namespace poly */
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment