Input stream and output stream both running. Added lockfree boost dependency to git modules, removed carma build as it is not required and every time again downloads Armadillo. Added functions to enable / disable all channels at once. Fixed a bug with RtAudio input streams. Fixed a bug in StreamMgr leading to segfaults (how to: use std::move ;)).

This commit is contained in:
Anne de Jong 2022-09-27 17:20:45 +02:00
parent 5ce5fba50b
commit 288e7c8dc5
17 changed files with 244 additions and 182 deletions

8
.gitmodules vendored
View File

@ -14,7 +14,7 @@
path = third_party/tomlplusplus
url = https://github.com/marzer/tomlplusplus
[submodule "third_party/lockfree"]
path = third_party/lockfree
path = third_party/boost/lockfree
url = https://github.com/boostorg/lockfree
[submodule "third_party/carma"]
path = third_party/carma
@ -22,3 +22,9 @@
[submodule "third_party/thread-pool"]
path = third_party/thread-pool
url = https://github.com/bshoshany/thread-pool
[submodule "third_party/rtaudio"]
path = third_party/rtaudio
url = https://github.com/thestk/rtaudio
[submodule "third_party/boost/core"]
path = third_party/boost/core
url = https://github.com/boostorg/core

View File

@ -80,7 +80,6 @@ set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
# ############################# End compilation flags
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
add_subdirectory(third_party/carma)
if(LASP_FFT_BACKEND STREQUAL "FFTW")
find_library(fftw3 REQUIRED NAMES fftw fftw3)

View File

@ -5,14 +5,15 @@ add_definitions(-DARMA_DONT_USE_WRAPPER)
configure_file(lasp_config.h.in lasp_config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(SYSTEM ../../third_party/carma/include)
include_directories(SYSTEM
../../third_party/carma/extern/armadillo-code/include)
include_directories(SYSTEM
../../third_party/carma/extern/pybind11/include)
include_directories(SYSTEM
../../third_party/carma/extern/armadillo-code/include)
include_directories(SYSTEM ../../third_party/carma/include)
include_directories(../../third_party/DebugTrace-cpp/include)
include_directories(../../third_party/lockfreeThreadsafe/include)
include_directories(../../third_party/boost/core/include)
include_directories(../../third_party/boost/lockfree/include)
include_directories(../../third_party/gsl-lite/include)
include_directories(../../third_party/tomlplusplus/include)
include_directories(../../third_party/thread-pool)

View File

@ -2,8 +2,6 @@
#include "debugtrace.hpp"
#include "lasp_daqconfig.h"
DEBUGTRACE_VARIABLES;
#include "lasp_daq.h"
#if LASP_HAS_ULDAQ == 1
#include "lasp_uldaq.h"
@ -57,6 +55,7 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
}
double Daq::samplerate() const {
DEBUGTRACE_ENTER;
return availableSampleRates.at(sampleRateIndex);
}

View File

@ -1,4 +1,4 @@
#define DEBUGTRACE_ENABLED
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_daqconfig.h"

View File

@ -315,6 +315,28 @@ public:
* enabled.
*/
int getLowestOutChannel() const;
/**
* @brief Set all input channels to enabled / disabled state.
*
* @param val true if enabled, false if disabled.
*/
void setAllInputEnabled(bool val) {
for(auto& ch: inchannel_config) {
ch.enabled = val;
}
}
/**
* @brief Set all output channels to enabled / disabled state.
*
* @param val true if enabled, false if disabled.
*/
void setAllOutputEnabled(bool val) {
for(auto& ch: outchannel_config) {
ch.enabled = val;
}
}
};
/**
* @}

View File

@ -1,6 +1,7 @@
#include "lasp_daqdata.h"
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include <cassert>
#include "lasp_daqdata.h"
DEBUGTRACE_VARIABLES;

View File

@ -17,6 +17,7 @@ using std::vector;
DEBUGTRACE_VARIABLES;
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
DEBUGTRACE_ENTER;
vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
@ -34,25 +35,25 @@ void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
// "Our device info struct"
DeviceInfo 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;
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;
@ -82,7 +83,7 @@ void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
}
/* if (formats & RTAUDIO_SINT32) { */
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
*/
*/
/* } */
if (formats & RTAUDIO_SINT32) {
d.availableDataTypes.push_back(
@ -106,8 +107,9 @@ void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
}
}
static int mycallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames,
double streamTime, RtAudioStreamStatus status, void *userData);
static int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, double streamTime,
RtAudioStreamStatus status, void *userData);
static void myerrorcallback(RtAudioError::Type, const string &errorText);
@ -124,91 +126,91 @@ class RtAudioDaq : public Daq {
std::atomic<StreamStatus> _streamStatus{};
public:
public:
RtAudioDaq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: Daq(devinfo, config),
rtaudio(static_cast<RtAudio::Api>(devinfo.api.api_specific_subcode)),
nFramesPerBlock(Daq::framesPerBlock()) {
: Daq(devinfo, config),
rtaudio(static_cast<RtAudio::Api>(devinfo.api.api_specific_subcode)),
nFramesPerBlock(Daq::framesPerBlock()) {
DEBUGTRACE_ENTER;
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 runtime_error("RtAudio backend cannot run in duplex mode.");
// 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 runtime_error("RtAudio backend cannot run in duplex mode.");
}
assert(!monitorOutput);
std::unique_ptr<RtAudio::StreamParameters> inParams, outParams;
if (neninchannels() > 0) {
inParams = std::make_unique<RtAudio::StreamParameters>();
// +1 to get the count.
inParams->nChannels = getHighestInChannel() + 1;
if (inParams->nChannels < 1) {
throw runtime_error("Invalid input number of channels");
}
assert(!monitorOutput);
inParams->firstChannel = 0;
inParams->deviceId = devinfo.api_specific_devindex;
std::unique_ptr<RtAudio::StreamParameters> inParams, outParams;
} else {
if (neninchannels() > 0) {
outParams = std::make_unique<RtAudio::StreamParameters>();
inParams = std::make_unique<RtAudio::StreamParameters>();
// +1 to get the count.
inParams->nChannels = getHighestInChannel() + 1;
if (inParams->nChannels < 1) {
throw runtime_error("Invalid input number of channels");
}
inParams->firstChannel = 0;
inParams->deviceId = devinfo.api_specific_devindex;
} else {
outParams = std::make_unique<RtAudio::StreamParameters>();
outParams->nChannels = getHighestOutChannel() + 1;
if (outParams->nChannels < 1) {
throw runtime_error("Invalid output number of channels");
}
outParams->firstChannel = 0;
outParams->deviceId = devinfo.api_specific_devindex;
outParams->nChannels = getHighestOutChannel() + 1;
if (outParams->nChannels < 1) {
throw runtime_error("Invalid output number of channels");
}
RtAudio::StreamOptions streamoptions;
streamoptions.flags = RTAUDIO_HOG_DEVICE | RTAUDIO_NONINTERLEAVED;
streamoptions.numberOfBuffers = 2;
streamoptions.streamName = "RtAudio stream";
streamoptions.priority = 0;
RtAudioFormat format;
using Dtype = DataTypeDescriptor::DataType;
const Dtype dtype = dataType();
switch (dtype) {
case Dtype::dtype_fl32:
format = RTAUDIO_FLOAT32;
break;
case Dtype::dtype_fl64:
format = RTAUDIO_FLOAT64;
break;
case Dtype::dtype_int8:
format = RTAUDIO_SINT8;
break;
case Dtype::dtype_int16:
format = RTAUDIO_SINT16;
break;
case Dtype::dtype_int32:
format = RTAUDIO_SINT32;
break;
default:
throw runtime_error("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);
outParams->firstChannel = 0;
outParams->deviceId = devinfo.api_specific_devindex;
}
RtAudio::StreamOptions streamoptions;
streamoptions.flags = RTAUDIO_HOG_DEVICE | RTAUDIO_NONINTERLEAVED;
streamoptions.numberOfBuffers = 2;
streamoptions.streamName = "RtAudio stream";
streamoptions.priority = 0;
RtAudioFormat format;
using Dtype = DataTypeDescriptor::DataType;
const Dtype dtype = dataType();
switch (dtype) {
case Dtype::dtype_fl32:
format = RTAUDIO_FLOAT32;
break;
case Dtype::dtype_fl64:
format = RTAUDIO_FLOAT64;
break;
case Dtype::dtype_int8:
format = RTAUDIO_SINT8;
break;
case Dtype::dtype_int16:
format = RTAUDIO_SINT16;
break;
case Dtype::dtype_int32:
format = RTAUDIO_SINT32;
break;
default:
throw runtime_error("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);
}
virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override {
OutDaqCallback outCallback) override final {
DEBUGTRACE_ENTER;
@ -221,24 +223,25 @@ class RtAudioDaq : public Daq {
// Logical XOR
if (inCallback && outCallback) {
throw runtime_error("Either input or output stream possible for RtAudio. "
"Stream duplex mode not provided.");
"Stream duplex mode not provided.");
}
if (inCallback) {
_incallback = inCallback;
if (neninchannels()==0) {
if (neninchannels() == 0) {
throw runtime_error(
"Input callback given, but stream does not provide input data");
}
}
if (outCallback) {
_outcallback = outCallback;
if (nenoutchannels()==0) {
if (nenoutchannels() == 0) {
throw runtime_error(
"Output callback given, but stream does not provide output data");
}
}
// Start the stream. Throws on error.
rtaudio.startStream();
// If we are here, we are running without errors.
@ -247,9 +250,9 @@ class RtAudioDaq : public Daq {
_streamStatus = status;
}
StreamStatus getStreamStatus() const override { return _streamStatus; }
StreamStatus getStreamStatus() const override final { return _streamStatus; }
void stop() override {
void stop() override final {
DEBUGTRACE_ENTER;
if (getStreamStatus().runningOK()) {
rtaudio.stopStream();
@ -261,11 +264,10 @@ class RtAudioDaq : public Daq {
}
int streamCallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, double streamTime,
unsigned int nFrames, double streamTime,
RtAudioStreamStatus status) {
RtAudioStreamStatus status) {
/* DEBUGTRACE_ENTER; */
DEBUGTRACE_ENTER;
using se = StreamStatus::StreamError;
@ -280,16 +282,16 @@ class RtAudioDaq : public Daq {
};
switch (status) {
case RTAUDIO_INPUT_OVERFLOW:
stopWithError(se::inputXRun);
return 1;
break;
case RTAUDIO_OUTPUT_UNDERFLOW:
stopWithError(se::outputXRun);
return 1;
break;
default:
break;
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 = DataTypeDescriptor();
@ -299,25 +301,27 @@ class RtAudioDaq : public Daq {
us sw = dtype_descr.sw;
if (nFrames != nFramesPerBlock) {
cerr << "RtAudio backend error: nFrames does not match block size!"
<< endl;
<< endl;
stopWithError(se::logicError);
return 1;
}
if (inputBuffer) {
assert(_incallback);
std::vector<uint8_t *> ptrs;
ptrs.reserve(neninchannels);
/* DaqData(neninchannels_inc_mon, nFramesPerBlock, dtype); */
us i = 0;
for (int ch = getLowestInChannel(); ch <= getHighestInChannel(); ch++) {
if (inchannel_config.at(ch).enabled) {
ptrs.push_back(&static_cast<uint8_t *>(
inputBuffer)[sw * ninchannels * ch * nFramesPerBlock]);
ptrs.push_back(static_cast<uint8_t *>(inputBuffer) +
sw * i * nFramesPerBlock);
i++;
}
}
DaqData d{neninchannels, nFramesPerBlock, dtype};
d.copyInFromRaw(ptrs);
assert(_incallback);
bool ret = _incallback(d);
if (!ret) {
stopWithError(se::noError);
@ -326,20 +330,24 @@ class RtAudioDaq : public Daq {
}
if (outputBuffer) {
assert(_outcallback);
std::vector<uint8_t *> ptrs;
ptrs.reserve(nenoutchannels);
/* outCallback */
for (int ch = 0; ch <= getHighestOutChannel(); ch++) {
/* DEBUGTRACE_PRINT(outchannel_config.at(ch).enabled); */
us i = 0;
for (int ch = getLowestOutChannel(); ch <= getHighestOutChannel(); ch++) {
if (outchannel_config.at(ch).enabled) {
ptrs.push_back(&(static_cast<uint8_t *>(
outputBuffer)[sw * nenoutchannels * ch * nFramesPerBlock]));
ptrs.push_back(static_cast<uint8_t *>(outputBuffer) +
sw * i * nFramesPerBlock);
i++;
}
}
DaqData d{nenoutchannels, nFramesPerBlock, dtype};
assert(_outcallback);
bool ret = _outcallback(d);
if (!ret) {
stopWithError(se::noError);
@ -362,7 +370,7 @@ class RtAudioDaq : public Daq {
};
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
const DaqConfiguration &config) {
return std::make_unique<RtAudioDaq>(devinfo, config);
}
@ -370,7 +378,7 @@ void myerrorcallback(RtAudioError::Type, const string &errorText) {
cerr << "RtAudio backend stream error: " << errorText << endl;
}
int mycallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames,
double streamTime, RtAudioStreamStatus status, void *userData) {
double streamTime, RtAudioStreamStatus status, void *userData) {
return static_cast<RtAudioDaq *>(userData)->streamCallback(
outputBuffer, inputBuffer, nFrames, streamTime, status);

View File

@ -57,7 +57,14 @@ void StreamMgr::checkRightThread() const {
void StreamMgr::rescanDAQDevices(bool background,
std::function<void()> callback) {
DEBUGTRACE_ENTER;
checkRightThread();
if(!_devices_mtx.try_lock()) {
throw rte("A background DAQ device scan is probably already running");
}
_devices_mtx.unlock();
if (_inputStream || _outputStream) {
throw rte("Rescanning DAQ devices only possible when no stream is running");
}
@ -70,6 +77,7 @@ void StreamMgr::rescanDAQDevices(bool background,
}
}
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
DEBUGTRACE_ENTER;
std::scoped_lock lck(_devices_mtx);
_devices = DeviceInfo::getDeviceInfo();
if (callback) {
@ -79,6 +87,7 @@ void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
bool StreamMgr::inCallback(const DaqData &data) {
/* DEBUGTRACE_ENTER; */
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
for (auto &handler : _inDataHandlers) {
@ -122,8 +131,6 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
/* DEBUGTRACE_ENTER; */
assert(data.nframes == signal.size());
/* cerr << "SFSG: data.nframes:" << data.nframes << endl; */
/* cerr << "SFSG: data.nchannels:" << data.nchannels << endl; */
T *res = reinterpret_cast<T *>(data.raw_ptr());
if (std::is_floating_point<T>()) {
for (us ch = 0; ch < data.nchannels; ch++) {
@ -266,7 +273,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
if (isInput) {
_inputStream = std::move(daq);
if (daq->duplexMode()) {
if (_inputStream->duplexMode()) {
assert(!_outputStream);
}
} else {

View File

@ -13,48 +13,48 @@ class StreamMgr;
class InDataHandler {
protected:
StreamMgr &_mgr;
protected:
StreamMgr &_mgr;
#if LASP_DEBUG == 1
std::atomic<bool> stopCalled{false};
std::atomic<bool> stopCalled{false};
#endif
public:
virtual ~InDataHandler();
public:
virtual ~InDataHandler();
/**
* @brief When constructed, the handler is added to the stream manager, which
* will call the handlers's inCallback() until stop() is called.
*
* @param mgr Stream manager.
*/
InDataHandler(StreamMgr &mgr);
/**
* @brief When constructed, the handler is added to the stream manager, which
* will call the handlers's inCallback() until stop() is called.
*
* @param mgr Stream manager.
*/
InDataHandler(StreamMgr &mgr);
/**
* @brief This function is called when input data from a DAQ is available.
*
* @param daqdata Input data from DAQ
*
* @return true if no error. False to stop the stream from running.
*/
virtual bool inCallback(const DaqData &daqdata) = 0;
/**
* @brief This function is called when input data from a DAQ is available.
*
* @param daqdata Input data from DAQ
*
* @return true if no error. False to stop the stream from running.
*/
virtual bool inCallback(const DaqData &daqdata) = 0;
/**
* @brief This function should be called from the constructor of the
* implementation of InDataHandler. It will start the stream's calling of
* inCallback().
*/
void start();
/**
* @brief This function should be called from the constructor of the
* implementation of InDataHandler. It will start the stream's calling of
* inCallback().
*/
void start();
/**
* @brief This function should be called from the destructor of derived
* classes, to disable the calls to inCallback(), such that proper
* destruction of the object is allowed and no other threads call methods
* from the object. It removes the inCallback() from the callback list of the
* StreamMgr(). **Failing to call this function results in deadlocks, errors
* like "pure virtual function called", or other**.
*/
void stop();
/**
* @brief This function should be called from the destructor of derived
* classes, to disable the calls to inCallback(), such that proper
* destruction of the object is allowed and no other threads call methods
* from the object. It removes the inCallback() from the callback list of the
* StreamMgr(). **Failing to call this function results in deadlocks, errors
* like "pure virtual function called", or other**.
*/
void stop();
};
/**
@ -101,7 +101,7 @@ class StreamMgr {
// Singleton, no public destructor
~StreamMgr();
public:
public:
enum class StreamType : us {
/**
* @brief Input stream
@ -136,15 +136,16 @@ public:
/**
* @brief Triggers a background scan of the DAQ devices, which updates the
* internally stored list of devices.
* internally stored list of devices. Throws a runtime error when a
* background thread is already scanning for devices.
*
* @param background Perform searching for DAQ devices in the background. If
* set to true, the function returns immediately.
* @param callback Function to call when complete.
*/
void
rescanDAQDevices(bool background = false,
std::function<void()> callback = std::function<void()>());
rescanDAQDevices(bool background = false,
std::function<void()> callback = std::function<void()>());
/**
* @brief Start a stream based on given configuration.
@ -163,6 +164,17 @@ public:
bool isStreamRunningOK(const StreamType type) const {
return getStreamStatus(type).runningOK();
}
bool isStreamRunning(const StreamType type) const {
switch(type) {
case(StreamType::input):
return bool(_inputStream);
break;
case(StreamType::output):
return bool(_outputStream);
break;
}
return false;
}
/**
* @brief Get the streamstatus object corresponding to a given stream.
@ -205,7 +217,7 @@ public:
*/
void setSiggen(std::shared_ptr<Siggen> s);
private:
private:
bool inCallback(const DaqData &data);
bool outCallback(DaqData &data);

View File

@ -12,7 +12,7 @@ void init_daq(py::module &m) {
py::class_<Daq, DaqConfiguration, DeviceInfo> daq(m, "Daq");
/// Daq::StreamStatus
py::class_<Daq::StreamStatus> ss(m, "StreamStatus");
py::class_<Daq::StreamStatus> ss(daq, "StreamStatus");
ss.def("error", &Daq::StreamStatus::error);
ss.def("runningOK", &Daq::StreamStatus::runningOK);
ss.def_readonly("isRunning", &Daq::StreamStatus::isRunning);
@ -29,6 +29,8 @@ void init_daq(py::module &m) {
.value("apiSpecificError",
Daq::StreamStatus::StreamError::apiSpecificError);
ss.def("errorMsg", &Daq::StreamStatus::errorMsg);
/// Daq
daq.def("neninchannels", &Daq::neninchannels);
daq.def("nenoutchannels", &Daq::nenoutchannels);

View File

@ -91,4 +91,6 @@ void init_daqconfiguration(py::module &m) {
&DaqConfiguration::inchannel_config);
daqconfig.def_readwrite("outchannel_config",
&DaqConfiguration::outchannel_config);
daqconfig.def("setAllInputEnabled", &DaqConfiguration::setAllInputEnabled);
daqconfig.def("setAllOutputEnabled", &DaqConfiguration::setAllOutputEnabled);
}

View File

@ -56,7 +56,7 @@ void init_dsp(py::module &m) {
});
/// BiquadBank
py::class_<BiquadBank> bqb(m, "BiquadBank");
py::class_<BiquadBank, std::shared_ptr<BiquadBank>> bqb(m, "BiquadBank");
bqb.def(py::init<const dmat&,const vd*>());
bqb.def("setGains",&BiquadBank::setGains);
bqb.def("filter",&BiquadBank::filter);

View File

@ -29,4 +29,5 @@ void init_streammgr(py::module &m) {
smgr.def("getDeviceInfo", &StreamMgr::getDeviceInfo);
smgr.def("getStreamStatus", &StreamMgr::getStreamStatus);
smgr.def("isStreamRunningOK", &StreamMgr::isStreamRunningOK);
smgr.def("isStreamRunning", &StreamMgr::isStreamRunning);
}

1
third_party/boost/core vendored Submodule

@ -0,0 +1 @@
Subproject commit b407b5d87df30f75ca501c1b6f2930c0913d4ca7

1
third_party/rtaudio vendored Submodule

@ -0,0 +1 @@
Subproject commit a3e9aa621e65a0744f125e9740d057a4d088e0e9