YARP
Yet Another Robot Platform
PortAudioPlayerDeviceDriver.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 <cstdlib>
9 #include <cstring>
10 #include <portaudio.h>
11 #include <yarp/dev/DeviceDriver.h>
12 #include <yarp/dev/api.h>
13 
14 #include <yarp/os/Time.h>
15 #include <yarp/os/LogComponent.h>
16 #include <yarp/os/LogStream.h>
17 #include <mutex>
18 
19 using namespace yarp::os;
20 using namespace yarp::dev;
21 
22 #define SLEEP_TIME 0.010f
23 
24 #if 0
25 #define PA_SAMPLE_TYPE paFloat32
26 typedef float SAMPLE;
27 #define SAMPLE_SILENCE (0.0f)
28 #elif 1
29 #define PA_SAMPLE_TYPE paInt16
30 typedef short SAMPLE;
31 #define SAMPLE_SILENCE (0)
32 #elif 1
33 #define PA_SAMPLE_TYPE paInt8
34 typedef char SAMPLE;
35 #define SAMPLE_SILENCE (0)
36 #else
37 #define PA_SAMPLE_TYPE paUInt8
38 typedef unsigned char SAMPLE;
39 #define SAMPLE_SILENCE (128)
40 #define SAMPLE_UNSIGNED
41 #endif
42 
43 #define DEFAULT_FRAMES_PER_BUFFER (512)
44 
45 namespace {
46 YARP_LOG_COMPONENT(PORTAUDIOPLAYER, "yarp.devices.portaudioPlayer")
47 }
48 
49 
50 /* This routine will be called by the PortAudio engine when audio is needed.
51 ** It may be called at interrupt level on some machines so don't do anything
52 ** that could mess up the system like calling malloc() or free().
53 */
54 static int bufferIOCallback( const void *inputBuffer, void *outputBuffer,
55  unsigned long framesPerBuffer,
56  const PaStreamCallbackTimeInfo* timeInfo,
57  PaStreamCallbackFlags statusFlags,
58  void *userData )
59 {
60  CircularAudioBuffer_16t *playdata = static_cast<CircularAudioBuffer_16t*>(userData);
61  size_t num_play_channels = playdata->getMaxSize().getChannels();
62  int finished = paComplete;
63 
64  if (1)
65  {
66  auto* wptr = (SAMPLE*)outputBuffer;
67  unsigned int i;
68 
69  size_t framesLeft = playdata->size().getSamples()* playdata->size().getChannels();
70 
71  YARP_UNUSED(inputBuffer);
72  YARP_UNUSED(timeInfo);
73  YARP_UNUSED(statusFlags);
74  YARP_UNUSED(userData);
75 
76  if( framesLeft/ num_play_channels < framesPerBuffer )
77  {
78  // final buffer
79  for( i=0; i<framesLeft/ num_play_channels; i++ )
80  {
81  *wptr++ = playdata->read(); // left
82  if (num_play_channels == 2) {
83  *wptr++ = playdata->read(); // right
84  }
85  for (size_t chs = 2; chs < num_play_channels; chs++) {
86  playdata->read(); //remove all additional channels > 2
87  }
88  }
89  for( ; i<framesPerBuffer; i++ )
90  {
91  *wptr++ = 0; // left
92  if (num_play_channels == 2) {
93  *wptr++ = 0; // right
94  }
95  }
96 #ifdef STOP_PLAY_ON_EMPTY_BUFFER
97  //if we return paComplete, then the callback is not called anymore.
98  //method Pa_IsStreamActive() will return 1.
99  //user needs to call Pa_StopStream() before starting a new recording session
100  finished = paComplete;
101 #else
102  finished = paContinue;
103 #endif
104  }
105  else
106  {
107 #if 0
108  yCDebug(PORTAUDIOPLAYER) << "Reading" << framesPerBuffer*2 << "bytes from the circular buffer";
109 #endif
110  for( i=0; i<framesPerBuffer; i++ )
111  {
112  *wptr++ = playdata->read(); // left
113  if (num_play_channels == 2) {
114  *wptr++ = playdata->read(); // right
115  }
116  for (size_t chs = 2; chs < num_play_channels; chs++) {
117  playdata->read(); //remove all additional channels > 2
118  }
119  }
120  //if we return paContinue, then the callback will be invoked again later
121  //method Pa_IsStreamActive() will return 0
122  finished = paContinue;
123  }
124  return finished;
125  }
126 
127  yCError(PORTAUDIOPLAYER, "No read operations requested, aborting");
128  return paAbort;
129 }
130 
132 {
133 }
134 
136 {
137 
138  return true;
139 }
140 
142 {
143  while(this->isStopping()==false)
144  {
145  //The status of the buffer (i.e. the return value of Pa_IsStreamActive() depends on the returned value
146  //of the callback function bufferIOCallback() which may return paContinue or paComplete.
147  if(m_playback_enabled)
148  {
149  m_err = Pa_IsStreamActive(m_stream);
150  if (m_err < 0)
151  {
152  handleError();
153  yCError(PORTAUDIOPLAYER) << "Unhandled error. Calling abortSound()";
154  abortSound();
155  continue;
156  }
157  if (m_err == 1)
158  {
159  //already playing something
160  }
161  else if (m_err == 0)
162  {
163  //the playback is stopped
164  }
165  }
167  }
168  return;
169 }
170 
172  m_stream(nullptr),
173  m_err(paNoError),
174  m_system_resource(nullptr)
175 {
176  memset(&m_outputParameters, 0, sizeof(PaStreamParameters));
177 }
178 
180 {
181  close();
182 }
183 
185 {
186  this->stop();
187  if (m_stream != nullptr)
188  {
189  m_err = Pa_CloseStream(m_stream);
190  if (m_err != paNoError)
191  {
192  yCError(PORTAUDIOPLAYER, "An error occurred while closing the portaudio stream");
193  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err);
194  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err));
195  }
196  }
197 
198  if (this->m_outputBuffer != nullptr)
199  {
200  delete this->m_outputBuffer;
201  this->m_outputBuffer = nullptr;
202  }
203 
204  return (m_err == paNoError);
205 }
206 
207 bool PortAudioPlayerDeviceDriver::configureDeviceAndStart()
208 {
210  if (m_outputBuffer == nullptr) {
211  m_outputBuffer = new CircularAudioBuffer_16t("portatudio_play", playback_buffer_size);
212  }
213 
214  m_err = Pa_Initialize();
215  if (m_err != paNoError)
216  {
217  yCError(PORTAUDIOPLAYER, "portaudio system failed to initialize");
218  return false;
219  }
220 
221  m_outputParameters.device = (m_device_id == -1) ? Pa_GetDefaultOutputDevice() : m_device_id;
222  m_outputParameters.channelCount = static_cast<int>(m_audioplayer_cfg.numChannels);
223  m_outputParameters.sampleFormat = PA_SAMPLE_TYPE;
224  m_outputParameters.suggestedLatency = Pa_GetDeviceInfo(m_outputParameters.device)->defaultLowOutputLatency;
225  m_outputParameters.hostApiSpecificStreamInfo = nullptr;
226 
227  m_err = Pa_OpenStream(
228  &m_stream,
229  nullptr,
230  &m_outputParameters,
231  (double)(m_audioplayer_cfg.frequency),
233  paClipOff,
236 
237  if (m_err != paNoError)
238  {
239  yCError(PORTAUDIOPLAYER, "An error occurred while using the portaudio stream");
240  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err);
241  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err));
242  }
243 
244  //start the thread
245  this->start();
246 
247  return true;
248 }
249 
251 {
252  if (config.check("help"))
253  {
254  yCInfo(PORTAUDIOPLAYER, "Some examples:");
255  yCInfo(PORTAUDIOPLAYER, "yarpdev --device portaudioPlayer --help");
256  yCInfo(PORTAUDIOPLAYER, "yarpdev --device AudioPlayerWrapper --subdevice portaudioPlayer --start");
257  return false;
258  }
259 
260  bool b = configurePlayerAudioDevice(config.findGroup("AUDIO_BASE"), "portaudioPlayer");
261  if (!b) { return false; }
262 
263  m_device_id = config.check("id", Value(-1), "which portaudio index to use (-1=automatic)").asInt32();
264  m_driver_frame_size = config.check("driver_frame_size", Value(0), "").asInt32();
265  if (m_driver_frame_size == 0) {
267  }
268 
269  b = configureDeviceAndStart();
270  return (m_err==paNoError);
271 }
272 
274 {
275  //Pa_Terminate();
277 
278  if(m_err != paNoError )
279  {
280  yCError(PORTAUDIOPLAYER, "An error occurred while using the portaudio stream" );
281  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err );
282  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err ) );
283  }
284 }
285 
287 {
288  return interruptDeviceAndClose();
289 }
290 
291 bool PortAudioPlayerDeviceDriver::abortSound()
292 {
293  yCInfo(PORTAUDIOPLAYER, "=== Stopping and clearing stream.==="); fflush(stdout);
294  m_err = Pa_StopStream(m_stream );
295  if(m_err != paNoError )
296  {
297  yCError(PORTAUDIOPLAYER, "abortSound: error occurred while stopping the portaudio stream" );
298  yCError(PORTAUDIOPLAYER, "Error number: %d", m_err );
299  yCError(PORTAUDIOPLAYER, "Error message: %s", Pa_GetErrorText(m_err ) );
300  }
301 
303 
304  return (m_err==paNoError);
305 }
306 
308 {
309  yCError(PORTAUDIOPLAYER, "Not yet implemented");
310  return false;
311 }
312 
314 {
315  while (Pa_IsStreamStopped(m_stream) == 0)
316  {
318  size_t tmp = m_outputBuffer->size().getSamples();
319  if (tmp == 0) {
320  break;
321  }
322  }
323 }
324 
326 {
327  AudioPlayerDeviceBase::startPlayback();
328  m_err = Pa_StartStream(m_stream);
329  if (m_err < 0) { handleError(); return false; }
330  yCInfo(PORTAUDIOPLAYER) << "started playback";
331  return true;
332 }
333 
335 {
336  AudioPlayerDeviceBase::stopPlayback();
337  m_err = Pa_StopStream(m_stream);
338  if (m_err < 0) { handleError(); return false; }
339  yCInfo(PORTAUDIOPLAYER) << "stopped playback";
340  return true;
341 }
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)
#define SLEEP_TIME
bool threadInit() override
Initialization method.
bool stopPlayback() override
Stop the playback.
void run() override
Main body of the new thread.
bool setHWGain(double gain) override
Sets the hardware gain of the playback device (if supported by the hardware)
bool close() override
Close the DeviceDriver.
bool startPlayback() override
Start the playback.
bool open(yarp::os::Searchable &config) override
Open the DeviceDriver.
void threadRelease() override
Release method.
yarp::dev::CircularAudioBuffer_16t * m_outputBuffer
bool configurePlayerAudioDevice(yarp::os::Searchable &config, std::string device_name)
AudioDeviceDriverSettings m_audioplayer_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 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 yCDebug(component,...)
Definition: LogComponent.h:109
#define YARP_LOG_COMPONENT(name,...)
Definition: LogComponent.h:77
An interface for the device drivers.
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.
#define YARP_UNUSED(var)
Definition: api.h:162