YARP
Yet Another Robot Platform
PortAudioRecorderDeviceDriver.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 <cstdio>
22 #include <cstdlib>
23 #include <cstring>
24 #include <portaudio.h>
25 #include <yarp/dev/DeviceDriver.h>
26 #include <yarp/dev/api.h>
27 
28 #include <yarp/os/Time.h>
29 #include <yarp/os/LogComponent.h>
30 #include <yarp/os/LogStream.h>
31 #include <mutex>
32 
33 using namespace yarp::os;
34 using namespace yarp::dev;
35 
36 #define SLEEP_TIME 0.005f
37 
38 #if 0
39 #define PA_SAMPLE_TYPE paFloat32
40 typedef float SAMPLE;
41 #define SAMPLE_SILENCE (0.0f)
42 #elif 1
43 #define PA_SAMPLE_TYPE paInt16
44 typedef short SAMPLE;
45 #define SAMPLE_SILENCE (0)
46 #elif 1
47 #define PA_SAMPLE_TYPE paInt8
48 typedef char SAMPLE;
49 #define SAMPLE_SILENCE (0)
50 #else
51 #define PA_SAMPLE_TYPE paUInt8
52 typedef unsigned char SAMPLE;
53 #define SAMPLE_SILENCE (128)
54 #define SAMPLE_UNSIGNED
55 #endif
56 
57 
58 namespace {
59 YARP_LOG_COMPONENT(PORTAUDIORECORDER, "yarp.devices.portaudioRecorder")
60 }
61 
62 
63 /* This routine will be called by the PortAudio engine when audio is needed.
64 ** It may be called at interrupt level on some machines so don't do anything
65 ** that could mess up the system like calling malloc() or free().
66 */
67 static int bufferIOCallback( const void *inputBuffer, void *outputBuffer,
68  unsigned long framesPerBuffer,
69  const PaStreamCallbackTimeInfo* timeInfo,
70  PaStreamCallbackFlags statusFlags,
71  void *userData )
72 {
73  CircularAudioBuffer_16t *recdata = (CircularAudioBuffer_16t*)(userData);
74  int num_rec_channels = recdata->getMaxSize().getChannels();
75  int finished = paComplete;
76 
77  if (1)
78  {
79  const auto* rptr = (const SAMPLE*)inputBuffer;
80  unsigned int framesToCalc;
81  unsigned int i;
82  size_t framesLeft = (recdata->getMaxSize().getSamples()* recdata->getMaxSize().getChannels()) -
83  (recdata->size().getSamples() * recdata->size().getChannels());
84 
85  YARP_UNUSED(outputBuffer); // just to prevent unused variable warnings
86  YARP_UNUSED(timeInfo);
87  YARP_UNUSED(statusFlags);
88  YARP_UNUSED(userData);
89 
90  if( framesLeft/ num_rec_channels < framesPerBuffer )
91  {
92  framesToCalc = framesLeft/ num_rec_channels;
93 #ifdef STOP_REC_ON_EMPTY_BUFFER
94  //if we return paComplete, then the callback is not called anymore.
95  //method Pa_IsStreamActive() will return 1.
96  //user needs to call Pa_StopStream() before starting a new recording session
97  finished = paComplete;
98 #else
99  finished = paContinue;
100 #endif
101  }
102  else
103  {
104  framesToCalc = framesPerBuffer;
105  //if we return paContinue, then the callback will be invoked again later
106  //method Pa_IsStreamActive() will return 0
107  finished = paContinue;
108  }
109 
110  if( inputBuffer == nullptr )
111  {
112  for( i=0; i<framesToCalc; i++ )
113  {
114  recdata->write(0); // left
115  if(num_rec_channels == 2 ) recdata->write(0); // right
116  }
117  }
118  else
119  {
120 #if 0
121  yCDebug(PORTAUDIORECORDER) << "Writing" << framesToCalc*2*2 << "bytes in the circular buffer";
122 #endif
123  for( i=0; i<framesToCalc; i++ )
124  {
125  recdata->write(*rptr++); // left
126  if(num_rec_channels == 2 ) recdata->write(*rptr++); // right
127  }
128  }
129  return finished;
130  }
131 
132  yCError(PORTAUDIORECORDER, "No write operations requested, aborting");
133  return paAbort;
134 }
135 
137  m_stream(nullptr),
138  m_err(paNoError),
139  m_recDataBuffer(nullptr),
140  m_isRecording(false),
141  m_system_resource(nullptr)
142 {
143  memset(&m_inputParameters, 0, sizeof(PaStreamParameters));
144 }
145 
147 {
148  close();
149 }
150 
151 
153 {
154  m_driverConfig.cfg_rate = config.check("rate",Value(0),"audio sample rate (0=automatic)").asInt32();
155  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();
156  m_driverConfig.cfg_recChannels = config.check("channels", Value(0), "number of audio channels (0=automatic, max is 2)").asInt32();
157  m_driverConfig.cfg_deviceNumber = config.check("id",Value(-1),"which portaudio index to use (-1=automatic)").asInt32();
158 
159  return open(m_driverConfig);
160 }
161 
163 {
164  m_config = config;
165 
166  if (m_config.cfg_recChannels == 0) m_config.cfg_recChannels = DEFAULT_NUM_CHANNELS;
167 
168  if (m_config.cfg_rate == 0) m_config.cfg_rate = DEFAULT_SAMPLE_RATE;
169 
170  if (m_config.cfg_samples == 0) m_config.cfg_samples = m_config.cfg_rate; // by default let's use chunks of 1 second
171 
172 // size_t debug_numRecBytes = m_config.cfg_samples * sizeof(SAMPLE) * m_config.cfg_recChannels;
173  AudioBufferSize rec_buffer_size (m_config.cfg_samples, m_config.cfg_recChannels, sizeof(SAMPLE));
174  if (m_recDataBuffer ==nullptr)
175  m_recDataBuffer = new CircularAudioBuffer_16t("portatudio_rec", rec_buffer_size);
176 
177  m_err = Pa_Initialize();
178  if(m_err != paNoError )
179  {
180  yCError(PORTAUDIORECORDER, "portaudio system failed to initialize");
181  return false;
182  }
183 
184  m_inputParameters.device = (config.cfg_deviceNumber ==-1)?Pa_GetDefaultInputDevice(): config.cfg_deviceNumber;
185  yCInfo(PORTAUDIORECORDER, "Device number %d", m_inputParameters.device);
186  m_inputParameters.channelCount = m_config.cfg_recChannels;
187  m_inputParameters.sampleFormat = PA_SAMPLE_TYPE;
188  if ((Pa_GetDeviceInfo(m_inputParameters.device ))!=nullptr) {
189  m_inputParameters.suggestedLatency = Pa_GetDeviceInfo(m_inputParameters.device )->defaultLowInputLatency;
190  }
191  m_inputParameters.hostApiSpecificStreamInfo = nullptr;
192 
193  m_err = Pa_OpenStream(
194  &m_stream,
195  &m_inputParameters,
196  nullptr,
197  m_config.cfg_rate,
199  paClipOff,
201  m_recDataBuffer);
202 
203  if(m_err != paNoError )
204  {
205  yCError(PORTAUDIORECORDER, "An error occurred while using the portaudio stream" );
206  yCError(PORTAUDIORECORDER, "Error number: %d", m_err );
207  yCError(PORTAUDIORECORDER, "Error message: %s", Pa_GetErrorText(m_err ) );
208  }
209 
210  //start the thread
211  bool ret = this->start();
212  YARP_UNUSED(ret);
213 
214  return (m_err==paNoError);
215 }
216 
217 /*
218 void recStreamThread::handleError(const PaError& err)
219 {
220  Pa_Terminate();
221  if( err != paNoError )
222  {
223  yCError(PORTAUDIORECORDER, "An error occurred while using the portaudio stream" );
224  yCError(PORTAUDIORECORDER, "Error number: %d", err );
225  yCError(PORTAUDIORECORDER, "Error message: %s", Pa_GetErrorText( err ) );
226  }
227 }
228 */
229 
231 {
232  //Pa_Terminate();
233  m_recDataBuffer->clear();
234 
235  if(m_err != paNoError )
236  {
237  yCError(PORTAUDIORECORDER, "An error occurred while using the portaudio stream" );
238  yCError(PORTAUDIORECORDER, "Error number: %d", m_err );
239  yCError(PORTAUDIORECORDER, "Error message: %s", Pa_GetErrorText(m_err ) );
240  }
241 }
242 
244 {
245  this->stop();
246  if (m_stream != nullptr)
247  {
248  m_err = Pa_CloseStream(m_stream );
249  if(m_err != paNoError )
250  {
251  yCError(PORTAUDIORECORDER, "An error occurred while closing the portaudio stream" );
252  yCError(PORTAUDIORECORDER, "Error number: %d", m_err );
253  yCError(PORTAUDIORECORDER, "Error message: %s", Pa_GetErrorText(m_err ) );
254  }
255  }
256 
257  if (this->m_recDataBuffer != nullptr)
258  {
259  delete this->m_recDataBuffer;
260  this->m_recDataBuffer = nullptr;
261  }
262 
263  return (m_err==paNoError);
264 }
265 
267 {
268  if (m_isRecording == true) return true;
269  std::lock_guard<std::mutex> lock(m_mutex);
270  m_isRecording = true;
271 #ifdef BUFFER_AUTOCLEAR
272  this->m_recDataBuffer->clear();
273 #endif
274  m_err = Pa_StartStream(m_stream );
275  if(m_err < 0 ) {handleError(); return false;}
276  yCInfo(PORTAUDIORECORDER) << "PortAudioRecorderDeviceDriver started recording";
277  return true;
278 }
279 
281 {
282  if (m_isRecording == false) return true;
283  std::lock_guard<std::mutex> lock(m_mutex);
284  m_isRecording = false;
285 #ifdef BUFFER_AUTOCLEAR
286  this->m_recDataBuffer->clear();
287 #endif
288  m_err = Pa_StopStream(m_stream );
289  if(m_err < 0 ) {handleError(); return false;}
290  yCInfo(PORTAUDIORECORDER) << "PortAudioRecorderDeviceDriver stopped recording";
291  return true;
292 }
293 
294 bool PortAudioRecorderDeviceDriver::getSound(yarp::sig::Sound& sound, size_t min_number_of_samples, size_t max_number_of_samples, double max_samples_timeout_s)
295 {
296  //check for something_to_record
297  {
298  #ifdef AUTOMATIC_REC_START
299  if (m_isRecording == false)
300  {
301  this->startRecording();
302  }
303  #else
304  double debug_time = yarp::os::Time::now();
305  while (m_isRecording == false)
306  {
307  if (yarp::os::Time::now() - debug_time > 5.0)
308  {
309  yCInfo(PORTAUDIORECORDER) << "getSound() is currently waiting. Use ::startRecording() to start the audio stream";
310  debug_time = yarp::os::Time::now();
311  }
313  }
314  #endif
315  }
316 
317  //prevents simultaneous start/stop/reset etc.
318  //std::lock_guard<std::mutex> lock(m_mutex); //This must be used carefully
319 
320  //check on input parameters
321  if (max_number_of_samples < min_number_of_samples)
322  {
323  yCError(PORTAUDIORECORDER) << "max_number_of_samples must be greater than min_number_of_samples!";
324  return false;
325  }
326  if (max_number_of_samples > this->m_config.cfg_samples)
327  {
328  yCWarning(PORTAUDIORECORDER) << "max_number_of_samples bigger than the internal audio buffer! It will be truncated to:" << this->m_config.cfg_samples;
329  max_number_of_samples = this->m_config.cfg_samples;
330  }
331 
332  //wait until the desired number of samples are obtained
333  size_t buff_size = 0;
334  double start_time = yarp::os::Time::now();
335  double debug_time = yarp::os::Time::now();
336  do
337  {
338  buff_size = m_recDataBuffer->size().getSamples();
339  if (buff_size >= max_number_of_samples) { break; }
340  if (buff_size >= min_number_of_samples && yarp::os::Time::now() - start_time > max_samples_timeout_s) { break; }
341  if (m_isRecording == false) { break; }
342 
343  if (yarp::os::Time::now() - debug_time > 1.0)
344  {
345  debug_time = yarp::os::Time::now();
346  yCDebug(PORTAUDIORECORDER) << "PortAudioRecorderDeviceDriver::getSound() Buffer size is " << buff_size << "/" << max_number_of_samples << " after 1s";
347  }
348 
350  }
351  while (true);
352 
353  //prepare the sound data struct
354  size_t samples_to_be_copied = buff_size;
355  if (samples_to_be_copied > max_number_of_samples) samples_to_be_copied = max_number_of_samples;
356  if (sound.getChannels()!=this->m_config.cfg_recChannels && sound.getSamples() != samples_to_be_copied)
357  {
358  sound.resize(samples_to_be_copied, this->m_config.cfg_recChannels);
359  }
360  sound.setFrequency(this->m_config.cfg_rate);
361 
362  //fill the sound data struct, reading samples from the circular buffer
363  for (size_t i=0; i< samples_to_be_copied; i++)
364  for (size_t j=0; j<this->m_config.cfg_recChannels; j++)
365  {
366  SAMPLE s = m_recDataBuffer->read();
367  sound.set(s,i,j);
368  }
369  return true;
370 }
371 
373 {
374 }
375 
377 {
378  m_isRecording=false;
379  return true;
380 }
381 
383 {
384  while(this->isStopping()==false)
385  {
386  if (m_isRecording)
387  {
388  while( ( m_err = Pa_IsStreamActive(m_stream) ) == 1 )
389  {
391  }
392 
393  if (m_err == 0)
394  {
395  Pa_StopStream(m_stream);
396  yCDebug(PORTAUDIORECORDER) << "The recording stream has been stopped";
397  m_isRecording = false;
398  }
399  if(m_err < 0 )
400  {
401  handleError();
402  return;
403  }
404  }
405 
407  }
408  return;
409 }
410 
412 {
413  //no lock guard is needed here
414  size = this->m_recDataBuffer->size();
415  return true;
416 }
417 
419 {
420  //no lock guard is needed here
421  size = this->m_recDataBuffer->getMaxSize();
422  return true;
423 }
424 
426 {
427  std::lock_guard<std::mutex> lock(m_mutex);
428  this->m_recDataBuffer->clear();
429  yCDebug(PORTAUDIORECORDER) << "PortAudioRecorderDeviceDriver::resetRecordingAudioBuffer";
430  return true;
431 }
bool ret
short SAMPLE
#define DEFAULT_FRAMES_PER_BUFFER
#define DEFAULT_NUM_CHANNELS
#define DEFAULT_SAMPLE_RATE
#define PA_SAMPLE_TYPE
static int bufferIOCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
bool startRecording() override
Start the recording.
bool getRecordingAudioBufferCurrentSize(yarp::dev::AudioBufferSize &size) override
void run() override
Main body of the new thread.
bool threadInit() override
Initialization method.
bool getRecordingAudioBufferMaxSize(yarp::dev::AudioBufferSize &size) override
void threadRelease() override
Release method.
PortAudioRecorderDeviceDriverSettings m_driverConfig
bool close() override
Close the DeviceDriver.
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 stopRecording() override
Stop the recording.
bool open(yarp::os::Searchable &config) override
Open the DeviceDriver.
yarp::dev::AudioBufferSize getMaxSize()
A base class for nested structures that can be searched.
Definition: Searchable.h:69
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:32
bool stop()
Stop the thread.
Definition: Thread.cpp:84
bool isStopping()
Returns true if the thread is stopping (Thread::stop has been called).
Definition: Thread.cpp:102
bool start()
Start the new thread running.
Definition: Thread.cpp:96
A single value (typically within a Bottle).
Definition: Value.h:47
Class for storing sounds.
Definition: Sound.h:28
void setFrequency(int freq)
Set the frequency of the sound (i.e.
Definition: Sound.cpp:226
size_t getChannels() const
Get the number of channels of the sound.
Definition: Sound.cpp:409
void resize(size_t samples, size_t channels=1)
Set the sound size.
Definition: Sound.cpp:167
void set(audio_sample value, size_t sample, size_t channel=0)
Definition: Sound.cpp:206
size_t getSamples() const
Get the number of samples contained in the sound.
Definition: Sound.cpp:404
#define yCInfo(component,...)
Definition: LogComponent.h:135
#define yCError(component,...)
Definition: LogComponent.h:157
#define yCWarning(component,...)
Definition: LogComponent.h:146
#define yCDebug(component,...)
Definition: LogComponent.h:112
#define YARP_LOG_COMPONENT(name,...)
Definition: LogComponent.h:80
An interface for the device drivers.
yarp::dev::CircularAudioBuffer< unsigned short int > CircularAudioBuffer_16t
double now()
Return the current time in seconds, relative to an arbitrary starting point.
Definition: Time.cpp:124
void delay(double seconds)
Wait for a certain number of seconds.
Definition: Time.cpp:114
An interface to the operating system, including Port based communication.
#define YARP_UNUSED(var)
Definition: api.h:159