lasp/cpp_src/device/lasp_rtaudiodaq.cpp

430 lines
12 KiB
C++

/* #define DEBUGTRACE_ENABLED */
#include <mutex>
#include "debugtrace.hpp"
#include "lasp_mathtypes.h"
#include "lasp_rtaudiodaq.h"
#if LASP_HAS_RTAUDIO == 1
#include "RtAudio.h"
#include "lasp_daq.h"
#include <atomic>
#include <cassert>
using std::atomic;
using std::cerr;
using std::endl;
using rte = std::runtime_error;
using std::vector;
using lck = std::scoped_lock<std::mutex>;
class RtAudioDeviceInfo : public DeviceInfo {
public:
/**
* @brief Specific for the device (Sub-API). Important for the RtAudio
* backend, as RtAudio is able to handle different API's.
*/
int _api_devindex;
virtual std::unique_ptr<DeviceInfo> clone() const override {
return std::make_unique<DeviceInfo>(*this);
}
};
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
DEBUGTRACE_ENTER;
vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
for (auto api : apis) {
RtAudio rtaudio(api);
us count = rtaudio.getDeviceCount();
for (us devno = 0; devno < count; devno++) {
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
if (!devinfo.probed) {
// Device capabilities not successfully probed. Continue to next
continue;
}
// "Our device info struct"
RtAudioDeviceInfo d;
switch (api) {
case RtAudio::LINUX_ALSA:
d.api = rtaudioAlsaApi;
break;
case RtAudio::LINUX_PULSE:
d.api = rtaudioPulseaudioApi;
break;
case RtAudio::WINDOWS_WASAPI:
d.api = rtaudioWasapiApi;
break;
case RtAudio::WINDOWS_DS:
d.api = rtaudioDsApi;
break;
case RtAudio::WINDOWS_ASIO:
d.api = rtaudioAsioApi;
break;
default:
cerr << "Not implemented RtAudio API, skipping." << endl;
continue;
break;
}
d.device_name = devinfo.name;
d._api_devindex = devno;
/// When 48k is available we overwrite the default sample rate with the 48
/// kHz value, which is our preffered rate,
bool rate_48k_found = false;
for (us j = 0; j < devinfo.sampleRates.size(); j++) {
us rate_int = devinfo.sampleRates[j];
d.availableSampleRates.push_back((double)rate_int);
if (!rate_48k_found) {
if (devinfo.preferredSampleRate == rate_int) {
d.prefSampleRateIndex = j;
}
if (rate_int == 48000) {
d.prefSampleRateIndex = j;
rate_48k_found = true;
}
}
}
d.noutchannels = devinfo.outputChannels;
d.ninchannels = devinfo.inputChannels;
d.availableInputRanges = {1.0};
RtAudioFormat formats = devinfo.nativeFormats;
if (formats & RTAUDIO_SINT8) {
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_int8);
}
if (formats & RTAUDIO_SINT16) {
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_int16);
}
/* if (formats & RTAUDIO_SINT24) { *1/ */
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
*/
/* } */
if (formats & RTAUDIO_SINT32) {
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl32);
}
if (formats & RTAUDIO_FLOAT64) {
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64);
}
if (d.availableDataTypes.size() == 0) {
std::cerr << "RtAudio: No data types found in device!" << endl;
}
d.prefDataTypeIndex = d.availableDataTypes.size() - 1;
d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
d.prefFramesPerBlockIndex = 2;
devinfolist.push_back(std::make_unique<RtAudioDeviceInfo>(d));
}
}
}
static int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, double streamTime,
RtAudioStreamStatus status, void *userData);
static void myerrorcallback(RtAudioError::Type, const string &errorText);
class RtAudioDaq : public Daq {
RtAudio rtaudio;
const us nFramesPerBlock;
RtAudioDaq(const RtAudioDaq &) = delete;
RtAudioDaq &operator=(const RtAudioDaq &) = delete;
InDaqCallback _incallback;
OutDaqCallback _outcallback;
std::atomic<StreamStatus> _streamStatus{};
public:
RtAudioDaq(const DeviceInfo &devinfo_gen, const DaqConfiguration &config)
: Daq(devinfo_gen, config), rtaudio(static_cast<RtAudio::Api>(
devinfo_gen.api.api_specific_subcode)),
nFramesPerBlock(Daq::framesPerBlock()) {
DEBUGTRACE_ENTER;
// We make sure not to run RtAudio in duplex mode. This seems to be buggy
// and untested. Better to use a hardware-type loopback into the system.
if (duplexMode()) {
throw rte("RtAudio backend cannot run in duplex mode.");
}
assert(!monitorOutput);
const RtAudioDeviceInfo &devinfo =
static_cast<const RtAudioDeviceInfo &>(devinfo_gen);
std::unique_ptr<RtAudio::StreamParameters> inParams, outParams;
if (neninchannels() > 0) {
inParams = std::make_unique<RtAudio::StreamParameters>();
/// RtAudio lacks good bookkeeping when the first channel is not equal to
/// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels.
inParams->firstChannel = 0;
inParams->nChannels = devinfo_gen.ninchannels;
inParams->deviceId = devinfo._api_devindex;
} else {
outParams = std::make_unique<RtAudio::StreamParameters>();
/// RtAudio lacks good bookkeeping when the first channel is not equal to
/// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels.
outParams->firstChannel = 0;
outParams->nChannels = devinfo_gen.noutchannels;
outParams->deviceId = devinfo._api_devindex;
}
RtAudio::StreamOptions streamoptions;
streamoptions.flags = RTAUDIO_HOG_DEVICE | RTAUDIO_NONINTERLEAVED;
streamoptions.numberOfBuffers = 2;
streamoptions.streamName = "LASP RtAudio DAQ stream";
streamoptions.priority = 0;
RtAudioFormat format;
using Dtype = DataTypeDescriptor::DataType;
const Dtype dtype = dataType();
switch (dtype) {
case Dtype::dtype_fl32:
DEBUGTRACE_PRINT("Datatype float32");
format = RTAUDIO_FLOAT32;
break;
case Dtype::dtype_fl64:
DEBUGTRACE_PRINT("Datatype float64");
format = RTAUDIO_FLOAT64;
break;
case Dtype::dtype_int8:
DEBUGTRACE_PRINT("Datatype int8");
format = RTAUDIO_SINT8;
break;
case Dtype::dtype_int16:
DEBUGTRACE_PRINT("Datatype int16");
format = RTAUDIO_SINT16;
break;
case Dtype::dtype_int32:
DEBUGTRACE_PRINT("Datatype int32");
format = RTAUDIO_SINT32;
break;
default:
throw rte("Invalid data type specified for DAQ stream.");
break;
}
// Copy here, as it is used to return the *actual* number of frames per
// block.
unsigned int nFramesPerBlock_copy = nFramesPerBlock;
// Final step: open the stream.
rtaudio.openStream(outParams.get(), inParams.get(), format,
static_cast<us>(samplerate()), &nFramesPerBlock_copy,
mycallback, (void *)this, &streamoptions,
&myerrorcallback);
if (nFramesPerBlock_copy != nFramesPerBlock) {
throw rte(string("Got different number of frames per block back from RtAudio "
"backend: ") + std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
}
}
virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override final {
DEBUGTRACE_ENTER;
assert(!monitorOutput);
if (getStreamStatus().runningOK()) {
throw rte("Stream already running");
}
// Logical XOR
if (inCallback && outCallback) {
throw rte("Either input or output stream possible for RtAudio. "
"Stream duplex mode not provided.");
}
if (neninchannels() > 0) {
if (!inCallback) {
throw rte(
"Input callback given, but stream does not provide input data");
}
_incallback = inCallback;
}
if (nenoutchannels() > 0) {
if (!outCallback) {
throw rte(
"Output callback given, but stream does not provide output data");
}
_outcallback = outCallback;
}
// Start the stream. Throws on error.
rtaudio.startStream();
// If we are here, we are running without errors.
StreamStatus status;
status.isRunning = true;
_streamStatus = status;
}
StreamStatus getStreamStatus() const override final { return _streamStatus; }
void stop() override final {
DEBUGTRACE_ENTER;
if (getStreamStatus().runningOK()) {
rtaudio.stopStream();
}
StreamStatus s = _streamStatus;
s.isRunning = false;
s.errorType = StreamStatus::StreamError::noError;
_streamStatus = s;
}
int streamCallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, RtAudioStreamStatus status) {
DEBUGTRACE_ENTER;
using se = StreamStatus::StreamError;
int rval = 0;
auto stopWithError = [&](se e) {
DEBUGTRACE_PRINT("stopWithError");
StreamStatus stat = _streamStatus;
stat.errorType = e;
stat.isRunning = false;
_streamStatus = stat;
rval = 1;
};
switch (status) {
case RTAUDIO_INPUT_OVERFLOW:
stopWithError(se::inputXRun);
return 1;
break;
case RTAUDIO_OUTPUT_UNDERFLOW:
stopWithError(se::outputXRun);
return 1;
break;
default:
break;
}
const auto &dtype_descr = dtypeDescr();
const auto dtype = dataType();
const us neninchannels = this->neninchannels();
const us nenoutchannels = this->nenoutchannels();
const us sw = dtype_descr.sw;
if (nFrames != nFramesPerBlock) {
cerr << "RtAudio backend error: nFrames does not match block size!"
<< endl;
stopWithError(se::logicError);
return 1;
}
if (inputBuffer) {
assert(_incallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels);
const us ch_min = getLowestEnabledInChannel();
const us ch_max = getHighestEnabledInChannel();
assert(ch_min < ninchannels);
assert(ch_max < ninchannels);
/// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) {
if (inchannel_config.at(ch).enabled) {
byte_t *ptr =
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
ptrs.push_back(ptr);
}
}
DaqData d{nFramesPerBlock, neninchannels, dtype};
d.copyInFromRaw(ptrs);
_incallback(d);
}
if (outputBuffer) {
assert(_outcallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels);
/* outCallback */
const us ch_min = getLowestEnabledOutChannel();
const us ch_max = getHighestEnabledOutChannel();
assert(ch_min < noutchannels);
assert(ch_max < noutchannels);
/// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) {
if (outchannel_config.at(ch).enabled) {
ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
sw * ch * nFramesPerBlock);
}
}
DaqData d{nFramesPerBlock, nenoutchannels, dtype};
_outcallback(d);
// Copy over the buffer
us j = 0;
for (auto ptr : ptrs) {
d.copyToRaw(j, ptr);
j++;
}
}
return rval;
}
// RtAudio documentation says: if a stream is open, it will be stopped and
// closed automatically on deletion. Therefore the destructor here is a
// default one.
~RtAudioDaq() = default;
};
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
return std::make_unique<RtAudioDaq>(devinfo, config);
}
void myerrorcallback(RtAudioError::Type, const string &errorText) {
cerr << "RtAudio backend stream error: " << errorText << endl;
}
int mycallback(
void *outputBuffer, void *inputBuffer, unsigned int nFrames,
__attribute__((unused)) double streamTime, // Not used parameter streamTime
RtAudioStreamStatus status, void *userData) {
return static_cast<RtAudioDaq *>(userData)->streamCallback(
outputBuffer, inputBuffer, nFrames, status);
}
#endif // LASP_HAS_RTAUDIO == 1