/* #define DEBUGTRACE_ENABLED */ #include #include "debugtrace.hpp" #include "lasp_mathtypes.h" #include "lasp_rtaudiodaq.h" #if LASP_HAS_RTAUDIO == 1 #include "RtAudio.h" #include "lasp_daq.h" #include #include using std::atomic; using std::cerr; using std::endl; using rte = std::runtime_error; using std::vector; using lck = std::scoped_lock; 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 clone() const override { return std::make_unique(*this); } }; void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) { DEBUGTRACE_ENTER; vector 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(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{}; public: RtAudioDaq(const DeviceInfo &devinfo_gen, const DaqConfiguration &config) : Daq(devinfo_gen, config), rtaudio(static_cast( 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(devinfo_gen); std::unique_ptr inParams, outParams; if (neninchannels() > 0) { inParams = std::make_unique(); /// 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 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(samplerate()), &nFramesPerBlock_copy, mycallback, (void *)this, &streamoptions, &myerrorcallback); if (nFramesPerBlock_copy != nFramesPerBlock) { throw rte("Got different number of frames per block back from RtAudio " "backend. 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(); us neninchannels = this->neninchannels(); us nenoutchannels = this->nenoutchannels(); 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 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(inputBuffer) + sw * ch * nFramesPerBlock; ptrs.push_back(ptr); } } DaqData d{nFramesPerBlock, neninchannels, dtype}; d.copyInFromRaw(ptrs); bool ret = _incallback(d); if (!ret) { stopWithError(se::noError); return 1; } } if (outputBuffer) { assert(_outcallback); std::vector 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(outputBuffer) + sw * ch * nFramesPerBlock); } } DaqData d{nFramesPerBlock, nenoutchannels, dtype}; bool ret = _outcallback(d); if (!ret) { stopWithError(se::noError); return 1; } 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 createRtAudioDevice(const DeviceInfo &devinfo, const DaqConfiguration &config) { return std::make_unique(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(userData)->streamCallback( outputBuffer, inputBuffer, nFrames, status); } #endif // LASP_HAS_RTAUDIO == 1