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