481 lines
13 KiB
C++
481 lines
13 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>;
|
|
|
|
const unsigned RTAUDIO_MAX_CHANNELS = 8;
|
|
|
|
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 ID; // Copy of RtAudio::DeviceInfo::ID
|
|
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);
|
|
const us count = rtaudio.getDeviceCount();
|
|
|
|
const auto ids = rtaudio.getDeviceIds();
|
|
|
|
for (us i = 0; i < count; i++)
|
|
{
|
|
us id = ids.at(i);
|
|
|
|
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(id);
|
|
|
|
// "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.ID = id;
|
|
|
|
/// 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 = std::min(devinfo.outputChannels, RTAUDIO_MAX_CHANNELS);
|
|
d.ninchannels = std::min(devinfo.inputChannels, RTAUDIO_MAX_CHANNELS);
|
|
|
|
d.availableInputRanges = {1.0};
|
|
d.availableOutputRanges = {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);
|
|
|
|
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.ninchannels;
|
|
inParams->deviceId = devinfo.ID;
|
|
}
|
|
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.noutchannels;
|
|
outParams->deviceId = devinfo.ID;
|
|
}
|
|
|
|
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.
|
|
RtAudioErrorType err = rtaudio.openStream(outParams.get(), inParams.get(), format,
|
|
static_cast<us>(samplerate()), &nFramesPerBlock_copy,
|
|
mycallback, (void *)this, &streamoptions);
|
|
if (err != RTAUDIO_NO_ERROR)
|
|
{
|
|
throw std::runtime_error(string("Error opening stream: ") + rtaudio.getErrorText());
|
|
}
|
|
|
|
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.
|
|
const auto err = rtaudio.startStream();
|
|
if (err != RTAUDIO_NO_ERROR)
|
|
{
|
|
throw std::runtime_error(string("Error starting stream: ") + rtaudio.getErrorText());
|
|
}
|
|
|
|
// 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())
|
|
{
|
|
const auto err = rtaudio.stopStream();
|
|
if(err != RTAUDIO_NO_ERROR) {
|
|
std::cerr << "Error occured while stopping the stream: " << rtaudio.getErrorText() << endl;
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
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
|