YARP
Yet Another Robot Platform
PortAudioDeviceDriver.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2006-2021 Istituto Italiano di Tecnologia (IIT)
3 * SPDX-License-Identifier: LGPL-2.1-or-later
4 */
5
7
8#include <cstdio>
9#include <cstdlib>
10#include <cstring>
11#include <portaudio.h>
13#include <yarp/dev/api.h>
14
15#include <yarp/os/Time.h>
17#include <yarp/os/LogStream.h>
18
19using namespace yarp::os;
20using namespace yarp::dev;
21
22#define SLEEP_TIME 0.005f
23
24#if 0
25#define PA_SAMPLE_TYPE paFloat32
26typedef float SAMPLE;
27#define SAMPLE_SILENCE (0.0f)
28#elif 1
29#define PA_SAMPLE_TYPE paInt16
30typedef short SAMPLE;
31#define SAMPLE_SILENCE (0)
32#elif 1
33#define PA_SAMPLE_TYPE paInt8
34typedef char SAMPLE;
35#define SAMPLE_SILENCE (0)
36#else
37#define PA_SAMPLE_TYPE paUInt8
38typedef unsigned char SAMPLE;
39#define SAMPLE_SILENCE (128)
40#define SAMPLE_UNSIGNED
41#endif
42
43namespace {
44YARP_LOG_COMPONENT(PORTAUDIO, "yarp.devices.portaudio")
45}
46
47/* This routine will be called by the PortAudio engine when audio is needed.
48** It may be called at interrupt level on some machines so don't do anything
49** that could mess up the system like calling malloc() or free().
50*/
51static int bufferIOCallback( const void *inputBuffer, void *outputBuffer,
52 unsigned long framesPerBuffer,
53 const PaStreamCallbackTimeInfo* timeInfo,
54 PaStreamCallbackFlags statusFlags,
55 void *userData )
56{
57 auto* dataBuffers = static_cast<circularDataBuffers*>(userData);
58 CircularAudioBuffer_16t *playdata = dataBuffers->playData;
59 CircularAudioBuffer_16t *recdata = dataBuffers->recData;
60 int num_rec_channels = dataBuffers->numRecChannels;
61 int num_play_channels = dataBuffers->numPlayChannels;
62 int finished = paComplete;
63
64 if (dataBuffers->canRec)
65 {
66 const auto* rptr = (const SAMPLE*)inputBuffer;
67 unsigned int framesToCalc;
68 unsigned int i;
69 size_t framesLeft = (recdata->getMaxSize().getSamples()* recdata->getMaxSize().getChannels()) -
70 (recdata->size().getSamples() * recdata->size().getChannels());
71
72 YARP_UNUSED(outputBuffer);
73 YARP_UNUSED(timeInfo);
74 YARP_UNUSED(statusFlags);
75 YARP_UNUSED(userData);
76
77 if( framesLeft/ num_rec_channels < framesPerBuffer )
78 {
79 framesToCalc = framesLeft/ num_rec_channels;
80#ifdef STOP_REC_ON_EMPTY_BUFFER
81 //if we return paComplete, then the callback is not called anymore.
82 //method Pa_IsStreamActive() will return 1.
83 //user needs to call Pa_StopStream() before starting a new recording session
84 finished = paComplete;
85#else
86 finished = paContinue;
87#endif
88 }
89 else
90 {
91 framesToCalc = framesPerBuffer;
92 //if we return paContinue, then the callback will be invoked again later
93 //method Pa_IsStreamActive() will return 0
94 finished = paContinue;
95 }
96
97 if( inputBuffer == nullptr )
98 {
99 for( i=0; i<framesToCalc; i++ )
100 {
101 recdata->write(0); // left
102 if (num_rec_channels == 2) {
103 recdata->write(0); // right
104 }
105 }
106 }
107 else
108 {
109 yCTrace(PORTAUDIO) << "Writing" << framesToCalc*2*2 << "bytes in the circular buffer";
110 for( i=0; i<framesToCalc; i++ )
111 {
112 recdata->write(*rptr++); // left
113 if (num_rec_channels == 2) {
114 recdata->write(*rptr++); // right
115 }
116 }
117 }
118 //note: you can record or play but not simultaneously (for now)
119 return finished;
120 }
121
122 if (dataBuffers->canPlay)
123 {
124 auto* wptr = (SAMPLE*)outputBuffer;
125 unsigned int i;
126
127 size_t framesLeft = playdata->size().getSamples()* playdata->size().getChannels();
128
129 YARP_UNUSED(inputBuffer); // just to prevent unused variable warnings
130 YARP_UNUSED(timeInfo);
131 YARP_UNUSED(statusFlags);
132 YARP_UNUSED(userData);
133
134 if( framesLeft/ num_play_channels < framesPerBuffer )
135 {
136 // final buffer
137 for( i=0; i<framesLeft/ num_play_channels; i++ )
138 {
139 *wptr++ = playdata->read(); // left
140 if (num_play_channels == 2) {
141 *wptr++ = playdata->read(); // right
142 }
143 for (int chs = 2; chs < num_play_channels; chs++) {
144 playdata->read(); //remove all additional channels > 2
145 }
146 }
147 for( ; i<framesPerBuffer; i++ )
148 {
149 *wptr++ = 0; // left
150 if (num_play_channels == 2) {
151 *wptr++ = 0; // right
152 }
153 }
154#ifdef STOP_PLAY_ON_EMPTY_BUFFER
155 //if we return paComplete, then the callback is not called anymore.
156 //method Pa_IsStreamActive() will return 1.
157 //user needs to call Pa_StopStream() before starting a new recording session
158 finished = paComplete;
159#else
160 finished = paContinue;
161#endif
162 }
163 else
164 {
165#if 1
166 yCDebug(PORTAUDIO) << "Reading" << framesPerBuffer*2 << "bytes from the circular buffer";
167#endif
168 for( i=0; i<framesPerBuffer; i++ )
169 {
170 *wptr++ = playdata->read(); // left
171 if (num_play_channels == 2) {
172 *wptr++ = playdata->read(); // right
173 }
174 for (int chs = 2; chs < num_play_channels; chs++) {
175 playdata->read(); //remove all additional channels > 2
176 }
177 }
178 //if we return paContinue, then the callback will be invoked again later
179 //method Pa_IsStreamActive() will return 0
180 finished = paContinue;
181 }
182 //note: you can record or play but not simultaneously (for now)
183 return finished;
184 }
185
186 yCError(PORTAUDIO, "No read/write operations requested, aborting");
187 return paAbort;
188}
189
191 stream(nullptr),
192 err(paNoError),
193 numSamples(0),
194 numBytes(0),
195 m_system_resource(nullptr),
196 m_numPlaybackChannels(0),
197 m_numRecordChannels(0),
198 m_frequency(0),
199 m_loopBack(false),
200 m_getSoundIsNotBlocking(true),
201 renderMode(RENDER_APPEND)
202{
203 memset(&inputParameters, 0, sizeof(PaStreamParameters));
204 memset(&outputParameters, 0, sizeof(PaStreamParameters));
206}
207
209{
210 close();
211}
212
213
215{
216 m_driverConfig.rate = config.check("rate",Value(0),"audio sample rate (0=automatic)").asInt32();
217 m_driverConfig.samples = config.check("samples",Value(0),"number of samples per network packet (0=automatic). For chunks of 1 second of recording set samples=rate. Channels number is handled internally.").asInt32();
218 m_driverConfig.playChannels = config.check("channels",Value(0),"number of audio channels (0=automatic, max is 2)").asInt32();
219 m_driverConfig.recChannels = config.check("channels", Value(0), "number of audio channels (0=automatic, max is 2)").asInt32();
220 m_driverConfig.wantRead = (bool)config.check("read","if present, just deal with reading audio (microphone)");
221 m_driverConfig.wantWrite = (bool)config.check("write","if present, just deal with writing audio (speaker)");
222 m_driverConfig.deviceNumber = config.check("dev_id",Value(-1),"which portaudio index to use (-1=automatic)").asInt32();
223
225 {
227 }
228
229 if (config.check("loopback","if present, send audio read from microphone immediately back to speaker"))
230 {
231 yCError(PORTAUDIO, "loopback not yet implemented");
232 m_loopBack = true;
233 }
234
235 if (config.check("render_mode_append"))
236 {
238 }
239 if (config.check("render_mode_immediate"))
240 {
242 }
243
244 return open(m_driverConfig);
245}
246
248{
249 int rate = config.rate;
250 int samples = config.samples;
251 int playChannels = config.playChannels;
252 int recChannels = config.recChannels;
253 bool wantRead = config.wantRead;
254 bool wantWrite = config.wantWrite;
255 int deviceNumber = config.deviceNumber;
256
257 if (playChannels == 0) {
258 playChannels = DEFAULT_NUM_CHANNELS;
259 }
260 if (recChannels == 0) {
261 recChannels = DEFAULT_NUM_CHANNELS;
262 }
263 m_numPlaybackChannels = playChannels;
264 m_numRecordChannels = recChannels;
265
266 if (rate == 0) {
267 rate = DEFAULT_SAMPLE_RATE;
268 }
269 m_frequency = rate;
270
271 if (samples == 0) {
272 numSamples = m_frequency; // by default let's stream chunks of 1 second
273 } else {
274 numSamples = samples;
275 }
276
277// size_t numPlayBytes = numSamples * sizeof(SAMPLE) * m_numPlaybackChannels;
278// size_t numRecBytes = numSamples * sizeof(SAMPLE) * m_numRecordChannels;
279// int twiceTheBuffer = 2;
280 AudioBufferSize playback_buffer_size(numSamples, m_numPlaybackChannels, sizeof(SAMPLE));
281 AudioBufferSize rec_buffer_size (numSamples, m_numRecordChannels, sizeof(SAMPLE));
284 if (dataBuffers.playData == nullptr) {
285 dataBuffers.playData = new CircularAudioBuffer_16t("portatudio_play", playback_buffer_size);
286 }
287 if (dataBuffers.recData == nullptr) {
288 dataBuffers.recData = new CircularAudioBuffer_16t("portatudio_rec", rec_buffer_size);
289 }
290 if (wantRead) {
291 dataBuffers.canRec = true;
292 }
293 if (wantWrite) {
294 dataBuffers.canPlay = true;
295 }
296
297 err = Pa_Initialize();
298 if( err != paNoError )
299 {
300 yCError(PORTAUDIO, "portaudio system failed to initialize");
301 return false;
302 }
303
304 inputParameters.device = (deviceNumber==-1)?Pa_GetDefaultInputDevice():deviceNumber;
305 yCInfo(PORTAUDIO, "Device number %d", inputParameters.device);
306 inputParameters.channelCount = m_numRecordChannels;
307 inputParameters.sampleFormat = PA_SAMPLE_TYPE;
308 if ((Pa_GetDeviceInfo( inputParameters.device ))!=nullptr) {
309 inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
310 }
311 inputParameters.hostApiSpecificStreamInfo = nullptr;
312
313 outputParameters.device = (deviceNumber==-1)?Pa_GetDefaultOutputDevice():deviceNumber;
314 outputParameters.channelCount = m_numPlaybackChannels;
315 outputParameters.sampleFormat = PA_SAMPLE_TYPE;
316 outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
317 outputParameters.hostApiSpecificStreamInfo = nullptr;
318
319 err = Pa_OpenStream(
320 &stream,
321 wantRead?(&inputParameters):nullptr,
322 wantWrite?(&outputParameters):nullptr,
325 paClipOff,
327 &dataBuffers );
328
329 if( err != paNoError )
330 {
331 yCError(PORTAUDIO, "An error occurred while using the portaudio stream");
332 yCError(PORTAUDIO, "Error number: %d", err);
333 yCError(PORTAUDIO, "Error message: %s", Pa_GetErrorText(err));
334 }
335
336 //start the thread
337 pThread.stream = stream;
338 pThread.start();
339
340 return (err==paNoError);
341}
342
343void streamThread::handleError()
344{
345 Pa_Terminate();
346 if( err != paNoError )
347 {
348 yCError(PORTAUDIO, "An error occurred while using the portaudio stream");
349 yCError(PORTAUDIO, "Error number: %d", err);
350 yCError(PORTAUDIO, "Error message: %s\n", Pa_GetErrorText(err));
351 }
352}
353
355{
356 //Pa_Terminate();
357 dataBuffers.playData->clear();
358
359 if( err != paNoError )
360 {
361 yCError(PORTAUDIO, "An error occurred while using the portaudio stream");
362 yCError(PORTAUDIO, "Error number: %d", err);
363 yCError(PORTAUDIO, "Error message: %s", Pa_GetErrorText(err));
364 }
365}
366
368{
369 pThread.stop();
370 if (stream != nullptr)
371 {
372 err = Pa_CloseStream( stream );
373 if( err != paNoError )
374 {
375 yCError(PORTAUDIO, "An error occurred while closing the portaudio stream");
376 yCError(PORTAUDIO, "Error number: %d", err);
377 yCError(PORTAUDIO, "Error message: %s", Pa_GetErrorText(err));
378 }
379 }
380
381 if (this->dataBuffers.playData != nullptr)
382 {
383 delete this->dataBuffers.playData;
384 this->dataBuffers.playData = nullptr;
385 }
386 if (this->dataBuffers.recData != nullptr)
387 {
388 delete this->dataBuffers.recData;
389 this->dataBuffers.recData = nullptr;
390 }
391
392 return (err==paNoError);
393}
394
396{
397 pThread.something_to_record = true;
398 err = Pa_StartStream( stream );
399 if( err < 0 ) {handleError(); return false;}
400 return true;
401}
402
404{
405 pThread.something_to_record = false;
406 err = Pa_StopStream( stream );
407 if( err < 0 ) {handleError(); return false;}
408 return true;
409}
410
411bool PortAudioDeviceDriver::getSound(yarp::sig::Sound& sound, size_t min_number_of_samples, size_t max_number_of_samples, double max_samples_timeout_s)
412{
413 if (pThread.something_to_record == false)
414 {
415 this->startRecording();
416 }
417
418 size_t buff_size = 0;
419 int buff_size_wdt = 0;
420 do
421 {
422 buff_size = dataBuffers.recData->size().getSamples();
423
424 if (buff_size_wdt > 100)
425 {
426 if (buff_size == 0)
427 {
428 yCError(PORTAUDIO) << "PortAudioDeviceDriver::getSound() Buffer size is still zero after 100 iterations, returning";
429 return false;
430 }
431 else
432 {
433 yCDebug(PORTAUDIO) << "PortAudioDeviceDriver::getSound() Buffer size is " << buff_size << "/" << this->numSamples <<" after 100 iterations";
435 {
436 yCError(PORTAUDIO) << "PortAudioDeviceDriver::getSound() is in not-blocking mode, returning";
437 return false;
438 }
439 }
440 }
441 buff_size_wdt++;
443 }
444 while (buff_size < this->numSamples);
445
446 buff_size_wdt = 0;
447
448 if (sound.getChannels()!=this->m_numRecordChannels && sound.getSamples() != this->numSamples)
449 {
450 sound.resize(this->numSamples,this->m_numRecordChannels);
451 }
452 sound.setFrequency(this->m_frequency);
453
454 for (size_t i = 0; i < this->numSamples; i++) {
455 for (size_t j=0; j<this->m_numRecordChannels; j++)
456 {
457 SAMPLE s = dataBuffers.recData->read();
458 sound.set(s,i,j);
459 }
460 }
461 return true;
462}
463
465{
466 yCInfo(PORTAUDIO, "=== Stopping and clearing stream.==="); fflush(stdout);
467 err = Pa_StopStream( stream );
468 if( err != paNoError )
469 {
470 yCError(PORTAUDIO, "abortSound: error occurred while stopping the portaudio stream" );
471 yCError(PORTAUDIO, "Error number: %d", err );
472 yCError(PORTAUDIO, "Error message: %s", Pa_GetErrorText( err ) );
473 }
474
475 dataBuffers.playData->clear();
476
477 return (err==paNoError);
478}
479
481{
482}
483
485{
486 something_to_play=false;
488 err = paNoError;
489 return true;
490}
491
493{
494 while(this->isStopping()==false)
495 {
497 {
498 something_to_play = false;
499 err = Pa_StartStream( stream );
500 if( err != paNoError ) {handleError(); return;}
501
502 while( ( err = Pa_IsStreamActive( stream ) ) == 1 )
503 {
505 }
506 if (err == 0)
507 {
508 yCDebug(PORTAUDIO) << "The playback stream has been stopped";
509 }
510 if( err < 0 )
511 {
512 handleError();
513 return;
514 }
515
516 err = Pa_StopStream( stream );
517 //err = Pa_AbortStream( stream );
518 if( err < 0 )
519 {
520 handleError();
521 return;
522 }
523
524 }
525
527 {
528 while( ( err = Pa_IsStreamActive( stream ) ) == 1 )
529 {
531 }
532 if (err == 0)
533 {
534 Pa_StopStream(stream);
535 yCDebug(PORTAUDIO) << "The recording stream has been stopped";
536 something_to_record = false;
537 }
538 if( err < 0 )
539 {
540 handleError();
541 return;
542 }
543 }
544
546 }
547 return;
548}
549
551{
552 dataBuffers.playData->clear();
553
554// size_t num_bytes = sound.getBytesPerSample();
555 size_t num_channels = sound.getChannels();
556 size_t num_samples = sound.getSamples();
557
558 for (size_t i = 0; i < num_samples; i++) {
559 for (size_t j = 0; j < num_channels; j++) {
560 dataBuffers.playData->write(sound.get(i, j));
561 }
562 }
563
564 pThread.something_to_play = true;
565 return true;
566}
567
569{
570 int freq = sound.getFrequency();
571 size_t chans = sound.getChannels();
572 if (freq != this->m_frequency ||
573 chans != this->m_numPlaybackChannels)
574 {
575 //wait for current playback to finish
576 while (Pa_IsStreamStopped(stream )==0)
577 {
579 }
580
581 //reset the driver
582 yCInfo(PORTAUDIO, "***** audio driver configuration changed, resetting");
583 yCInfo(PORTAUDIO) << "changing from: " << this->m_numPlaybackChannels << "channels, " << this->m_frequency << " Hz, ->" <<
584 chans << "channels, " << freq << " Hz";
585 this->close();
586 m_driverConfig.playChannels = (int)(chans);
587 m_driverConfig.rate = (int)(freq);
588 bool ok = open(m_driverConfig);
589 if (ok == false)
590 {
591 yCError(PORTAUDIO, "error occurred during audio driver reconfiguration, aborting");
592 return false;
593 }
594 }
595
597 return immediateSound(sound);
598 } else if (renderMode == RENDER_APPEND) {
599 return appendSound(sound);
600 }
601
602 return false;
603}
604
606{
607// size_t num_bytes = sound.getBytesPerSample();
608 size_t num_channels = sound.getChannels();
609 size_t num_samples = sound.getSamples();
610
611 for (size_t i = 0; i < num_samples; i++) {
612 for (size_t j = 0; j < num_channels; j++) {
613 dataBuffers.playData->write(sound.get(i, j));
614 }
615 }
616
617 pThread.something_to_play = true;
618 return true;
619}
620
622{
623 size = this->dataBuffers.playData->size();
624 return true;
625}
626
628{
629 size = this->dataBuffers.playData->getMaxSize();
630 return true;
631}
632
634{
635 this->dataBuffers.playData->clear();
636 return true;
637}
638
640{
641 size = this->dataBuffers.recData->size();
642 return true;
643}
644
646{
647 size = this->dataBuffers.recData->getMaxSize();
648 return true;
649}
650
652{
653 this->dataBuffers.recData->clear();
654 return true;
655}
656
658{
659 pThread.something_to_play = true;
660 return true;
661}
662
664{
665 pThread.something_to_play = false;
666 return true;
667}
668
670{
671 yCError(PORTAUDIO,"Not yet implemented");
672 return false;
673}
674
676{
677 yCError(PORTAUDIO, "Not yet implemented");
678 return false;
679}
680
681bool PortAudioDeviceDriver::isPlaying(bool& playback_enabled)
682{
683 playback_enabled = true;
684 return true;
685}
686
687bool PortAudioDeviceDriver::isRecording(bool& recording_enabled)
688{
689 recording_enabled = true;
690 return true;
691}
#define DEFAULT_NUM_CHANNELS
#define DEFAULT_SAMPLE_RATE
int16_t * samples
#define PA_SAMPLE_TYPE
static int bufferIOCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
#define SLEEP_TIME
short SAMPLE
#define DEFAULT_FRAMES_PER_BUFFER
bool getSound(yarp::sig::Sound &sound, size_t min_number_of_samples, size_t max_number_of_samples, double max_samples_timeout_s) override
Get a sound from a device.
bool startPlayback() override
Start the playback.
bool setSWGain(double gain) override
Sets a software gain for the grabbed audio.
bool open(yarp::os::Searchable &config) override
Open the DeviceDriver.
bool stopRecording() override
Stop the recording.
bool isPlaying(bool &playback_enabled) override
Check if the playback has been enabled (e.g.
bool getPlaybackAudioBufferCurrentSize(yarp::dev::AudioBufferSize &size) override
PortAudioDeviceDriverSettings m_driverConfig
bool resetRecordingAudioBuffer() override
bool getPlaybackAudioBufferMaxSize(yarp::dev::AudioBufferSize &size) override
bool setHWGain(double gain) override
Sets the hardware gain of the grabbing device (if supported by the hardware)
bool startRecording() override
Start the recording.
bool getRecordingAudioBufferCurrentSize(yarp::dev::AudioBufferSize &size) override
bool resetPlaybackAudioBuffer() override
bool renderSound(const yarp::sig::Sound &sound) override
Render a sound using a device (i.e.
bool isRecording(bool &recording_enabled) override
Check if the recording has been enabled (e.g.
bool appendSound(const yarp::sig::Sound &sound)
enum PortAudioDeviceDriver::@90 renderMode
bool close() override
Close the DeviceDriver.
bool stopPlayback() override
Stop the playback.
bool immediateSound(const yarp::sig::Sound &sound)
bool getRecordingAudioBufferMaxSize(yarp::dev::AudioBufferSize &size) override
void run() override
Main body of the new thread.
bool threadInit() override
Initialization method.
void threadRelease() override
Release method.
yarp::dev::AudioBufferSize getMaxSize()
A base class for nested structures that can be searched.
Definition: Searchable.h:56
virtual bool check(const std::string &key) const =0
Check if there exists a property of the given name.
static void delaySystem(double seconds)
Definition: SystemClock.cpp:29
bool stop()
Stop the thread.
Definition: Thread.cpp:81
bool isStopping()
Returns true if the thread is stopping (Thread::stop has been called).
Definition: Thread.cpp:99
bool start()
Start the new thread running.
Definition: Thread.cpp:93
A single value (typically within a Bottle).
Definition: Value.h:43
Class for storing sounds See Audio in YARP for additional documentation on YARP audio.
Definition: Sound.h:25
void setFrequency(int freq)
Set the frequency of the sound (i.e.
Definition: Sound.cpp:229
size_t getChannels() const
Get the number of channels of the sound.
Definition: Sound.cpp:424
void resize(size_t samples, size_t channels=1)
Set the sound size.
Definition: Sound.cpp:168
int getFrequency() const
Get the frequency of the sound (i.e.
Definition: Sound.cpp:224
audio_sample get(size_t sample, size_t channel=0) const
Definition: Sound.cpp:175
void set(audio_sample value, size_t sample, size_t channel=0)
Definition: Sound.cpp:209
size_t getSamples() const
Get the number of samples contained in the sound.
Definition: Sound.cpp:419
#define yCInfo(component,...)
Definition: LogComponent.h:171
#define yCError(component,...)
Definition: LogComponent.h:213
#define yCTrace(component,...)
Definition: LogComponent.h:84
#define yCDebug(component,...)
Definition: LogComponent.h:128
#define YARP_LOG_COMPONENT(name,...)
Definition: LogComponent.h:76
For streams capable of holding different kinds of content, check what they actually have.
yarp::dev::CircularAudioBuffer< unsigned short int > CircularAudioBuffer_16t
void delay(double seconds)
Wait for a certain number of seconds.
Definition: Time.cpp:111
An interface to the operating system, including Port based communication.
yarp::dev::CircularAudioBuffer_16t * playData
yarp::dev::CircularAudioBuffer_16t * recData
#define YARP_UNUSED(var)
Definition: api.h:162