LASP 1.0
Library for Acoustic Signal Processing
Loading...
Searching...
No Matches
lasp_rtaudiodaq.cpp
Go to the documentation of this file.
1/* #define DEBUGTRACE_ENABLED */
2#include <mutex>
3#include "debugtrace.hpp"
4#include "lasp_mathtypes.h"
5
6#include "lasp_rtaudiodaq.h"
7#if LASP_HAS_RTAUDIO == 1
8#include "RtAudio.h"
9#include "lasp_daq.h"
10#include <atomic>
11#include <cassert>
12
13using std::atomic;
14using std::cerr;
15using std::endl;
16using rte = std::runtime_error;
17using std::vector;
18using lck = std::scoped_lock<std::mutex>;
19
20class RtAudioDeviceInfo : public DeviceInfo {
21public:
26 int _api_devindex;
27 virtual std::unique_ptr<DeviceInfo> clone() const override {
28 return std::make_unique<DeviceInfo>(*this);
29 }
30};
31
32void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
33 DEBUGTRACE_ENTER;
34
35 vector<RtAudio::Api> apis;
36 RtAudio::getCompiledApi(apis);
37
38 for (auto api : apis) {
39 RtAudio rtaudio(api);
40 us count = rtaudio.getDeviceCount();
41 for (us devno = 0; devno < count; devno++) {
42
43 RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
44 if (!devinfo.probed) {
45 // Device capabilities not successfully probed. Continue to next
46 continue;
47 }
48 // "Our device info struct"
49 RtAudioDeviceInfo d;
50 switch (api) {
51 case RtAudio::LINUX_ALSA:
52 d.api = rtaudioAlsaApi;
53 break;
54 case RtAudio::LINUX_PULSE:
55 d.api = rtaudioPulseaudioApi;
56 break;
57 case RtAudio::WINDOWS_WASAPI:
58 d.api = rtaudioWasapiApi;
59 break;
60 case RtAudio::WINDOWS_DS:
61 d.api = rtaudioDsApi;
62 break;
63 case RtAudio::WINDOWS_ASIO:
64 d.api = rtaudioAsioApi;
65 break;
66 default:
67 cerr << "Not implemented RtAudio API, skipping." << endl;
68 continue;
69 break;
70 }
71
72 d.device_name = devinfo.name;
73 d._api_devindex = devno;
74
77 bool rate_48k_found = false;
78
79 for (us j = 0; j < devinfo.sampleRates.size(); j++) {
80
81 us rate_int = devinfo.sampleRates[j];
82
83 d.availableSampleRates.push_back((double)rate_int);
84
85 if (!rate_48k_found) {
86
87 if (devinfo.preferredSampleRate == rate_int) {
88 d.prefSampleRateIndex = j;
89 }
90
91 if (rate_int == 48000) {
92 d.prefSampleRateIndex = j;
93 rate_48k_found = true;
94 }
95 }
96 }
97
98 d.noutchannels = devinfo.outputChannels;
99 d.ninchannels = devinfo.inputChannels;
100
101 d.availableInputRanges = {1.0};
102
103 RtAudioFormat formats = devinfo.nativeFormats;
104 if (formats & RTAUDIO_SINT8) {
105 d.availableDataTypes.push_back(
107 }
108 if (formats & RTAUDIO_SINT16) {
109 d.availableDataTypes.push_back(
111 }
112 /* if (formats & RTAUDIO_SINT24) { *1/ */
113 /* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
114 */
115 /* } */
116 if (formats & RTAUDIO_SINT32) {
117 d.availableDataTypes.push_back(
119 }
120 if (formats & RTAUDIO_FLOAT64) {
121 d.availableDataTypes.push_back(
123 }
124 if (d.availableDataTypes.size() == 0) {
125 std::cerr << "RtAudio: No data types found in device!" << endl;
126 }
127
128 d.prefDataTypeIndex = d.availableDataTypes.size() - 1;
129
130 d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
131 d.prefFramesPerBlockIndex = 2;
132
133 devinfolist.push_back(std::make_unique<RtAudioDeviceInfo>(d));
134 }
135 }
136}
137
138static int mycallback(void *outputBuffer, void *inputBuffer,
139 unsigned int nFrames, double streamTime,
140 RtAudioStreamStatus status, void *userData);
141
142static void myerrorcallback(RtAudioError::Type, const string &errorText);
143
144class RtAudioDaq : public Daq {
145
146 RtAudio rtaudio;
147 const us nFramesPerBlock;
148
149 RtAudioDaq(const RtAudioDaq &) = delete;
150 RtAudioDaq &operator=(const RtAudioDaq &) = delete;
151
152 InDaqCallback _incallback;
153 OutDaqCallback _outcallback;
154
155 std::atomic<StreamStatus> _streamStatus{};
156
157public:
158 RtAudioDaq(const DeviceInfo &devinfo_gen, const DaqConfiguration &config)
159 : Daq(devinfo_gen, config), rtaudio(static_cast<RtAudio::Api>(
160 devinfo_gen.api.api_specific_subcode)),
161 nFramesPerBlock(Daq::framesPerBlock()) {
162
163 DEBUGTRACE_ENTER;
164
165 // We make sure not to run RtAudio in duplex mode. This seems to be buggy
166 // and untested. Better to use a hardware-type loopback into the system.
167 if (duplexMode()) {
168 throw rte("RtAudio backend cannot run in duplex mode.");
169 }
170 assert(!monitorOutput);
171 const RtAudioDeviceInfo &devinfo =
172 static_cast<const RtAudioDeviceInfo &>(devinfo_gen);
173
174 std::unique_ptr<RtAudio::StreamParameters> inParams, outParams;
175
176 if (neninchannels() > 0) {
177
178 inParams = std::make_unique<RtAudio::StreamParameters>();
179
183 inParams->firstChannel = 0;
184 inParams->nChannels = devinfo_gen.ninchannels;
185 inParams->deviceId = devinfo._api_devindex;
186
187 } else {
188
189 outParams = std::make_unique<RtAudio::StreamParameters>();
190
194 outParams->firstChannel = 0;
195 outParams->nChannels = devinfo_gen.noutchannels;
196 outParams->deviceId = devinfo._api_devindex;
197 }
198
199 RtAudio::StreamOptions streamoptions;
200 streamoptions.flags = RTAUDIO_HOG_DEVICE | RTAUDIO_NONINTERLEAVED;
201
202 streamoptions.numberOfBuffers = 2;
203 streamoptions.streamName = "LASP RtAudio DAQ stream";
204 streamoptions.priority = 0;
205
206 RtAudioFormat format;
207 using Dtype = DataTypeDescriptor::DataType;
208 const Dtype dtype = dataType();
209 switch (dtype) {
210 case Dtype::dtype_fl32:
211 DEBUGTRACE_PRINT("Datatype float32");
212 format = RTAUDIO_FLOAT32;
213 break;
214 case Dtype::dtype_fl64:
215 DEBUGTRACE_PRINT("Datatype float64");
216 format = RTAUDIO_FLOAT64;
217 break;
218 case Dtype::dtype_int8:
219 DEBUGTRACE_PRINT("Datatype int8");
220 format = RTAUDIO_SINT8;
221 break;
222 case Dtype::dtype_int16:
223 DEBUGTRACE_PRINT("Datatype int16");
224 format = RTAUDIO_SINT16;
225 break;
226 case Dtype::dtype_int32:
227 DEBUGTRACE_PRINT("Datatype int32");
228 format = RTAUDIO_SINT32;
229 break;
230 default:
231 throw rte("Invalid data type specified for DAQ stream.");
232 break;
233 }
234
235 // Copy here, as it is used to return the *actual* number of frames per
236 // block.
237 unsigned int nFramesPerBlock_copy = nFramesPerBlock;
238
239 // Final step: open the stream.
240 rtaudio.openStream(outParams.get(), inParams.get(), format,
241 static_cast<us>(samplerate()), &nFramesPerBlock_copy,
242 mycallback, (void *)this, &streamoptions,
243 &myerrorcallback);
244
245 if (nFramesPerBlock_copy != nFramesPerBlock) {
246 throw rte(string("Got different number of frames per block back from RtAudio "
247 "backend: ") + std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
248 }
249 }
250
251 virtual void start(InDaqCallback inCallback,
252 OutDaqCallback outCallback) override final {
253
254 DEBUGTRACE_ENTER;
255
256 assert(!monitorOutput);
257
258 if (getStreamStatus().runningOK()) {
259 throw rte("Stream already running");
260 }
261
262 // Logical XOR
263 if (inCallback && outCallback) {
264 throw rte("Either input or output stream possible for RtAudio. "
265 "Stream duplex mode not provided.");
266 }
267
268 if (neninchannels() > 0) {
269 if (!inCallback) {
270 throw rte(
271
272 "Input callback given, but stream does not provide input data");
273 }
274
275 _incallback = inCallback;
276 }
277 if (nenoutchannels() > 0) {
278 if (!outCallback) {
279 throw rte(
280 "Output callback given, but stream does not provide output data");
281 }
282 _outcallback = outCallback;
283 }
284
285 // Start the stream. Throws on error.
286 rtaudio.startStream();
287
288 // If we are here, we are running without errors.
289 StreamStatus status;
290 status.isRunning = true;
291 _streamStatus = status;
292 }
293
294 StreamStatus getStreamStatus() const override final { return _streamStatus; }
295
296 void stop() override final {
297 DEBUGTRACE_ENTER;
298 if (getStreamStatus().runningOK()) {
299 rtaudio.stopStream();
300 }
301 StreamStatus s = _streamStatus;
302 s.isRunning = false;
304 _streamStatus = s;
305 }
306
307 int streamCallback(void *outputBuffer, void *inputBuffer,
308 unsigned int nFrames, RtAudioStreamStatus status) {
309
310 DEBUGTRACE_ENTER;
311
312 using se = StreamStatus::StreamError;
313
314 int rval = 0;
315 auto stopWithError = [&](se e) {
316 DEBUGTRACE_PRINT("stopWithError");
317 StreamStatus stat = _streamStatus;
318 stat.errorType = e;
319 stat.isRunning = false;
320 _streamStatus = stat;
321 rval = 1;
322 };
323
324 switch (status) {
325 case RTAUDIO_INPUT_OVERFLOW:
326 stopWithError(se::inputXRun);
327 return 1;
328 break;
329 case RTAUDIO_OUTPUT_UNDERFLOW:
330 stopWithError(se::outputXRun);
331 return 1;
332 break;
333 default:
334 break;
335 }
336
337 const auto &dtype_descr = dtypeDescr();
338 const auto dtype = dataType();
339
340 const us neninchannels = this->neninchannels();
341 const us nenoutchannels = this->nenoutchannels();
342 const us sw = dtype_descr.sw;
343 if (nFrames != nFramesPerBlock) {
344 cerr << "RtAudio backend error: nFrames does not match block size!"
345 << endl;
346 stopWithError(se::logicError);
347 return 1;
348 }
349
350 if (inputBuffer) {
351 assert(_incallback);
352 std::vector<byte_t *> ptrs;
353 ptrs.reserve(neninchannels);
354
355 const us ch_min = getLowestEnabledInChannel();
356 const us ch_max = getHighestEnabledInChannel();
357 assert(ch_min < ninchannels);
358 assert(ch_max < ninchannels);
359
361 for (us ch = ch_min; ch <= ch_max; ch++) {
362 if (inchannel_config.at(ch).enabled) {
363 byte_t *ptr =
364 static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
365 ptrs.push_back(ptr);
366 }
367 }
368 DaqData d{nFramesPerBlock, neninchannels, dtype};
369 d.copyInFromRaw(ptrs);
370
371 _incallback(d);
372 }
373
374 if (outputBuffer) {
375 assert(_outcallback);
376 std::vector<byte_t *> ptrs;
377 ptrs.reserve(nenoutchannels);
378
379 /* outCallback */
380
381 const us ch_min = getLowestEnabledOutChannel();
382 const us ch_max = getHighestEnabledOutChannel();
383 assert(ch_min < noutchannels);
384 assert(ch_max < noutchannels);
386 for (us ch = ch_min; ch <= ch_max; ch++) {
387 if (outchannel_config.at(ch).enabled) {
388 ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
389 sw * ch * nFramesPerBlock);
390 }
391 }
392 DaqData d{nFramesPerBlock, nenoutchannels, dtype};
393
394 _outcallback(d);
395 // Copy over the buffer
396 us j = 0;
397 for (auto ptr : ptrs) {
398 d.copyToRaw(j, ptr);
399 j++;
400 }
401 }
402
403 return rval;
404 }
405
406 // RtAudio documentation says: if a stream is open, it will be stopped and
407 // closed automatically on deletion. Therefore the destructor here is a
408 // default one.
409 ~RtAudioDaq() = default;
410};
411
412std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
413 const DaqConfiguration &config) {
414 return std::make_unique<RtAudioDaq>(devinfo, config);
415}
416
417void myerrorcallback(RtAudioError::Type, const string &errorText) {
418 cerr << "RtAudio backend stream error: " << errorText << endl;
419}
420int mycallback(
421 void *outputBuffer, void *inputBuffer, unsigned int nFrames,
422 __attribute__((unused)) double streamTime, // Not used parameter streamTime
423 RtAudioStreamStatus status, void *userData) {
424
425 return static_cast<RtAudioDaq *>(userData)->streamCallback(
426 outputBuffer, inputBuffer, nFrames, status);
427}
428
429#endif // LASP_HAS_RTAUDIO == 1
Configuration of a DAQ device.
int getHighestEnabledOutChannel() const
Get the highest channel number from the list of enabled output channels.
int getHighestEnabledInChannel() const
Get the enabled highest channel number from the list of enabled input channels.
int getLowestEnabledInChannel() const
Get the lowest channel number from the list of enabled input channels.
int getLowestEnabledOutChannel() const
Get the lowest channel number from the list of enabled output channels.
Data coming from / going to DAQ. Non-interleaved format, which means data in buffer is ordered by cha...
Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a custom device,...
Definition lasp_daq.h:29
const DataTypeDescriptor & dtypeDescr() const
More elaborate description of the datatypes.
Definition lasp_daq.cpp:86
std::runtime_error rte
Definition lasp_daq.h:86
us neninchannels(bool include_monitorchannels=true) const
Returns the number of enabled input channels.
Definition lasp_daq.cpp:99
bool duplexMode() const
Whether the device runs in duplex mode (both input and output), or false if only input / only output.
Definition lasp_daq.h:224
virtual void start(InDaqCallback inCallback, OutDaqCallback outCallback)=0
Start the Daq.
DataTypeDescriptor::DataType dataType() const
Returns datatype (enum) corresponding to the datatype of the samples.
Definition lasp_daq.cpp:83
virtual void stop()=0
Stop the Daq device. Throws an exception if the device is not running at the time this method is call...
virtual StreamStatus getStreamStatus() const =0
Get stream status corresponding to current DAQ.
us nenoutchannels() const
Returns the number of enabled output channels.
Definition lasp_daq.cpp:104
DataType
Basic data types coming from a DAQ that we can deal with. The naming will be self-explainging.
Structure containing device info parameters.
unsigned ninchannels
The number of input channels available for the device.
unsigned noutchannels
The number of output channels available for the device.
virtual std::unique_ptr< DeviceInfo > clone() const
Clone a device info.
DeviceInfo & operator=(const DeviceInfo &)=delete
std::runtime_error rte
Definition lasp_daq.cpp:16
std::vector< std::unique_ptr< DeviceInfo > > DeviceInfoList
std::function< void(DaqData &)> OutDaqCallback
Definition lasp_daq.h:23
std::function< void(const DaqData &)> InDaqCallback
Definition lasp_daq.h:18
char byte_t
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist)
Append RtAudio backend devices to the list.
std::unique_ptr< Daq > createRtAudioDevice(const DeviceInfo &devinfo, const DaqConfiguration &config)
Method called from Daq::createDaq.
std::scoped_lock< std::mutex > lck
size_t us
We often use boolean values.
Definition lasp_types.h:29