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>
12#include <yarp/dev/api.h>
13
14#include <yarp/os/Time.h>
16#include <yarp/os/LogStream.h>
17#include <mutex>
18
19using namespace yarp::os;
20using namespace yarp::dev;
21
22#define SLEEP_TIME 0.010f
23
24#if 0
25#define PA_SAMPLE_TYPE paFloat32
26typedef float SAMPLE;
27#define SAMPLE_SILENCE (0.0f)
28#elif 1
29#define PA_SAMPLE_TYPE paInt16
30typedef short SAMPLE;
31#define SAMPLE_SILENCE (0)
32#elif 1
33#define PA_SAMPLE_TYPE paInt8
34typedef char SAMPLE;
35#define SAMPLE_SILENCE (0)
36#else
37#define PA_SAMPLE_TYPE paUInt8
38typedef unsigned char SAMPLE;
39#define SAMPLE_SILENCE (128)
40#define SAMPLE_UNSIGNED
41#endif
42
43#define DEFAULT_FRAMES_PER_BUFFER (512)
44
45namespace {
46YARP_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*/
54static 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.
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
207bool 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,
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("dev_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{
289}
290
291bool 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:63
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:43
#define yCInfo(component,...)
Definition: LogComponent.h:171
#define yCError(component,...)
Definition: LogComponent.h:213
#define yCDebug(component,...)
Definition: LogComponent.h:128
#define YARP_LOG_COMPONENT(name,...)
Definition: LogComponent.h:76
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.
#define YARP_UNUSED(var)
Definition: api.h:162