hydrogen 1.2.3
AlsaAudioDriver.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
24
25#if defined(H2CORE_HAVE_ALSA) || _DOXYGEN_
26
27#include <pthread.h>
28#include <iostream>
30#include <core/EventQueue.h>
31
32namespace H2Core
33{
34
36
37static int alsa_xrun_recovery( snd_pcm_t *handle, int err )
38{
39 if ( err == -EPIPE ) { /* under-run */
40 err = snd_pcm_prepare( handle );
41 } else if ( err == -ESTRPIPE ) {
42 while ( ( err = snd_pcm_resume( handle ) ) == -EAGAIN ) {
43 sleep( 1 ); /* wait until the suspend flag is released */
44 }
45 if ( err < 0 ) {
46 err = snd_pcm_prepare( handle );
47 if ( err < 0 ) {
48 std::cerr << "Can't recover from suspend, prepare failed: " << snd_strerror( err ) << std::endl;
49 }
50 }
51 return 0;
52 }
53 return err;
54}
55
57{
58 Base *__object = (Base*)param;
59 AlsaAudioDriver *pDriver = ( AlsaAudioDriver* )param;
60
61 // stolen from amSynth
62 struct sched_param sched;
63 sched.sched_priority = 50;
64 int res = sched_setscheduler( 0, SCHED_FIFO, &sched );
65 sched_getparam( 0, &sched );
66 if ( res ) {
67 __ERRORLOG( "Can't set realtime scheduling for ALSA Driver" );
68 }
69 __INFOLOG( QString( "Scheduling priority = %1" ).arg( sched.sched_priority ) );
70
71 sleep( 1 );
72
73 int err;
74 if ( ( err = snd_pcm_prepare( pDriver->m_pPlayback_handle ) ) < 0 ) {
75 __ERRORLOG( QString( "Cannot prepare audio interface for use: %1" )
76 .arg( snd_strerror ( err ) ) );
77 }
78
79 int nFrames = pDriver->m_nBufferSize;
80 __INFOLOG( QString( "nFrames: %1" ).arg( nFrames ) );
81 short pBuffer[ nFrames * 2 ];
82
83 float *pOut_L = pDriver->m_pOut_L;
84 float *pOut_R = pDriver->m_pOut_R;
85
86 int nTimeoutInMilliseconds = 100;
87
88 while ( pDriver->m_bIsRunning ) {
89 // prepare the audio data
90 pDriver->m_processCallback( nFrames, nullptr );
91
92 for ( int i = 0; i < nFrames; ++i ) {
93 pBuffer[ i * 2 ] = ( short )( pOut_L[ i ] * 32768.0 );
94 pBuffer[ i * 2 + 1 ] = ( short )( pOut_R[ i ] * 32768.0 );
95 }
96
97 // Check whether the playback stream is ready to process
98 // input.
99 if ( ( err = snd_pcm_wait( pDriver->m_pPlayback_handle,
100 nTimeoutInMilliseconds ) ) < 1 ) {
101 // Playback stream is not ready. Since we opened the stream
102 // in blocking mode, the call to snd_pcm_writei() may take
103 // forever and cause the audio engine to stop working
104 // entirely. In addition, this also prevents the audio
105 // driver to be stopped and thus prevents the user from
106 // selecting a different/working version.
107 if ( err == 0 ) {
108 ___ERRORLOG( QString( "timeout after [%1] milliseconds" )
109 .arg( nTimeoutInMilliseconds ) );
110 } else {
111 ___ERRORLOG( QString( "Error while waiting for playback stream: %1" )
112 .arg( snd_strerror( err ) ) );
113 }
114 pDriver->m_nXRuns++;
116 } else {
117
118 // Playback stream is ready, let's write out the audio
119 // buffer.
120 if ( ( err = snd_pcm_writei( pDriver->m_pPlayback_handle, pBuffer, nFrames ) ) < 0 ) {
121 ___ERRORLOG( QString( "Error while writing playback stream: %1" )
122 .arg( snd_strerror( err ) ) );
123
124 // Try to bring the playback device in a nice state
125 // again and retry writing the output buffer.
126 if ( ( err = snd_pcm_recover( pDriver->m_pPlayback_handle, err, 0 ) ) == 0 ) {
127 ___INFOLOG( "Successfully recovered from error. Attempt to write buffer again." );
128 if ( ( err = snd_pcm_writei( pDriver->m_pPlayback_handle, pBuffer, nFrames ) ) < 0 ) {
129 ___ERRORLOG( QString( "Unable to write playback stream again: %1" )
130 .arg( snd_strerror( err ) ) );
131 pDriver->m_nXRuns++;
133 if ( ( err = snd_pcm_recover( pDriver->m_pPlayback_handle, err, 0 ) ) < 0 ) {
134 __ERRORLOG( QString( "Can't recover from XRUN: %1" )
135 .arg( snd_strerror( err ) ) );
136 }
137 }
138 } else {
139 __ERRORLOG( QString( "Can't recover from XRUN: %1" )
140 .arg( snd_strerror( err ) ) );
141 pDriver->m_nXRuns++;
143 }
144 }
145 }
146 }
147 return nullptr;
148}
149
150
153{
154 QStringList result;
155 void **pHints, **pHint;
156
157 if ( snd_device_name_hint( -1, "pcm", &pHints) < 0) {
158 ERRORLOG( "Couldn't get device hints" );
159 return result;
160 }
161
162 for ( pHint = pHints; *pHint != nullptr; pHint++) {
163 const char *sName = snd_device_name_get_hint( *pHint, "NAME"),
164 *sIOID = snd_device_name_get_hint( *pHint, "IOID");
165
166 if ( sIOID && QString( sIOID ) != "Output") {
167 continue;
168 }
169
170 QString sDev = QString( sName );
171 if ( sName ) {
172 free( (void *)sName );
173 }
174 if ( sIOID ) {
175 free( (void *)sIOID );
176 }
177 result.push_back( sDev );
178 }
179 snd_device_name_free_hint( pHints );
180 return result;
181}
182
184 : AudioOutput()
185 , m_bIsRunning( false )
186 , m_pOut_L( nullptr )
187 , m_pOut_R( nullptr )
188 , m_nXRuns( 0 )
189 , m_nBufferSize( 0 )
190 , m_pPlayback_handle( nullptr )
191 , m_processCallback( processCallback )
192{
195}
196
198{
199 if ( m_nXRuns > 0 ) {
200 WARNINGLOG( QString( "%1 xruns" ).arg( m_nXRuns ) );
201 }
202
203 snd_config_update_free_global();
204
205}
206
207
208int AlsaAudioDriver::init( unsigned nBufferSize )
209{
210 m_nBufferSize = nBufferSize;
211
212 return 0; // ok
213}
214
215
217{
218 INFOLOG( "to: " + m_sAlsaAudioDevice );
219 int nChannels = 2;
220
221 int err;
222
223 // provo ad aprire il device per verificare se e' libero ( non bloccante )
224 if ( ( err = snd_pcm_open( &m_pPlayback_handle,
225 m_sAlsaAudioDevice.toLocal8Bit(),
226 SND_PCM_STREAM_PLAYBACK,
227 SND_PCM_NONBLOCK ) ) < 0 ) {
228 ERRORLOG( QString( "Cannot open audio device [%1] (non-blocking): %2" )
229 .arg( m_sAlsaAudioDevice )
230 .arg( snd_strerror( err ) ) );
231
232 // Use the default device as a fallback.
233 m_sAlsaAudioDevice = "default";
234 if ( ( err = snd_pcm_open( &m_pPlayback_handle,
235 m_sAlsaAudioDevice.toLocal8Bit(),
236 SND_PCM_STREAM_PLAYBACK,
237 SND_PCM_NONBLOCK ) ) < 0 ) {
238 ERRORLOG( QString( "Cannot open default audio device [%1] (non-blocking) either: %2" )
239 .arg( m_sAlsaAudioDevice )
240 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
241 return 1;
242 }
243 WARNINGLOG( QString( "Using ALSA device [%1] instead." )
244 .arg( m_sAlsaAudioDevice ) );
245 }
246 if ( ( err = snd_pcm_close( m_pPlayback_handle ) ) < 0 ) {
247 ERRORLOG( QString( "Unable to close non-blocking playback stream of audio device [%1]: %2" )
248 .arg( m_sAlsaAudioDevice )
249 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
250 }
251
252 // Apro il device ( bloccante )
253 if ( ( err = snd_pcm_open( &m_pPlayback_handle,
254 m_sAlsaAudioDevice.toLocal8Bit(),
255 SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 ) {
256 ERRORLOG( QString( "Cannot open audio device [%1] (blocking): %2" )
257 .arg( m_sAlsaAudioDevice )
258 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
259 return 1;
260 }
261
262 snd_pcm_hw_params_t *hw_params;
263 snd_pcm_hw_params_alloca( &hw_params );
264 if ( hw_params == nullptr ) {
265 ERRORLOG( "error in snd_pcm_hw_params_alloca" );
266 return 1;
267 }
268
269 if ( ( err = snd_pcm_hw_params_any( m_pPlayback_handle, hw_params ) ) < 0 ) {
270 ERRORLOG( QString( "error in snd_pcm_hw_params_any: %1" )
271 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
272 return 1;
273 }
274// snd_pcm_hw_params_set_access( m_pPlayback_handle, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED );
275
276 if ( ( err = snd_pcm_hw_params_set_access( m_pPlayback_handle,
277 hw_params,
278 SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 ) {
279 ERRORLOG( QString( "error in snd_pcm_hw_params_set_access: %1" )
280 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
281 return 1;
282 }
283
284 if ( ( err = snd_pcm_hw_params_set_format( m_pPlayback_handle,
285 hw_params,
286 SND_PCM_FORMAT_S16_LE ) ) < 0 ) {
287 ERRORLOG( QString( "error in snd_pcm_hw_params_set_format: %1" )
288 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
289 return 1;
290 }
291
292 snd_pcm_hw_params_set_rate_near( m_pPlayback_handle,
293 hw_params,
295 nullptr );
296
297 if ( ( err = snd_pcm_hw_params_set_channels( m_pPlayback_handle,
298 hw_params, nChannels ) ) < 0 ) {
299 ERRORLOG( QString( "error in snd_pcm_hw_params_set_channels: %1" )
300 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
301 return 1;
302 }
303
304 // Configure buffer size, periods and period size. The user
305 // "BufferSize" setting defines the user's intention for the
306 // number of frames processed in a callback period. In ALSA, this
307 // is the "period", whereas the actual buffer (as reported by
308 // *_get_buffer_size) is sized to keep at least 2 periods' worth
309 // of data.
310 //
311 unsigned nPeriods = 2;
312 if ( ( err = snd_pcm_hw_params_set_periods_near( m_pPlayback_handle,
313 hw_params,
314 &nPeriods,
315 nullptr ) ) < 0 ) {
316 ERRORLOG( QString( "error in snd_pcm_hw_params_set_periods_near: %1" )
317 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
318 return 1;
319 }
320 INFOLOG( QString( "nPeriods: %1" ).arg( nPeriods ) );
321
322 snd_pcm_uframes_t period_size = m_nBufferSize;
323
324 if ( ( err = snd_pcm_hw_params_set_period_size_near( m_pPlayback_handle,
325 hw_params,
326 &period_size,
327 nullptr ) ) < 0 ) {
328 ERRORLOG( QString( "error in snd_pcm_hw_params_set_period_size_near: %1" )
329 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
330 return 1;
331 }
332 m_nBufferSize = period_size;
333
334 if ( ( err = snd_pcm_hw_params( m_pPlayback_handle, hw_params ) ) < 0 ) {
335 ERRORLOG( QString( "error in snd_pcm_hw_params: %1" )
336 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
337 return 1;
338 }
339
340 snd_pcm_hw_params_get_rate( hw_params, &m_nSampleRate, nullptr );
341
342 INFOLOG( QString( "*** PERIOD SIZE: %1" ).arg( period_size ) );
343 INFOLOG( QString( "*** SAMPLE RATE: %1" ).arg( m_nSampleRate ) );
344 INFOLOG( QString( "*** BUFFER SIZE: %1" ).arg( nPeriods * m_nBufferSize ) );
345
346 //snd_pcm_hw_params_free( hw_params );
347
348 m_pOut_L = new float[ m_nBufferSize ];
349 m_pOut_R = new float[ m_nBufferSize ];
350
351 memset( m_pOut_L, 0, m_nBufferSize * sizeof( float ) );
352 memset( m_pOut_R, 0, m_nBufferSize * sizeof( float ) );
353
354 m_bIsRunning = true;
355
356 // start the main thread
357 pthread_attr_t attr;
358 pthread_attr_init( &attr );
359 pthread_create( &alsaAudioDriverThread, &attr, alsaAudioDriver_processCaller, this );
360
361 return 0; // OK
362}
363
364
365
366
368{
369 INFOLOG( "" );
370
371 m_bIsRunning = false;
372
373 pthread_join( alsaAudioDriverThread, nullptr );
374
375 snd_pcm_close( m_pPlayback_handle );
376
377 delete[] m_pOut_L;
378 m_pOut_L = nullptr;
379
380 delete[] m_pOut_R;
381 m_pOut_R = nullptr;
382}
383
385{
386 return m_nBufferSize;
387}
388
390{
391 return m_nSampleRate;
392}
393
395{
396 return m_pOut_L;
397}
398
400{
401 return m_pOut_R;
402}
403};
404
405#endif // H2CORE_HAVE_ALSA
#define sleep(SECONDS)
#define __ERRORLOG(x)
Definition Object.h:251
#define INFOLOG(x)
Definition Object.h:237
#define WARNINGLOG(x)
Definition Object.h:238
#define __INFOLOG(x)
Definition Object.h:249
#define ERRORLOG(x)
Definition Object.h:239
#define ___INFOLOG(x)
Definition Object.h:255
#define ___ERRORLOG(x)
Definition Object.h:257
virtual void disconnect() override
virtual float * getOut_L() override
virtual int init(unsigned nBufferSize) override
virtual float * getOut_R() override
virtual unsigned getBufferSize() override
virtual int connect() override
static QStringList getDevices()
Use the name hints to build a list of potential device names.
AlsaAudioDriver(audioProcessCallback processCallback)
audioProcessCallback m_processCallback
virtual unsigned getSampleRate() override
Base abstract class for audio output classes.
Definition AudioOutput.h:39
Base class.
Definition Object.h:62
static EventQueue * get_instance()
Returns a pointer to the current EventQueue singleton stored in __instance.
Definition EventQueue.h:224
void push_event(const EventType type, const int nValue)
Queues the next event into the EventQueue.
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
unsigned m_nSampleRate
Sample rate of the audio.
int(* audioProcessCallback)(uint32_t, void *)
Definition AudioOutput.h:32
pthread_t alsaAudioDriverThread
@ EVENT_XRUN
Definition EventQueue.h:85
void * alsaAudioDriver_processCaller(void *param)
static int alsa_xrun_recovery(snd_pcm_t *handle, int err)