YARP
Yet Another Robot Platform
PortAudioPlayerDeviceDriver.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2006-2020 Istituto Italiano di Tecnologia (IIT)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
20 
21 #include <cstdlib>
22 #include <cstring>
23 #include <portaudio.h>
24 #include <yarp/dev/DeviceDriver.h>
25 #include <yarp/dev/api.h>
26 
27 #include <yarp/os/Time.h>
28 #include <yarp/os/LogComponent.h>
29 #include <yarp/os/LogStream.h>
30 #include <mutex>
31 
32 using namespace yarp::os;
33 using namespace yarp::dev;
34 
35 #define SLEEP_TIME 0.005f
36 
37 #if 0
38 #define PA_SAMPLE_TYPE paFloat32
39 typedef float SAMPLE;
40 #define SAMPLE_SILENCE (0.0f)
41 #elif 1
42 #define PA_SAMPLE_TYPE paInt16
43 typedef short SAMPLE;
44 #define SAMPLE_SILENCE (0)
45 #elif 1
46 #define PA_SAMPLE_TYPE paInt8
47 typedef char SAMPLE;
48 #define SAMPLE_SILENCE (0)
49 #else
50 #define PA_SAMPLE_TYPE paUInt8
51 typedef unsigned char SAMPLE;
52 #define SAMPLE_SILENCE (128)
53 #define SAMPLE_UNSIGNED
54 #endif
55 
56 
57 namespace {
58 YARP_LOG_COMPONENT(PORTAUDIOPLAYER, "yarp.devices.portaudioPlayer")
59 }
60 
61 
62 /* This routine will be called by the PortAudio engine when audio is needed.
63 ** It may be called at interrupt level on some machines so don't do anything
64 ** that could mess up the system like calling malloc() or free().
65 */
66 static int bufferIOCallback( const void *inputBuffer, void *outputBuffer,
67  unsigned long framesPerBuffer,
68  const PaStreamCallbackTimeInfo* timeInfo,
69  PaStreamCallbackFlags statusFlags,
70  void *userData )
71 {
72  CircularAudioBuffer_16t *playdata = static_cast<CircularAudioBuffer_16t*>(userData);
73  size_t num_play_channels = playdata->getMaxSize().getChannels();
74  int finished = paComplete;
75 
76  if (1)
77  {
78  auto* wptr = (SAMPLE*)outputBuffer;
79  unsigned int i;
80 
81  size_t framesLeft = playdata->size().getSamples()* playdata->size().getChannels();
82 
83  YARP_UNUSED(inputBuffer);
84  YARP_UNUSED(timeInfo);
85  YARP_UNUSED(statusFlags);
86  YARP_UNUSED(userData);
87 
88  if( framesLeft/ num_play_channels < framesPerBuffer )
89  {
90  // final buffer
91  for( i=0; i<framesLeft/ num_play_channels; i++ )
92  {
93  *wptr++ = playdata->read(); // left
94  if( num_play_channels == 2 ) *wptr++ = playdata->read(); // right
95  for (size_t chs=2; chs<num_play_channels; chs++) playdata->read(); //remove all additional channels > 2
96  }
97  for( ; i<framesPerBuffer; i++ )
98  {
99  *wptr++ = 0; // left
100  if(num_play_channels == 2 ) *wptr++ = 0; // right
101  }
102 #ifdef STOP_PLAY_ON_EMPTY_BUFFER
103  //if we return paComplete, then the callback is not called anymore.
104  //method Pa_IsStreamActive() will return 1.
105  //user needs to call Pa_StopStream() before starting a new recording session
106  finished = paComplete;
107 #else
108  finished = paContinue;
109 #endif
110  }
111  else
112  {
113 #if 0
114  yCDebug(PORTAUDIOPLAYER) << "Reading" << framesPerBuffer*2 << "bytes from the circular buffer";
115 #endif
116  for( i=0; i<framesPerBuffer; i++ )
117  {
118  *wptr++ = playdata->read(); // left
119  if( num_play_channels == 2 ) *wptr++ = playdata->read(); // right
120  for (size_t chs=2; chs<num_play_channels; chs++) playdata->read(); //remove all additional channels > 2
121  }
122  //if we return paContinue, then the callback will be invoked again later
123  //method Pa_IsStreamActive() will return 0
124  finished = paContinue;
125  }
126  return finished;
127  }
128 
129  yCError(PORTAUDIOPLAYER, "No read operations requested, aborting");
130  return paAbort;
131 }
132 
134  something_to_play(false),
135  stream(nullptr),
136  err(paNoError)
137 {
138 }
139 
141 {
142 }
143 
145 {
146 
147  return true;
148 }
149 
151 {
152  while(this->isStopping()==false)
153  {
154  if( something_to_play )
155  {
156  something_to_play = false;
157  err = Pa_StartStream( stream );
158  if( err != paNoError ) {handleError(); return;}
159 
160  while( ( err = Pa_IsStreamActive( stream ) ) == 1 )
161  {
163  }
164  if (err == 0)
165  {
166  yCDebug(PORTAUDIOPLAYER) << "The playback stream has been stopped";
167  }
168  if( err < 0 )
169  {
170  handleError();
171  return;
172  }
173 
174  err = Pa_StopStream( stream );
175  //err = Pa_AbortStream( stream );
176  if( err < 0 )
177  {
178  handleError();
179  return;
180  }
181 
182  }
183 
185  }
186  return;
187 }
188 
189 void PlayStreamThread::handleError()
190 {
191  Pa_Terminate();
192  if( err != paNoError )
193  {
194  yCError(PORTAUDIOPLAYER, "An error occurred while using the portaudio stream" );
195  yCError(PORTAUDIOPLAYER, "Error number: %d", err );
196  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText( err ) );
197  }
198 }
199 
201  m_stream(nullptr),
202  m_err(paNoError),
203  m_playDataBuffer(nullptr),
204  m_system_resource(nullptr),
205  renderMode(RENDER_APPEND)
206 {
207  memset(&m_outputParameters, 0, sizeof(PaStreamParameters));
208 }
209 
211 {
212  close();
213 }
214 
215 
217 {
218  m_driverConfig.cfg_rate = config.check("rate",Value(0),"audio sample rate (0=automatic)").asInt32();
219  m_driverConfig.cfg_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();
220  m_driverConfig.cfg_playChannels = config.check("channels",Value(0),"number of audio channels (0=automatic, max is 2)").asInt32();
221  m_driverConfig.cfg_deviceNumber = config.check("id",Value(-1),"which portaudio index to use (-1=automatic)").asInt32();
222 
223  if (config.check("render_mode_append"))
224  {
226  }
227  if (config.check("render_mode_immediate"))
228  {
230  }
231 
232  return open(m_driverConfig);
233 }
234 
236 {
237  m_config = config;
238 
239  if (m_config.cfg_playChannels==0) m_config.cfg_playChannels = DEFAULT_NUM_CHANNELS;
240 
241  if (m_config.cfg_rate==0) m_config.cfg_rate = DEFAULT_SAMPLE_RATE;
242 
243  if (m_config.cfg_samples==0) m_config.cfg_samples = m_config.cfg_rate; // by default let's use chunks of 1 second
244 
245 // size_t debug_numPlayBytes = (m_config.cfg_samples * sizeof(SAMPLE) * m_config.cfg_playChannels);
246  AudioBufferSize playback_buffer_size(m_config.cfg_samples, m_config.cfg_playChannels, sizeof(SAMPLE));
247  if (m_playDataBuffer ==nullptr)
248  m_playDataBuffer = new CircularAudioBuffer_16t("portatudio_play", playback_buffer_size);
249 
250  m_err = Pa_Initialize();
251  if(m_err != paNoError )
252  {
253  yCError(PORTAUDIOPLAYER, "portaudio system failed to initialize");
254  return false;
255  }
256 
257  m_outputParameters.device = (config.cfg_deviceNumber ==-1)?Pa_GetDefaultOutputDevice(): config.cfg_deviceNumber;
258  m_outputParameters.channelCount = m_config.cfg_playChannels;
259  m_outputParameters.sampleFormat = PA_SAMPLE_TYPE;
260  m_outputParameters.suggestedLatency = Pa_GetDeviceInfo(m_outputParameters.device )->defaultLowOutputLatency;
261  m_outputParameters.hostApiSpecificStreamInfo = nullptr;
262 
263  m_err = Pa_OpenStream(
264  &m_stream,
265  nullptr,
266  &m_outputParameters,
267  m_config.cfg_rate,
269  paClipOff,
271  m_playDataBuffer);
272 
273  if(m_err != paNoError )
274  {
275  yCError(PORTAUDIOPLAYER, "An error occurred while using the portaudio stream" );
276  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err );
277  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err ) );
278  }
279 
280  //start the thread
281  m_pThread.stream = m_stream;
282  m_pThread.start();
283 
284  return (m_err==paNoError);
285 }
286 
288 {
289  //Pa_Terminate();
290  m_playDataBuffer->clear();
291 
292  if(m_err != paNoError )
293  {
294  yCError(PORTAUDIOPLAYER, "An error occurred while using the portaudio stream" );
295  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err );
296  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err ) );
297  }
298 }
299 
301 {
302  m_pThread.stop();
303  if (m_stream != nullptr)
304  {
305  m_err = Pa_CloseStream(m_stream );
306  if(m_err != paNoError )
307  {
308  yCError(PORTAUDIOPLAYER, "An error occurred while closing the portaudio stream" );
309  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err );
310  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err ) );
311  }
312  }
313 
314  if (this->m_playDataBuffer != nullptr)
315  {
316  delete this->m_playDataBuffer;
317  this->m_playDataBuffer = nullptr;
318  }
319 
320  return (m_err==paNoError);
321 }
322 
324 {
325  yCInfo(PORTAUDIOPLAYER, "=== Stopping and clearing stream.==="); fflush(stdout);
326  m_err = Pa_StopStream(m_stream );
327  if(m_err != paNoError )
328  {
329  yCError(PORTAUDIOPLAYER, "abortSound: error occurred while stopping the portaudio stream" );
330  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err );
331  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err ) );
332  }
333 
334  m_playDataBuffer->clear();
335 
336  return (m_err==paNoError);
337 }
338 
340 {
341  m_playDataBuffer->clear();
342 
343 // size_t num_bytes = sound.getBytesPerSample();
344  size_t num_channels = sound.getChannels();
345  size_t num_samples = sound.getSamples();
346 
347  for (size_t i=0; i<num_samples; i++)
348  for (size_t j=0; j<num_channels; j++)
349  m_playDataBuffer->write (sound.get(i,j));
350 
351  m_pThread.something_to_play = true;
352  return true;
353 }
354 
356 {
357  //prevents simultaneous start/stop/reset etc.
358  std::lock_guard<std::mutex> lock(m_mutex);
359 
360  size_t freq = sound.getFrequency();
361  size_t chans = sound.getChannels();
362  if (freq == 0)
363  {
364  yCError(PORTAUDIOPLAYER) << "received a bad audio sample of frequency 0";
365  return false;
366  }
367  if (chans == 0)
368  {
369  yCError(PORTAUDIOPLAYER) << "received a bad audio sample with 0 channels";
370  return false;
371  }
372 
373  if (freq != this->m_config.cfg_rate ||
374  chans != this->m_config.cfg_playChannels)
375  {
376  //wait for current playback to finish
377  while (Pa_IsStreamStopped(m_stream )==0)
378  {
380  }
381 
382  //reset the driver
383  yCInfo(PORTAUDIOPLAYER, "***** audio driver configuration changed, resetting");
384  yCInfo(PORTAUDIOPLAYER) << "changing from: " << this->m_config.cfg_playChannels << "channels, " << this->m_config.cfg_rate << " Hz, ->" <<
385  chans << "channels, " << freq << " Hz";
386  this->close();
387  m_driverConfig.cfg_playChannels = (int)(chans);
388  m_driverConfig.cfg_rate = (int)(freq);
389  bool ok = open(m_driverConfig);
390  if (ok == false)
391  {
392  yCError(PORTAUDIOPLAYER, "error occurred during audio driver reconfiguration, aborting");
393  return false;
394  }
395  }
396 
398  return immediateSound(sound);
399  else if (renderMode == RENDER_APPEND)
400  return appendSound(sound);
401 
402  return false;
403 }
404 
406 {
407 // size_t num_bytes = sound.getBytesPerSample();
408  size_t num_channels = sound.getChannels();
409  size_t num_samples = sound.getSamples();
410 
411  for (size_t i=0; i<num_samples; i++)
412  for (size_t j=0; j<num_channels; j++)
413  m_playDataBuffer->write (sound.get(i,j));
414 
415  m_pThread.something_to_play = true;
416  return true;
417 }
418 
420 {
421  //no lock guard is needed here
422  size = this->m_playDataBuffer->size();
423  return true;
424 }
425 
427 {
428  //no lock guard is needed here
429  size = this->m_playDataBuffer->getMaxSize();
430  return true;
431 }
432 
434 {
435  std::lock_guard<std::mutex> lock(m_mutex);
436  this->m_playDataBuffer->clear();
437  return true;
438 }
439 
441 {
442  std::lock_guard<std::mutex> lock(m_mutex);
443  m_pThread.something_to_play = true;
444  return true;
445 }
446 
448 {
449  std::lock_guard<std::mutex> lock(m_mutex);
450  m_pThread.something_to_play = false;
451  return true;
452 }
LogStream.h
PortAudioPlayerDeviceDriverSettings
Definition: PortAudioPlayerDeviceDriver.h:39
yarp::dev::CircularAudioBuffer::write
void write(SAMPLE elem)
Definition: CircularAudioBuffer.h:48
DEFAULT_FRAMES_PER_BUFFER
#define DEFAULT_FRAMES_PER_BUFFER
Definition: PortAudioDeviceDriver.h:33
yarp::dev::CircularAudioBuffer::clear
void clear()
Definition: CircularAudioBuffer.h:87
PortAudioPlayerDeviceDriver::m_driverConfig
PortAudioPlayerDeviceDriverSettings m_driverConfig
Definition: PortAudioPlayerDeviceDriver.h:126
PlayStreamThread::PlayStreamThread
PlayStreamThread()
Definition: PortAudioPlayerDeviceDriver.cpp:133
yarp::os::Searchable
A base class for nested structures that can be searched.
Definition: Searchable.h:69
yarp::sig::Sound::getFrequency
int getFrequency() const
Get the frequency of the sound (i.e.
Definition: Sound.cpp:221
PlayStreamThread::stream
PaStream * stream
Definition: PortAudioPlayerDeviceDriver.h:57
yarp::sig::Sound::getSamples
size_t getSamples() const
Get the number of samples contained in the sound.
Definition: Sound.cpp:404
PlayStreamThread::threadRelease
void threadRelease() override
Release method.
Definition: PortAudioPlayerDeviceDriver.cpp:140
yarp::dev::AudioBufferSize
Definition: AudioBufferSize.h:26
yarp::dev::AudioBufferSize::getChannels
size_t getChannels()
Definition: AudioBufferSize.h:32
PortAudioPlayerDeviceDriver::open
bool open(yarp::os::Searchable &config) override
Open the DeviceDriver.
Definition: PortAudioPlayerDeviceDriver.cpp:216
YARP_LOG_COMPONENT
#define YARP_LOG_COMPONENT(name,...)
Definition: LogComponent.h:80
PortAudioPlayerDeviceDriverSettings::cfg_rate
size_t cfg_rate
Definition: PortAudioPlayerDeviceDriver.h:41
PortAudioPlayerDeviceDriver::RENDER_APPEND
@ RENDER_APPEND
Definition: PortAudioPlayerDeviceDriver.h:127
PortAudioPlayerDeviceDriver.h
PortAudioPlayerDeviceDriverSettings::cfg_samples
size_t cfg_samples
Definition: PortAudioPlayerDeviceDriver.h:42
PortAudioPlayerDeviceDriver::abortSound
bool abortSound()
Definition: PortAudioPlayerDeviceDriver.cpp:323
YARP_UNUSED
#define YARP_UNUSED(var)
Definition: api.h:159
PortAudioPlayerDeviceDriver::appendSound
bool appendSound(const yarp::sig::Sound &sound)
Definition: PortAudioPlayerDeviceDriver.cpp:405
yarp::sig::Sound::getChannels
size_t getChannels() const
Get the number of channels of the sound.
Definition: Sound.cpp:409
yarp::dev
An interface for the device drivers.
Definition: audioBufferSizeData.cpp:17
PortAudioPlayerDeviceDriver::renderMode
enum PortAudioPlayerDeviceDriver::@0 renderMode
yarp::dev::CircularAudioBuffer
Definition: CircularAudioBuffer.h:25
yarp::dev::CircularAudioBuffer::size
AudioBufferSize size()
Definition: CircularAudioBuffer.h:59
SLEEP_TIME
#define SLEEP_TIME
Definition: PortAudioPlayerDeviceDriver.cpp:35
PortAudioPlayerDeviceDriver::RENDER_IMMEDIATE
@ RENDER_IMMEDIATE
Definition: PortAudioPlayerDeviceDriver.h:127
PortAudioPlayerDeviceDriver::handleError
void handleError()
Definition: PortAudioPlayerDeviceDriver.cpp:287
yarp::os::SystemClock::delaySystem
static void delaySystem(double seconds)
Definition: SystemClock.cpp:32
PortAudioPlayerDeviceDriver::resetPlaybackAudioBuffer
bool resetPlaybackAudioBuffer() override
Definition: PortAudioPlayerDeviceDriver.cpp:433
PortAudioPlayerDeviceDriver::~PortAudioPlayerDeviceDriver
~PortAudioPlayerDeviceDriver() override
Definition: PortAudioPlayerDeviceDriver.cpp:210
yarp::os::Searchable::check
virtual bool check(const std::string &key) const =0
Check if there exists a property of the given name.
yarp::sig::Sound::get
audio_sample get(size_t sample, size_t channel=0) const
Definition: Sound.cpp:174
yarp::dev::AudioBufferSize::getSamples
size_t getSamples()
Definition: AudioBufferSize.h:31
LogComponent.h
PortAudioPlayerDeviceDriver::getPlaybackAudioBufferCurrentSize
bool getPlaybackAudioBufferCurrentSize(yarp::dev::AudioBufferSize &size) override
Definition: PortAudioPlayerDeviceDriver.cpp:419
yCError
#define yCError(component,...)
Definition: LogComponent.h:157
PA_SAMPLE_TYPE
#define PA_SAMPLE_TYPE
Definition: PortAudioPlayerDeviceDriver.cpp:42
yarp::os::Thread::isStopping
bool isStopping()
Returns true if the thread is stopping (Thread::stop has been called).
Definition: Thread.cpp:102
yCInfo
#define yCInfo(component,...)
Definition: LogComponent.h:135
PortAudioPlayerDeviceDriver::immediateSound
bool immediateSound(const yarp::sig::Sound &sound)
Definition: PortAudioPlayerDeviceDriver.cpp:339
yarp::os
An interface to the operating system, including Port based communication.
Definition: AbstractCarrier.h:17
yCDebug
#define yCDebug(component,...)
Definition: LogComponent.h:112
yarp::sig::Sound
Class for storing sounds.
Definition: Sound.h:28
SAMPLE
short SAMPLE
Definition: PortAudioPlayerDeviceDriver.cpp:43
PortAudioPlayerDeviceDriver::stopPlayback
bool stopPlayback() override
Stop the playback.
Definition: PortAudioPlayerDeviceDriver.cpp:447
DEFAULT_NUM_CHANNELS
#define DEFAULT_NUM_CHANNELS
Definition: PortAudioDeviceDriver.h:31
PortAudioPlayerDeviceDriver::startPlayback
bool startPlayback() override
Start the playback.
Definition: PortAudioPlayerDeviceDriver.cpp:440
yarp::dev::CircularAudioBuffer_16t
yarp::dev::CircularAudioBuffer< unsigned short int > CircularAudioBuffer_16t
Definition: CircularAudioBuffer.h:118
PortAudioPlayerDeviceDriver::renderSound
bool renderSound(const yarp::sig::Sound &sound) override
Render a sound using a device (i.e.
Definition: PortAudioPlayerDeviceDriver.cpp:355
yarp::dev::CircularAudioBuffer::read
SAMPLE read()
Definition: CircularAudioBuffer.h:71
yarp::os::Thread::start
bool start()
Start the new thread running.
Definition: Thread.cpp:96
DEFAULT_SAMPLE_RATE
#define DEFAULT_SAMPLE_RATE
Definition: PortAudioDeviceDriver.h:30
Time.h
PlayStreamThread::run
void run() override
Main body of the new thread.
Definition: PortAudioPlayerDeviceDriver.cpp:150
PortAudioPlayerDeviceDriver::close
bool close() override
Close the DeviceDriver.
Definition: PortAudioPlayerDeviceDriver.cpp:300
PlayStreamThread::threadInit
bool threadInit() override
Initialization method.
Definition: PortAudioPlayerDeviceDriver.cpp:144
bufferIOCallback
static int bufferIOCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
Definition: PortAudioPlayerDeviceDriver.cpp:66
PlayStreamThread::something_to_play
bool something_to_play
Definition: PortAudioPlayerDeviceDriver.h:56
yarp::os::Value
A single value (typically within a Bottle).
Definition: Value.h:47
PortAudioPlayerDeviceDriverSettings::cfg_playChannels
size_t cfg_playChannels
Definition: PortAudioPlayerDeviceDriver.h:43
yarp::os::Thread::stop
bool stop()
Stop the thread.
Definition: Thread.cpp:84
SAMPLE
short SAMPLE
Definition: PortAudioDeviceDriver.cpp:43
api.h
yarp::dev::CircularAudioBuffer::getMaxSize
yarp::dev::AudioBufferSize getMaxSize()
Definition: CircularAudioBuffer.h:82
PortAudioPlayerDeviceDriver::getPlaybackAudioBufferMaxSize
bool getPlaybackAudioBufferMaxSize(yarp::dev::AudioBufferSize &size) override
Definition: PortAudioPlayerDeviceDriver.cpp:426
yarp::os::Time::delay
void delay(double seconds)
Wait for a certain number of seconds.
Definition: Time.cpp:114
PortAudioPlayerDeviceDriver::PortAudioPlayerDeviceDriver
PortAudioPlayerDeviceDriver()
Definition: PortAudioPlayerDeviceDriver.cpp:200
PortAudioPlayerDeviceDriverSettings::cfg_deviceNumber
int cfg_deviceNumber
Definition: PortAudioPlayerDeviceDriver.h:44
DeviceDriver.h