LASP 1.0
Library for Acoustic Signal Processing
Loading...
Searching...
No Matches
lasp_portaudiodaq.cpp
Go to the documentation of this file.
1/* #define DEBUGTRACE_ENABLED */
2#include "debugtrace.hpp"
3#include "lasp_config.h"
4
5#if LASP_HAS_PORTAUDIO == 1
6#include "lasp_portaudiodaq.h"
7#include "portaudio.h"
8#include <gsl-lite/gsl-lite.hpp>
9#include <mutex>
10#include <string>
11
12using rte = std::runtime_error;
13using std::cerr;
14using std::endl;
15using std::string;
16using std::to_string;
17
18inline void throwIfError(PaError e) {
19 DEBUGTRACE_ENTER;
20 if (e != paNoError) {
21 throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
22 }
23}
24
28class OurPaDeviceInfo : public DeviceInfo {
29public:
33 PaDeviceInfo _paDevInfo;
34
35 virtual std::unique_ptr<DeviceInfo> clone() const override final {
36 return std::make_unique<OurPaDeviceInfo>(*this);
37 }
38};
39
40void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
41 DEBUGTRACE_ENTER;
42 try {
43
44 PaError err = Pa_Initialize();
48 throwIfError(err);
49
50 auto fin = gsl::finally([&err] {
51 DEBUGTRACE_PRINT("Terminating PortAudio instance");
52 err = Pa_Terminate();
53 if (err != paNoError) {
54 cerr << "Error terminating PortAudio. Do not know what to do." << endl;
55 }
56 });
57
58 /* const PaDeviceInfo *deviceInfo; */
59 const int numDevices = Pa_GetDeviceCount();
60 if (numDevices < 0) {
61 throw rte("PortAudio could not find any devices");
62 }
63 for (us i = 0; i < (us)numDevices; i++) {
64 /* DEBUGTRACE_PRINT(i); */
65
66 const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
67 if (!deviceInfo) {
68 throw rte("No device info struct returned");
69 }
70 OurPaDeviceInfo d;
71 d._paDevInfo = *deviceInfo;
72 d.api = portaudioApi;
73 d.device_name = deviceInfo->name;
74
75 d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
78
79 d.prefDataTypeIndex = 2;
80
81 d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
82 22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
83 88200.0, 96000.0, 192000.0};
84 d.prefSampleRateIndex = 9;
85
86 d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
87 d.prefFramesPerBlockIndex = 2;
88
89 d.availableInputRanges = {1.0};
90
91 d.ninchannels = deviceInfo->maxInputChannels;
92 d.noutchannels = deviceInfo->maxOutputChannels;
93
94 devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
95 }
96 }
97
98 catch (rte &e) {
99 cerr << "PortAudio backend error: " << e.what() << std::endl;
100 return;
101 }
102}
103
118static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
119 unsigned long framesPerBuffer,
120 const PaStreamCallbackTimeInfo *timeInfo,
121 PaStreamCallbackFlags statusFlags, void *userData);
122
123class PortAudioDaq : public Daq {
124 bool _shouldPaTerminate = false;
125 PaStream *_stream = nullptr;
126 std::atomic<StreamStatus::StreamError> _streamError =
128 InDaqCallback _incallback;
129 OutDaqCallback _outcallback;
130
131public:
132 PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
133 const DaqConfiguration &config);
134
135 void start(InDaqCallback inCallback,
136 OutDaqCallback outCallback) override final;
137 void stop() override final;
138
139 StreamStatus getStreamStatus() const override final;
140
152 int memberPaCallback(const void *inputBuffer, void *outputBuffer,
153 unsigned long framesPerBuffer,
154 const PaStreamCallbackTimeInfo *timeInfo,
155 PaStreamCallbackFlags statusFlags);
156 ~PortAudioDaq();
157};
158
159std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
160 const DaqConfiguration &config) {
161
162 const OurPaDeviceInfo *_info =
163 dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
164 if (_info == nullptr) {
165 throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
166 }
167 return std::make_unique<PortAudioDaq>(*_info, config);
168}
169
170static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
171 unsigned long framesPerBuffer,
172 const PaStreamCallbackTimeInfo *timeInfo,
173 PaStreamCallbackFlags statusFlags, void *userData) {
174 return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
175 inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
176}
177
178PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
179 const DaqConfiguration &config)
180 : Daq(devinfo_gen, config) {
181
182 DEBUGTRACE_ENTER;
183 PaError err = Pa_Initialize();
187 throwIfError(err);
188
189 // OK, Pa_Initialize successfully finished, it means we have to clean up with
190 // Pa_Terminate in the destructor.
191 _shouldPaTerminate = true;
192
193 // Going to find the device in the list. If its there, we have to retrieve
194 // the index, as this is required in the PaStreamParameters struct
195 int devindex = -1;
196 for (int i = 0; i < Pa_GetDeviceCount(); i++) {
197 bool ok = true;
198 const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
199 if (!info) {
200 throw rte("No device structure returned from PortAudio");
201 }
202 ok &= string(info->name) == devinfo_gen._paDevInfo.name;
203 ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
204 ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
205 ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
206 ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
207
208 if (ok) {
209 devindex = i;
210 }
211 }
212 if (devindex < 0) {
213 throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
214 }
215
216 using Dtype = DataTypeDescriptor::DataType;
217 const Dtype dtype = dataType();
218 // Sample format is bit flag
219 PaSampleFormat format = paNonInterleaved;
220 switch (dtype) {
221 case Dtype::dtype_fl32:
222 DEBUGTRACE_PRINT("Datatype float32");
223 format |= paFloat32;
224 break;
225 case Dtype::dtype_fl64:
226 DEBUGTRACE_PRINT("Datatype float64");
227 throw rte("Invalid data type specified for DAQ stream.");
228 break;
229 case Dtype::dtype_int8:
230 DEBUGTRACE_PRINT("Datatype int8");
231 format |= paInt8;
232 break;
233 case Dtype::dtype_int16:
234 DEBUGTRACE_PRINT("Datatype int16");
235 format |= paInt16;
236 break;
237 case Dtype::dtype_int32:
238 DEBUGTRACE_PRINT("Datatype int32");
239 format |= paInt32;
240 break;
241 default:
242 throw rte("Invalid data type specified for DAQ stream.");
243 break;
244 }
245
246 std::unique_ptr<PaStreamParameters> instreamParams;
247 std::unique_ptr<PaStreamParameters> outstreamParams;
248
249 if (neninchannels() > 0) {
250 instreamParams = std::make_unique<PaStreamParameters>(
251 PaStreamParameters({.device = devindex,
252 .channelCount = (int)neninchannels(),
253 .sampleFormat = format,
254 .suggestedLatency = framesPerBlock() / samplerate(),
255 .hostApiSpecificStreamInfo = nullptr}));
256 }
257 if (nenoutchannels() > 0) {
258 outstreamParams = std::make_unique<PaStreamParameters>(
259 PaStreamParameters({.device = devindex,
260 .channelCount = (int)nenoutchannels(),
261 .sampleFormat = format,
262 .suggestedLatency = framesPerBlock() / samplerate(),
263 .hostApiSpecificStreamInfo = nullptr}));
264 }
265
266 // Next step: check whether we are OK
267 err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
268 samplerate());
269 throwIfError(err);
270
271 err = Pa_OpenStream(&_stream, // stream
272 instreamParams.get(), // inputParameters
273 outstreamParams.get(), // outputParameters
274 samplerate(), // yeah,
275 framesPerBlock(), // framesPerBuffer
276 paNoFlag, // streamFlags
277 rawPaCallback, this);
278 throwIfError(err);
279}
280
281void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
282 DEBUGTRACE_ENTER;
283 assert(_stream);
284 if (Pa_IsStreamActive(_stream)) {
285 throw rte("Stream is already running");
286 }
287
288 // Logical XOR
289 if (inCallback && outCallback) {
290 throw rte("Either input or output stream possible for RtAudio. "
291 "Stream duplex mode not provided.");
292 }
293
294 if (neninchannels() > 0) {
295 if (!inCallback) {
296 throw rte(
297
298 "Input callback given, but stream does not provide input data");
299 }
300
301 _incallback = inCallback;
302 }
303 if (nenoutchannels() > 0) {
304 if (!outCallback) {
305 throw rte(
306 "Output callback given, but stream does not provide output data");
307 }
308 _outcallback = outCallback;
309 }
310
311 PaError err = Pa_StartStream(_stream);
312 throwIfError(err);
313}
314void PortAudioDaq::stop() {
315 DEBUGTRACE_ENTER;
316 assert(_stream);
317 if (Pa_IsStreamStopped(_stream)) {
318 throw rte("Stream is already stopped");
319 }
320 PaError err = Pa_StopStream(_stream);
321 throwIfError(err);
322}
323Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
324 Daq::StreamStatus status;
325 // Copy over atomic flag.
326 status.errorType = _streamError;
327 // Check if stream is still running.
328 if (_stream) {
329 if (Pa_IsStreamActive(_stream)) {
330 status.isRunning = true;
331 }
332 }
333 return status;
334}
335
336PortAudioDaq::~PortAudioDaq() {
337 PaError err;
338 if (_stream) {
339 if (Pa_IsStreamActive(_stream)) {
340 stop();
341 }
342
343 err = Pa_CloseStream(_stream);
344 _stream = nullptr;
345 if (err != paNoError) {
346 cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
347 }
348 assert(_shouldPaTerminate);
349 }
350
351 if (_shouldPaTerminate) {
352 err = Pa_Terminate();
353 if (err != paNoError) {
354 cerr << "Error terminating PortAudio. Do not know what to do." << endl;
355 }
356 }
357}
358int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
359 unsigned long framesPerBuffer,
360 const PaStreamCallbackTimeInfo *timeInfo,
361 PaStreamCallbackFlags statusFlags) {
362
363 DEBUGTRACE_ENTER;
365 if (statusFlags & paPrimingOutput) {
366 // Initial output buffers generated. So nothing with input yet
367 return paContinue;
368 }
369 if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
370 _streamError = se::inputXRun;
371 return paAbort;
372 }
373 if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
374 _streamError = se::outputXRun;
375 return paAbort;
376 }
377 if (framesPerBuffer != framesPerBlock()) {
378 cerr << "Logic error: expected a block size of: " << framesPerBlock()
379 << endl;
380 _streamError = se::logicError;
381 return paAbort;
382 }
383
384 const us neninchannels = this->neninchannels();
385 const us nenoutchannels = this->nenoutchannels();
386 const auto &dtype_descr = dtypeDescr();
387 const auto dtype = dataType();
388 const us sw = dtype_descr.sw;
389 if (inputBuffer) {
390 assert(_incallback);
391 std::vector<byte_t *> ptrs;
392 ptrs.reserve(neninchannels);
393
394 const us ch_min = getLowestEnabledInChannel();
395 const us ch_max = getHighestEnabledInChannel();
396 assert(ch_min < ninchannels);
397 assert(ch_max < ninchannels);
398
401 for (us ch = ch_min; ch <= ch_max; ch++) {
402 if (inchannel_config.at(ch).enabled) {
403 byte_t *ch_ptr =
404 reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
405 ptrs.push_back(ch_ptr);
406 }
407 }
408 DaqData d{framesPerBuffer, neninchannels, dtype};
409 d.copyInFromRaw(ptrs);
410
411 _incallback(d);
412 }
413
414 if (outputBuffer) {
415 assert(_outcallback);
416 std::vector<byte_t *> ptrs;
417 ptrs.reserve(nenoutchannels);
418
419 /* outCallback */
420
421 const us ch_min = getLowestEnabledOutChannel();
422 const us ch_max = getHighestEnabledOutChannel();
423 assert(ch_min < noutchannels);
424 assert(ch_max < noutchannels);
426 for (us ch = ch_min; ch <= ch_max; ch++) {
427 if (outchannel_config.at(ch).enabled) {
428 byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
429 ptrs.push_back(ch_ptr);
430 }
431 }
432 DaqData d{framesPerBuffer, nenoutchannels, dtype};
433
434 _outcallback(d);
435 // Copy over the buffer
436 us j = 0;
437 for (auto ptr : ptrs) {
438 d.copyToRaw(j, ptr);
439 j++;
440 }
441 }
442
443 return paContinue;
444}
445#endif
Information regarding a stream.
Definition lasp_daq.h:39
StreamError errorType
Definition lasp_daq.h:72
Configuration of a DAQ device.
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
std::runtime_error rte
Definition lasp_daq.h:86
virtual void start(InDaqCallback inCallback, OutDaqCallback outCallback)=0
Start the Daq.
virtual void stop()=0
Stop the Daq device. Throws an exception if the device is not running at the time this method is call...
DataType
Basic data types coming from a DAQ that we can deal with. The naming will be self-explainging.
Structure containing device info parameters.
virtual std::unique_ptr< DeviceInfo > clone() const
Clone a device info.
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
std::unique_ptr< Daq > createPortAudioDevice(const DeviceInfo &devinfo, const DaqConfiguration &config)
Method called from Daq::createDaq.
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
Append PortAudio backend devices to the list.
size_t us
We often use boolean values.
Definition lasp_types.h:29