From 7ce45e9c820ab128a54756606b41bddb71b049b9 Mon Sep 17 00:00:00 2001 From: "J.A. de Jong - Redu-Sone B.V., ASCEE V.O.F" Date: Tue, 6 Feb 2024 14:59:51 +0100 Subject: [PATCH] Some comment improvements, and portaudio API improvements. Also, disabled PortAudio PulseAudio backend as it is not working properly. --- cmake/portaudio.cmake | 3 +- cpp_src/device/lasp_daq.h | 8 +- cpp_src/device/lasp_streammgr.cpp | 107 ++-- .../device/portaudio/lasp_portaudiodaq.cpp | 488 +++++++++--------- 4 files changed, 302 insertions(+), 304 deletions(-) diff --git a/cmake/portaudio.cmake b/cmake/portaudio.cmake index 053f950..8a9d4eb 100644 --- a/cmake/portaudio.cmake +++ b/cmake/portaudio.cmake @@ -4,7 +4,8 @@ if(LASP_HAS_PORTAUDIO) if(WIN32) else() set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend") - set(PA_USE_JACK TRUE CACHE BOOL "Build PortAudio with Jack backend") + set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend") + set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend") # set(PA_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA") set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library") endif() diff --git a/cpp_src/device/lasp_daq.h b/cpp_src/device/lasp_daq.h index e1f9d3d..e3e3c51 100644 --- a/cpp_src/device/lasp_daq.h +++ b/cpp_src/device/lasp_daq.h @@ -48,6 +48,10 @@ public: logicError, }; + // Below the only members of this class, which are public. + bool isRunning = false; + StreamError errorType{StreamError::noError}; + /** * @brief Map between error types and messages */ @@ -61,7 +65,7 @@ public: {StreamError::logicError, "Logic error (probably a bug)"}, }; - bool isRunning = false; + /** * @brief Check if stream has error * @@ -69,8 +73,6 @@ public: */ bool error() const { return errorType != StreamError::noError; }; - StreamError errorType{StreamError::noError}; - std::string errorMsg() const { return errorMessages.at(errorType); } /** diff --git a/cpp_src/device/lasp_streammgr.cpp b/cpp_src/device/lasp_streammgr.cpp index 146805c..af5f56b 100644 --- a/cpp_src/device/lasp_streammgr.cpp +++ b/cpp_src/device/lasp_streammgr.cpp @@ -1,16 +1,19 @@ /* #define DEBUGTRACE_ENABLED */ #include "lasp_streammgr.h" -#include "debugtrace.hpp" -#include "lasp_biquadbank.h" -#include "lasp_indatahandler.h" -#include "lasp_thread.h" -#include + #include + +#include #include #include #include #include +#include "debugtrace.hpp" +#include "lasp_biquadbank.h" +#include "lasp_indatahandler.h" +#include "lasp_thread.h" + using std::cerr; using std::endl; using rte = std::runtime_error; @@ -109,7 +112,6 @@ void StreamMgr::rescanDAQDevices_impl(std::function callback) { } } void StreamMgr::inCallback(const DaqData &data) { - DEBUGTRACE_ENTER; std::scoped_lock lck(_inDataHandler_mtx); @@ -118,7 +120,6 @@ void StreamMgr::inCallback(const DaqData &data) { if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(), [](const auto &a) { return bool(a); }) > 0) { - /// Found a filter in vector of input filters. So we have to apply the /// filters to each channel. @@ -151,7 +152,6 @@ void StreamMgr::inCallback(const DaqData &data) { } void StreamMgr::setSiggen(std::shared_ptr siggen) { - DEBUGTRACE_ENTER; checkRightThread(); @@ -179,7 +179,8 @@ void StreamMgr::setSiggen(std::shared_ptr siggen) { * * @return */ -template bool fillData(DaqData &data, const vd &signal) { +template +bool fillData(DaqData &data, const vd &signal) { /* DEBUGTRACE_ENTER; */ assert(data.nframes == signal.size()); @@ -212,7 +213,6 @@ template bool fillData(DaqData &data, const vd &signal) { return true; } void StreamMgr::outCallback(DaqData &data) { - /* DEBUGTRACE_ENTER; */ std::scoped_lock lck(_siggen_mtx); @@ -220,21 +220,21 @@ void StreamMgr::outCallback(DaqData &data) { if (_siggen) { vd signal = _siggen->genSignal(data.nframes); switch (data.dtype) { - case (DataTypeDescriptor::DataType::dtype_fl32): - fillData(data, signal); - break; - case (DataTypeDescriptor::DataType::dtype_fl64): - fillData(data, signal); - break; - case (DataTypeDescriptor::DataType::dtype_int8): - fillData(data, signal); - break; - case (DataTypeDescriptor::DataType::dtype_int16): - fillData(data, signal); - break; - case (DataTypeDescriptor::DataType::dtype_int32): - fillData(data, signal); - break; + case (DataTypeDescriptor::DataType::dtype_fl32): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_fl64): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_int8): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_int16): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_int32): + fillData(data, signal); + break; } } else { // Set all values to 0. @@ -257,8 +257,6 @@ StreamMgr::~StreamMgr() { // virtual methods. This was really a bug. _inputStream.reset(); _outputStream.reset(); - - } void StreamMgr::stopAllStreams() { DEBUGTRACE_ENTER; @@ -273,11 +271,11 @@ void StreamMgr::startStream(const DaqConfiguration &config) { bool isInput = std::count_if(config.inchannel_config.cbegin(), config.inchannel_config.cend(), - [](auto &i) { return i.enabled; }); + [](auto &i) { return i.enabled; }) > 0; bool isOutput = std::count_if(config.outchannel_config.cbegin(), config.outchannel_config.cend(), - [](auto &i) { return i.enabled; }); + [](auto &i) { return i.enabled; }) > 0; // Find the first device that matches with the configuration std::scoped_lock lck(_devices_mtx); @@ -302,34 +300,40 @@ void StreamMgr::startStream(const DaqConfiguration &config) { bool isDuplex = isInput && isOutput; if (!isInput && !isOutput) { - throw rte("Neither input, nor output channels enabled for " - "stream. Cannot start."); + throw rte( + "Attempted stream start failed, stream does not have any enabled " + "channels. Please first enable channels in the channel configuration."); } if (isInput && _inputStream) { - throw rte("Error: an input stream is already running. Please " - "first stop existing stream"); + throw rte( + "Error: an input stream is already running. Please " + "first stop existing stream"); } else if (isOutput && _outputStream) { - throw rte("Error: output stream is already running. Please " - "first stop existing stream"); + throw rte( + "Error: output stream is already running. Please " + "first stop existing stream"); } else if (_inputStream) { if (_inputStream->duplexMode() && isOutput) { - throw rte("Error: output stream is already running (in duplex mode). " - "Please " - "first stop existing stream"); + throw rte( + "Error: output stream is already running (in duplex mode). " + "Please " + "first stop existing stream"); } } if (_outputStream && isInput && _outputStream->duplexModeForced && config.match(*_outputStream)) { - throw rte("This device is already opened for output. If input is also " - "required, please enable duplex mode for this device"); + throw rte( + "This device is already opened for output. If input is also " + "required, please enable duplex mode for this device"); } if (_inputStream && isOutput && _inputStream->duplexModeForced && config.match(*_inputStream)) { - throw rte("This device is already opened for input. If output is also " - "required, please enable duplex mode for this device"); + throw rte( + "This device is already opened for input. If output is also " + "required, please enable duplex mode for this device"); } InDaqCallback inCallback; @@ -352,13 +356,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) { d fs = daq->samplerate(); /// Create input filters _inputFilters.clear(); - - /// No input filter for monitor channel, which comes as the first input channel - /// In the list + + /// No input filter for monitor channel, which comes as the first input + /// channel In the list if (config.monitorOutput && devinfo->hasInternalOutputMonitor) { _inputFilters.push_back(nullptr); } - + for (auto &ch : daq->inchannel_config) { if (ch.enabled) { if (ch.digitalHighPassCutOn < 0) { @@ -371,7 +375,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) { SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn))); } } - } // End of input filter creation + } // End of input filter creation } if (isOutput) { @@ -398,7 +402,6 @@ void StreamMgr::startStream(const DaqConfiguration &config) { } } void StreamMgr::stopStream(const StreamType t) { - DEBUGTRACE_ENTER; checkRightThread(); @@ -423,7 +426,7 @@ void StreamMgr::stopStream(const StreamType t) { throw rte("Output stream is not running"); } _outputStream.reset(); - } // end else + } // end else } } @@ -436,8 +439,9 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) { if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) != _inDataHandlers.cend()) { - throw std::runtime_error("Error: handler already added. Probably start() " - "is called more than once on a handler object"); + throw std::runtime_error( + "Error: handler already added. Probably start() " + "is called more than once on a handler object"); } _inDataHandlers.push_back(handler); DEBUGTRACE_PRINT(_inDataHandlers.size()); @@ -467,7 +471,6 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const { } const Daq *StreamMgr::getDaq(StreamType type) const { - checkRightThread(); if (type == StreamType::input) { diff --git a/cpp_src/device/portaudio/lasp_portaudiodaq.cpp b/cpp_src/device/portaudio/lasp_portaudiodaq.cpp index d6f61bc..36f0b30 100644 --- a/cpp_src/device/portaudio/lasp_portaudiodaq.cpp +++ b/cpp_src/device/portaudio/lasp_portaudiodaq.cpp @@ -3,23 +3,22 @@ #include "lasp_config.h" #if LASP_HAS_PORTAUDIO == 1 -#include "lasp_portaudiodaq.h" -#include "portaudio.h" #include #include #include +#include "lasp_portaudiodaq.h" +#include "portaudio.h" + using rte = std::runtime_error; using std::cerr; using std::endl; using std::string; using std::to_string; -inline void throwIfError(PaError e) -{ +inline void throwIfError(PaError e) { DEBUGTRACE_ENTER; - if (e != paNoError) - { + if (e != paNoError) { throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e)); } } @@ -27,16 +26,14 @@ inline void throwIfError(PaError e) /** * @brief Device info, plus PortAudio stuff */ -class OurPaDeviceInfo : public DeviceInfo -{ -public: +class OurPaDeviceInfo : public DeviceInfo { + public: /** * @brief Store instance to PaDeviceInfo. */ PaDeviceInfo _paDevInfo; - virtual std::unique_ptr clone() const override final - { + virtual std::unique_ptr clone() const override final { return std::make_unique(*this); } OurPaDeviceInfo &operator=(const OurPaDeviceInfo &) = delete; @@ -44,44 +41,40 @@ public: OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {} }; -void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) -{ +void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) { DEBUGTRACE_ENTER; - try - { - + bool shouldPaTerminate = false; + try { PaError err = Pa_Initialize(); /// PortAudio says that Pa_Terminate() should not be called whenever there /// is an error in Pa_Initialize(). This is opposite to what most examples /// of PortAudio show. throwIfError(err); + shouldPaTerminate = true; - auto fin = gsl::finally([&err] - { + auto fin = gsl::finally([&err] { DEBUGTRACE_PRINT("Terminating PortAudio instance"); err = Pa_Terminate(); if (err != paNoError) { cerr << "Error terminating PortAudio. Do not know what to do." << endl; - } }); + } + }); const PaHostApiIndex apicount = Pa_GetHostApiCount(); - if (apicount < 0) - { + if (apicount < 0) { return; } /* const PaDeviceInfo *deviceInfo; */ const int numDevices = Pa_GetDeviceCount(); - if (numDevices < 0) - { + if (numDevices < 0) { throw rte("PortAudio could not find any devices"); } - for (us i = 0; i < (us)numDevices; i++) - { + for (us i = 0; i < (us)numDevices; i++) { /* DEBUGTRACE_PRINT(i); */ + bool hasDuplexMode = false; const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i); - if (!deviceInfo) - { + if (!deviceInfo) { throw rte("No device info struct returned"); } OurPaDeviceInfo d(*deviceInfo); @@ -90,36 +83,36 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) d.device_name = deviceInfo->name; const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi); - if (hostapiinfo == nullptr) - { + if (hostapiinfo == nullptr) { throw std::runtime_error("Hostapi nullptr!"); } - switch (hostapiinfo->type) - { - case paALSA: - d.api = portaudioALSAApi; - break; - case paASIO: - d.api = portaudioASIOApi; - break; - case paDirectSound: - d.api = portaudioDirectSoundApi; - break; - case paMME: - d.api = portaudioWMMEApi; - break; - case paWDMKS: - d.api = portaudioWDMKS; - break; - case paWASAPI: - d.api = portaudioWASAPIApi; - break; - case paPulseAudio: - d.api = portaudioPulseApi; - break; - default: - throw rte("Unimplemented portaudio API!"); - break; + switch (hostapiinfo->type) { + case paALSA: + // Duplex mode for alsa + hasDuplexMode = true; + d.api = portaudioALSAApi; + break; + case paASIO: + d.api = portaudioASIOApi; + break; + case paDirectSound: + d.api = portaudioDirectSoundApi; + break; + case paMME: + d.api = portaudioWMMEApi; + break; + case paWDMKS: + d.api = portaudioWDMKS; + break; + case paWASAPI: + d.api = portaudioWASAPIApi; + break; + case paPulseAudio: + d.api = portaudioPulseApi; + break; + default: + throw rte("Unimplemented portaudio API!"); + break; } d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16, @@ -128,7 +121,7 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) d.prefDataTypeIndex = 2; - d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0, + d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, 44100.0, 48000.0, 88200.0, 96000.0, 192000.0}; d.prefSampleRateIndex = 9; @@ -148,8 +141,13 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) } } - catch (rte &e) - { + catch (rte &e) { + if (shouldPaTerminate) { + PaError err = Pa_Terminate(); + if (err != paNoError) { + cerr << "Error terminating PortAudio. Do not know what to do." << endl; + } + } cerr << "PortAudio backend error: " << e.what() << std::endl; return; } @@ -174,16 +172,14 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); -class PortAudioDaq : public Daq -{ - bool _shouldPaTerminate = false; +class PortAudioDaq : public Daq { PaStream *_stream = nullptr; std::atomic _streamError = StreamStatus::StreamError::noError; InDaqCallback _incallback; OutDaqCallback _outcallback; -public: + public: PortAudioDaq(const OurPaDeviceInfo &devinfo_gen, const DaqConfiguration &config); @@ -212,13 +208,11 @@ public: }; std::unique_ptr createPortAudioDevice(const DeviceInfo &devinfo, - const DaqConfiguration &config) -{ + const DaqConfiguration &config) { DEBUGTRACE_ENTER; const OurPaDeviceInfo *_info = dynamic_cast(&devinfo); - if (_info == nullptr) - { + if (_info == nullptr) { throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo"); } return std::make_unique(*_info, config); @@ -227,144 +221,143 @@ std::unique_ptr createPortAudioDevice(const DeviceInfo &devinfo, static int rawPaCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, void *userData) -{ + PaStreamCallbackFlags statusFlags, void *userData) { return static_cast(userData)->memberPaCallback( inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); } PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen, const DaqConfiguration &config) - : Daq(devinfo_gen, config) -{ - + : Daq(devinfo_gen, config) { DEBUGTRACE_ENTER; - PaError err = Pa_Initialize(); - /// PortAudio says that Pa_Terminate() should not be called whenever there - /// is an error in Pa_Initialize(). This is opposite to what most examples - /// of PortAudio show. - throwIfError(err); + bool shouldPaTerminate = false; + try { + PaError err = Pa_Initialize(); + /// PortAudio says that Pa_Terminate() should not be called whenever there + /// is an error in Pa_Initialize(). This is opposite to what most examples + /// of PortAudio show. + throwIfError(err); - // OK, Pa_Initialize successfully finished, it means we have to clean up with - // Pa_Terminate in the destructor. - _shouldPaTerminate = true; + // OK, Pa_Initialize successfully finished, it means we have to clean up + // with Pa_Terminate in the destructor. + shouldPaTerminate = true; - // Going to find the device in the list. If its there, we have to retrieve - // the index, as this is required in the PaStreamParameters struct - int devindex = -1; - for (int i = 0; i < Pa_GetDeviceCount(); i++) - { - // DEBUGTRACE_PRINT(i); - bool ok = true; - const PaDeviceInfo *info = Pa_GetDeviceInfo(i); - if (!info) - { - throw rte("No device structure returned from PortAudio"); + // Going to find the device in the list. If its there, we have to retrieve + // the index, as this is required in the PaStreamParameters struct + int devindex = -1; + for (int i = 0; i < Pa_GetDeviceCount(); i++) { + // DEBUGTRACE_PRINT(i); + bool ok = true; + const PaDeviceInfo *info = Pa_GetDeviceInfo(i); + if (!info) { + throw rte("No device structure returned from PortAudio"); + } + ok &= string(info->name) == devinfo_gen.device_name; + ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi; + ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels; + ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels; + ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate; + + if (ok) { + devindex = i; + } } - ok &= string(info->name) == devinfo_gen.device_name; - ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi; - ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels; - ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels; - ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate; - - if (ok) - { - devindex = i; + if (devindex < 0) { + throw rte(string("Device not found: ") + string(devinfo_gen.device_name)); } - } - if (devindex < 0) - { - throw rte(string("Device not found: ") + string(devinfo_gen.device_name)); - } - using Dtype = DataTypeDescriptor::DataType; - const Dtype dtype = dataType(); - // Sample format is bit flag - PaSampleFormat format = paNonInterleaved; - switch (dtype) - { - case Dtype::dtype_fl32: - DEBUGTRACE_PRINT("Datatype float32"); - format |= paFloat32; - break; - case Dtype::dtype_fl64: - DEBUGTRACE_PRINT("Datatype float64"); - throw rte("Invalid data type specified for DAQ stream."); - break; - case Dtype::dtype_int8: - DEBUGTRACE_PRINT("Datatype int8"); - format |= paInt8; - break; - case Dtype::dtype_int16: - DEBUGTRACE_PRINT("Datatype int16"); - format |= paInt16; - break; - case Dtype::dtype_int32: - DEBUGTRACE_PRINT("Datatype int32"); - format |= paInt32; - break; - default: - throw rte("Invalid data type specified for DAQ stream."); - break; + using Dtype = DataTypeDescriptor::DataType; + const Dtype dtype = dataType(); + // Sample format is bit flag + PaSampleFormat format = paNonInterleaved; + switch (dtype) { + case Dtype::dtype_fl32: + DEBUGTRACE_PRINT("Datatype float32"); + format |= paFloat32; + break; + case Dtype::dtype_fl64: + DEBUGTRACE_PRINT("Datatype float64"); + throw rte("Invalid data type specified for DAQ stream."); + break; + case Dtype::dtype_int8: + DEBUGTRACE_PRINT("Datatype int8"); + format |= paInt8; + break; + case Dtype::dtype_int16: + DEBUGTRACE_PRINT("Datatype int16"); + format |= paInt16; + break; + case Dtype::dtype_int32: + DEBUGTRACE_PRINT("Datatype int32"); + format |= paInt32; + break; + default: + throw rte("Invalid data type specified for DAQ stream."); + break; + } + + std::unique_ptr instreamParams; + std::unique_ptr outstreamParams; + + if (neninchannels() > 0) { + instreamParams = std::make_unique(PaStreamParameters( + {.device = devindex, + .channelCount = (int)getHighestEnabledInChannel() + 1, + .sampleFormat = format, + .suggestedLatency = framesPerBlock() / samplerate(), + .hostApiSpecificStreamInfo = nullptr})); + } + if (nenoutchannels() > 0) { + outstreamParams = std::make_unique(PaStreamParameters( + {.device = devindex, + .channelCount = (int)getHighestEnabledOutChannel() + 1, + .sampleFormat = format, + .suggestedLatency = framesPerBlock() / samplerate(), + .hostApiSpecificStreamInfo = nullptr})); + } + + // Next step: check whether we are OK + err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(), + samplerate()); + throwIfError(err); + + err = Pa_OpenStream(&_stream, // stream + instreamParams.get(), // inputParameters + outstreamParams.get(), // outputParameters + samplerate(), // yeah, + framesPerBlock(), // framesPerBuffer + paNoFlag, // streamFlags + rawPaCallback, this); + throwIfError(err); + assert(_stream); + } catch (rte &e) { + if (shouldPaTerminate) { + PaError err = Pa_Terminate(); + if (err != paNoError) { + cerr << "Error terminating PortAudio. Do not know what to do." << endl; + } + } + throw; } - - std::unique_ptr instreamParams; - std::unique_ptr outstreamParams; - - if (neninchannels() > 0) - { - instreamParams = std::make_unique( - PaStreamParameters({.device = devindex, - .channelCount = (int)getHighestEnabledInChannel() + 1, - .sampleFormat = format, - .suggestedLatency = framesPerBlock() / samplerate(), - .hostApiSpecificStreamInfo = nullptr})); - } - if (nenoutchannels() > 0) - { - outstreamParams = std::make_unique( - PaStreamParameters({.device = devindex, - .channelCount = (int)getHighestEnabledOutChannel() + 1, - .sampleFormat = format, - .suggestedLatency = framesPerBlock() / samplerate(), - .hostApiSpecificStreamInfo = nullptr})); - } - - // Next step: check whether we are OK - err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(), - samplerate()); - throwIfError(err); - - err = Pa_OpenStream(&_stream, // stream - instreamParams.get(), // inputParameters - outstreamParams.get(), // outputParameters - samplerate(), // yeah, - framesPerBlock(), // framesPerBuffer - paNoFlag, // streamFlags - rawPaCallback, this); - throwIfError(err); } -void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) -{ +void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) { DEBUGTRACE_ENTER; assert(_stream); - if (Pa_IsStreamActive(_stream)) - { + + if (Pa_IsStreamActive(_stream)) { throw rte("Stream is already running"); } // Logical XOR - if (inCallback && outCallback) - { - throw rte("Either input or output stream possible for RtAudio. " - "Stream duplex mode not provided."); + if (inCallback && outCallback) { + throw rte( + "Either input or output stream possible for PortAudio. " + "Stream duplex mode not provided."); } - if (neninchannels() > 0) - { - if (!inCallback) - { + if (neninchannels() > 0) { + if (!inCallback) { throw rte( "Input callback given, but stream does not provide input data"); @@ -372,10 +365,8 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) _incallback = inCallback; } - if (nenoutchannels() > 0) - { - if (!outCallback) - { + if (nenoutchannels() > 0) { + if (!outCallback) { throw rte( "Output callback given, but stream does not provide output data"); } @@ -385,86 +376,94 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) PaError err = Pa_StartStream(_stream); throwIfError(err); } -void PortAudioDaq::stop() -{ +void PortAudioDaq::stop() { DEBUGTRACE_ENTER; assert(_stream); - if (Pa_IsStreamStopped(_stream)) - { + if (Pa_IsStreamStopped(_stream) > 1) { throw rte("Stream is already stopped"); } PaError err = Pa_StopStream(_stream); throwIfError(err); } -Daq::StreamStatus PortAudioDaq::getStreamStatus() const -{ +Daq::StreamStatus PortAudioDaq::getStreamStatus() const { + DEBUGTRACE_ENTER; + // Stores an error type and whether the Daq::StreamStatus status; - // Copy over atomic flag. - status.errorType = _streamError; - // Check if stream is still running. - if (_stream) - { - if (Pa_IsStreamActive(_stream)) - { - status.isRunning = true; + using StreamError = Daq::StreamStatus::StreamError; + Daq::StreamStatus::StreamError errortype = _streamError.load(); + + PaError err = Pa_IsStreamStopped(_stream); + if (err > 1) { + // Stream is stopped due to an error in the callback. The exact error type + // is filled in in the if-statement above + return status; + } else if (err == 0) { + // Still running + status.isRunning = true; + } else if (err < 0) { + // Stream encountered an error. + switch (err) { + case paInternalError: + errortype = StreamError::driverError; + break; + case paDeviceUnavailable: + errortype = StreamError::driverError; + break; + case paInputOverflowed: + errortype = StreamError::inputXRun; + break; + case paOutputUnderflowed: + errortype = StreamError::outputXRun; + break; + default: + errortype = StreamError::driverError; + cerr << "Portaudio backend error:" << Pa_GetErrorText(err) << endl; + break; } } + status.errorType = errortype; return status; } -PortAudioDaq::~PortAudioDaq() -{ +PortAudioDaq::~PortAudioDaq() { PaError err; - if (_stream) - { - if (Pa_IsStreamActive(_stream)) - { - stop(); - } - - err = Pa_CloseStream(_stream); - _stream = nullptr; - if (err != paNoError) - { - cerr << "Error closing PortAudio stream. Do not know what to do." << endl; - } - assert(_shouldPaTerminate); + assert(_stream); + if (Pa_IsStreamActive(_stream)) { + // Stop the stream first + stop(); } - if (_shouldPaTerminate) - { - err = Pa_Terminate(); - if (err != paNoError) - { - cerr << "Error terminating PortAudio. Do not know what to do." << endl; - } + err = Pa_CloseStream(_stream); + _stream = nullptr; + if (err != paNoError) { + cerr << "Error closing PortAudio stream. Do not know what to do." << endl; + } + + err = Pa_Terminate(); + if (err != paNoError) { + cerr << "Error terminating PortAudio. Do not know what to do." << endl; } } + int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags) -{ - - DEBUGTRACE_ENTER; + PaStreamCallbackFlags statusFlags) { + // DEBUGTRACE_ENTER; typedef Daq::StreamStatus::StreamError se; - if (statusFlags & paPrimingOutput) - { + if (statusFlags & paPrimingOutput) { // Initial output buffers generated. So nothing with input yet return paContinue; } - if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) - { + if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) { _streamError = se::inputXRun; return paAbort; } - if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) - { + if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) { _streamError = se::outputXRun; return paAbort; } - if (framesPerBuffer != framesPerBlock()) - { + if (framesPerBuffer != framesPerBlock()) { cerr << "Logic error: expected a block size of: " << framesPerBlock() << endl; _streamError = se::logicError; @@ -476,8 +475,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, const auto &dtype_descr = dtypeDescr(); const auto dtype = dataType(); const us sw = dtype_descr.sw; - if (inputBuffer) - { + if (inputBuffer) { assert(_incallback); std::vector ptrs; ptrs.reserve(neninchannels); @@ -489,10 +487,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, /// Only pass on the pointers of the channels we want. inputBuffer is /// noninterleaved, as specified in PortAudioDaq constructor. - for (us ch = ch_min; ch <= ch_max; ch++) - { - if (inchannel_config.at(ch).enabled) - { + for (us ch = ch_min; ch <= ch_max; ch++) { + if (inchannel_config.at(ch).enabled) { byte_t *ch_ptr = reinterpret_cast(const_cast(inputBuffer))[ch]; ptrs.push_back(ch_ptr); @@ -504,8 +500,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, _incallback(d); } - if (outputBuffer) - { + if (outputBuffer) { assert(_outcallback); std::vector ptrs; ptrs.reserve(nenoutchannels); @@ -517,10 +512,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, 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) - { + for (us ch = ch_min; ch <= ch_max; ch++) { + if (outchannel_config.at(ch).enabled) { byte_t *ch_ptr = reinterpret_cast(outputBuffer)[ch]; ptrs.push_back(ch_ptr); } @@ -530,8 +523,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, _outcallback(d); // Copy over the buffer us j = 0; - for (auto ptr : ptrs) - { + for (auto ptr : ptrs) { d.copyToRaw(j, ptr); j++; }