YARP
Yet Another Robot Platform
PortAudioRecorderDeviceDriver.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 <yarp/dev/api.h>
12 
13 #include <yarp/os/Time.h>
14 #include <yarp/os/LogComponent.h>
15 #include <yarp/os/LogStream.h>
16 
17 using namespace yarp::os;
18 using namespace yarp::dev;
19 
20 #define SLEEP_TIME 0.005f
21 
22 #if 0
23 #define PA_SAMPLE_TYPE paFloat32
24 typedef float SAMPLE;
25 #define SAMPLE_SILENCE (0.0f)
26 #elif 1
27 #define PA_SAMPLE_TYPE paInt16
28 typedef short SAMPLE;
29 #define SAMPLE_SILENCE (0)
30 #elif 1
31 #define PA_SAMPLE_TYPE paInt8
32 typedef char SAMPLE;
33 #define SAMPLE_SILENCE (0)
34 #else
35 #define PA_SAMPLE_TYPE paUInt8
36 typedef unsigned char SAMPLE;
37 #define SAMPLE_SILENCE (128)
38 #define SAMPLE_UNSIGNED
39 #endif
40 
41 namespace {
42 YARP_LOG_COMPONENT(PORTAUDIORECORDER, "yarp.devices.portaudioRecorder")
43 }
44 
45 #define DEFAULT_FRAMES_PER_BUFFER (512)
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 */
51 static int bufferIOCallback( const void *inputBuffer, void *outputBuffer,
52  unsigned long framesPerBuffer,
53  const PaStreamCallbackTimeInfo* timeInfo,
54  PaStreamCallbackFlags statusFlags,
55  void *userData )
56 {
57  CircularAudioBuffer_16t *recdata = (CircularAudioBuffer_16t*)(userData);
58  size_t num_rec_channels = recdata->getMaxSize().getChannels();
59  int finished = paComplete;
60 
61  if (1)
62  {
63  const auto* rptr = (const SAMPLE*)inputBuffer;
64  size_t framesToCalc;
65  size_t framesLeft = (recdata->getMaxSize().getSamples()* recdata->getMaxSize().getChannels()) -
66  (recdata->size().getSamples() * recdata->size().getChannels());
67 
68  YARP_UNUSED(outputBuffer); // just to prevent unused variable warnings
69  YARP_UNUSED(timeInfo);
70  YARP_UNUSED(statusFlags);
71  YARP_UNUSED(userData);
72 
73  if( framesLeft/ num_rec_channels < framesPerBuffer )
74  {
75  framesToCalc = framesLeft/ num_rec_channels;
76 #ifdef STOP_REC_ON_EMPTY_BUFFER
77  //if we return paComplete, then the callback is not called anymore.
78  //method Pa_IsStreamActive() will return 1.
79  //user needs to call Pa_StopStream() before starting a new recording session
80  finished = paComplete;
81 #else
82  finished = paContinue;
83 #endif
84  }
85  else
86  {
87  framesToCalc = framesPerBuffer;
88  //if we return paContinue, then the callback will be invoked again later
89  //method Pa_IsStreamActive() will return 0
90  finished = paContinue;
91  }
92 
93  if( inputBuffer == nullptr )
94  {
95  for( size_t i=0; i<framesToCalc; i++ )
96  {
97  for (size_t j=0; j < num_rec_channels; j++)
98  {
99  recdata->write(0);
100  }
101  }
102  }
103  else
104  {
105  for( size_t i=0; i<framesToCalc; i++ )
106  {
107  for (size_t j = 0; j < num_rec_channels; j++)
108  {
109  recdata->write(*rptr++);
110  }
111  }
112  }
113  return finished;
114  }
115 
116  yCError(PORTAUDIORECORDER, "No write operations requested, aborting");
117  return paAbort;
118 }
119 
121  m_stream(nullptr),
122  m_err(paNoError),
123  m_system_resource(nullptr)
124 {
125  memset(&m_inputParameters, 0, sizeof(PaStreamParameters));
126 }
127 
129 {
130  close();
131 }
132 
133 
135 {
136  if (config.check("help"))
137  {
138  yCInfo(PORTAUDIORECORDER, "Some examples:");
139  yCInfo(PORTAUDIORECORDER, "yarpdev --device portaudioRecorder --help");
140  yCInfo(PORTAUDIORECORDER, "yarpdev --device AudioRecorderWrapper --subdevice portaudioRecorder --start");
141  return false;
142  }
143 
144  bool b = configureRecorderAudioDevice(config.findGroup("AUDIO_BASE"),"portaudioRecorder");
145  if (!b) { return false; }
146 
147  m_device_id = config.check("id", Value(-1), "which portaudio index to use (-1=automatic)").asInt32();
148  m_driver_frame_size = config.check("driver_frame_size", Value(0), "").asInt32();
149  if (m_driver_frame_size == 0) {
151  }
152 
153  m_err = Pa_Initialize();
154  if(m_err != paNoError )
155  {
156  yCError(PORTAUDIORECORDER, "portaudio system failed to initialize");
157  return false;
158  }
159 
160  m_inputParameters.device = (m_device_id ==-1)?Pa_GetDefaultInputDevice(): m_device_id;
161  yCInfo(PORTAUDIORECORDER, "Device number %d", m_inputParameters.device);
162  m_inputParameters.channelCount = static_cast<int>(m_audiorecorder_cfg.numChannels);
163  m_inputParameters.sampleFormat = PA_SAMPLE_TYPE;
164  if ((Pa_GetDeviceInfo(m_inputParameters.device ))!=nullptr) {
165  m_inputParameters.suggestedLatency = Pa_GetDeviceInfo(m_inputParameters.device )->defaultLowInputLatency;
166  }
167  m_inputParameters.hostApiSpecificStreamInfo = nullptr;
168 
169  m_err = Pa_OpenStream(
170  &m_stream,
171  &m_inputParameters,
172  nullptr,
173  (double)(m_audiorecorder_cfg.frequency),
175  paClipOff,
177  m_inputBuffer);
178 
179  if(m_err != paNoError )
180  {
181  yCError(PORTAUDIORECORDER, "An error occurred while using the portaudio stream" );
182  yCError(PORTAUDIORECORDER, "Error number: %d", m_err );
183  yCError(PORTAUDIORECORDER, "Error message: %s", Pa_GetErrorText(m_err ) );
184  }
185 
186  //start the thread
187  bool ret = this->start();
188  YARP_UNUSED(ret);
189 
190  return (m_err==paNoError);
191 }
192 
194 {
195  //Pa_Terminate();
196  m_inputBuffer->clear();
197 
198  if(m_err != paNoError )
199  {
200  yCError(PORTAUDIORECORDER, "An error occurred while using the portaudio stream" );
201  yCError(PORTAUDIORECORDER, "Error number: %d", m_err );
202  yCError(PORTAUDIORECORDER, "Error message: %s", Pa_GetErrorText(m_err ) );
203  }
204 }
205 
207 {
208  this->stop();
209  if (m_stream != nullptr)
210  {
211  m_err = Pa_CloseStream(m_stream );
212  if(m_err != paNoError )
213  {
214  yCError(PORTAUDIORECORDER, "An error occurred while closing the portaudio stream" );
215  yCError(PORTAUDIORECORDER, "Error number: %d", m_err );
216  yCError(PORTAUDIORECORDER, "Error message: %s", Pa_GetErrorText(m_err ) );
217  }
218  }
219 
220  if (this->m_inputBuffer != nullptr)
221  {
222  delete this->m_inputBuffer;
223  this->m_inputBuffer = nullptr;
224  }
225 
226  return (m_err==paNoError);
227 }
228 
230 {
231  AudioRecorderDeviceBase::startRecording();
232  m_err = Pa_StartStream(m_stream );
233  if(m_err < 0 ) {handleError(); return false;}
234  yCInfo(PORTAUDIORECORDER) << "started recording";
235  return true;
236 }
237 
239 {
240  yCInfo(PORTAUDIORECORDER) << "not yet implemented recording";
241  return false;
242 }
243 
245 {
246  AudioRecorderDeviceBase::stopRecording();
247  m_err = Pa_StopStream(m_stream );
248  if(m_err < 0 ) {handleError(); return false;}
249  yCInfo(PORTAUDIORECORDER) << "stopped recording";
250  return true;
251 }
252 
254 {
255 }
256 
258 {
259  return true;
260 }
261 
263 {
264  while(this->isStopping()==false)
265  {
266  //The status of the buffer (i.e. the return value of Pa_IsStreamActive() depends on the returned value
267  //of the callback function bufferIOCallback() which may return paContinue or paComplete.
269  {
270  m_err = Pa_IsStreamActive(m_stream);
271  if (m_err < 0)
272  {
273  handleError();
274  yCError(PORTAUDIORECORDER) << "Unhandled error. Calling abortSound()";
275  //abortSound();
276  continue;
277  }
278  if (m_err == 1)
279  {
280  //already doing something
281  }
282  else if (m_err == 0)
283  {
284  //the recording is stopped
285  }
286  }
287 
289  }
290  return;
291 }
bool ret
short SAMPLE
#define PA_SAMPLE_TYPE
#define DEFAULT_FRAMES_PER_BUFFER
static int bufferIOCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
bool startRecording() override
Start the recording.
void run() override
Main body of the new thread.
bool threadInit() override
Initialization method.
bool setHWGain(double gain) override
Sets the hardware gain of the grabbing device (if supported by the hardware)
void threadRelease() override
Release method.
bool close() override
Close the DeviceDriver.
bool stopRecording() override
Stop the recording.
bool open(yarp::os::Searchable &config) override
Open the DeviceDriver.
yarp::dev::CircularAudioBuffer_16t * m_inputBuffer
bool configureRecorderAudioDevice(yarp::os::Searchable &config, std::string device_name)
AudioDeviceDriverSettings m_audiorecorder_cfg
yarp::dev::AudioBufferSize getMaxSize()
A base class for nested structures that can be searched.
Definition: Searchable.h:66
virtual bool check(const std::string &key) const =0
Check if there exists a property of the given name.
virtual Bottle & findGroup(const std::string &key) const =0
Gets a list corresponding to a given keyword.
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:45
#define yCInfo(component,...)
Definition: LogComponent.h:132
#define yCError(component,...)
Definition: LogComponent.h:154
#define YARP_LOG_COMPONENT(name,...)
Definition: LogComponent.h:77
An interface for the device drivers.
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.
#define YARP_UNUSED(var)
Definition: api.h:162