I'm trying to receive audio from the soundcard via RtAudio Api. It has a callback function that gets called once the audio has enough bytes received and the user can then copy the data to a custom object. This custom object can be sent to the callback via pointer. My class that encapsulates RtAudio looks like this:
class Audio {
private:
AudioConfiguration config;
AudioData data;
RtAudio* rt;
// the void* d is the casted AudioData-object
static int input( void*, void* inputBuffer, unsigned int bufferSize, double, RtAudioStreamStatus status, void* d );
void openStream( RtAudio::StreamParameters& params, RtAudio::StreamOptions& options, AudioConfiguration& config );
bool isDeviceOk();
public:
// ctor & dtor
Audio( AudioConfiguration& c );
~Audio();
// copy ctor & assignment
Audio( const Audio& other );
Audio& operator=( const Audio& a );
// move ctor & assignment
Audio( Audio&& other );
Audio& operator=( Audio&& a);
AudioConfiguration& getConfiguration();
AudioData& getData();
void start();
void stop();
};
This is the implementation of the static function that gets called from inside the audio thread
int Audio::input( void*, void* inputBuffer, unsigned int bufferSize, double, RtAudioStreamStatus status, void* d ){
if( status == RTAUDIO_INPUT_OVERFLOW )
std::cout << "Audio Thread: Input overflow detected." << std::endl;
//std::cout << "Audio Thread: Received input from soundcard" << std::endl;
float* in = static_cast<float*>( inputBuffer );
AudioData* data = static_cast<AudioData*>( d );
boost::lock_guard<boost::mutex> lock{data->getMutex()};
unsigned int i = 0;
while( i < bufferSize ){
data->getBuffer().push_back( *in );
in++;
i++;
}
return 0;
}
The custom object that I share between the threads is of the class AudioData, which looks like this:
class AudioData {
private:
boost::circular_buffer<float> buffer;
boost::mutex mutex;
public:
AudioData();
~AudioData();
boost::circular_buffer<float>& getBuffer();
boost::mutex& getMutex();
};
The audio-object gets embedded in a Recorder-Object, which then reads the buffer in the AudioData member variable of Audio.
typedef boost::container::vector<boost::circular_buffer<float>> Buffers;
class Recorder {
private:
Audio audio;
Buffers buffers;
/*
* Reads n samples per channel from audio buffers to NUM_CHANNELS distinct buffers
* When done, it returns the number of samples read into each channel
* Why? We want to keep audio buffer to be locked as minimal time as possible
*/
unsigned int read( unsigned int samples );
/*
* Deletes n samples from channel buffer
* When done, it returns the number of samples deleted
*/
unsigned int deleteBegin( unsigned int ch, unsigned int samples );
/*
* Detects the trigger on TRIGGER_CHANNEL
* Returns true, if trigger was found and its position
*/
bool detectTrigger( unsigned int* pos );
public:
Recorder( AudioConfiguration& c );
Recorder( Audio&& a );
boost::container::vector<float> record( RecorderConfiguration& config );
};
The function record(..) looks like this:
boost::container::vector<float> Recorder::record( RecorderConfiguration& config ){
AudioConfiguration audioConfig = audio.getConfiguration();
unsigned int length = ( audioConfig.fs * config.length ) / 1000;
boost::container::vector<float> recording;
recording.resize( length );
// Tell Audio to start recording
audio.start();
// State
unsigned int times = 0; // Count averages
unsigned int left = length; // Samples left on current average
bool triggered = false; // Trigger has been read
while( true ){
// Read into local buffer
unsigned int samplesToRead = length / 10;
unsigned int samplesRead = read( samplesToRead );
// if not enough samples, wait for more
if( samplesRead < 100 )
continue;
// Actual logic
unsigned int triggerPos = 0;
if( !triggered && detectTrigger( &triggerPos ) ){
std::cout << "Recorder: Trigger detected." << std::endl;
triggered = true;
// delete everything that comes before trigger on both channels
for( unsigned int i = 0 ; i < NUM_CHANNELS ; i++ ){
deleteBegin( i, triggerPos - 1);
}
}
// Copy from buffer if trigger was found beforehand
if( triggered ){
boost::circular_buffer<float>& buffer = buffers[ EEG_CHANNEL ];
unsigned int samplesToCopy = buffer.size();
if( samplesToCopy > left )
samplesToCopy = left;
for( unsigned int i = 0 ; i < samplesToCopy ; i++ ){
recording[ length - left ] = recording[ left - left ] + buffer.front();
buffer.pop_front();
left--;
}
}
// current average done
if( left <= 0 ){
// increment times
times++;
// check if recording is done
if( times >= config.times )
break;
// if not
else {
triggered = false;
left = length;
}
}
}
// Stop receiving input from audio
audio.stop();
return recording;
}
I read that the heap is the place to hold data that is shared between threads, but in the example by rtaudio they use a global variable that gets allocated on the stack for pushing the data to Link. So I am a little bit confused. Help would be gladly accepted!
Edit: When i debug my app. I can see that the input function of the audio-thread gets called and it writes to the buffer. Also the record function works as expected. Only the buffer (of AudioData) does not seem to have any data in it...
Edit2: Here is the code where I register the callback in the rtaudio api.
void Audio::openStream( RtAudio::StreamParameters& params, RtAudio::StreamOptions& options, AudioConfiguration& config ){
try {
rt->openStream( nullptr, ¶ms, RTAUDIO_FLOAT32, config.fs, &config.bufferSize, &this->input, &data, &options, nullptr );
} catch( RtAudioError& e ){
std::cout << "Audio::openStream(): Cannot open stream." << std::endl;
throw e;
}
}