hydrogen 1.2.6
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-2025 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 free( (void *)sIOID );
168
169 if ( sName ) {
170 free( (void *)sName );
171 }
172
173 continue;
174 }
175
176 const QString sDev = QString( sName );
177 if ( sName ) {
178 free( (void *)sName );
179 }
180 if ( sIOID ) {
181 free( (void *)sIOID );
182 }
183 result.push_back( sDev );
184 }
185 snd_device_name_free_hint( pHints );
186 return result;
187}
188
190 : AudioOutput()
191 , m_bIsRunning( false )
192 , m_pOut_L( nullptr )
193 , m_pOut_R( nullptr )
194 , m_nXRuns( 0 )
195 , m_nBufferSize( 0 )
196 , m_pPlayback_handle( nullptr )
197 , m_processCallback( processCallback )
198{
201}
202
204{
205 if ( m_nXRuns > 0 ) {
206 WARNINGLOG( QString( "%1 xruns" ).arg( m_nXRuns ) );
207 }
208
209 snd_config_update_free_global();
210
211}
212
213
214int AlsaAudioDriver::init( unsigned nBufferSize )
215{
216 m_nBufferSize = nBufferSize;
217
218 return 0; // ok
219}
220
221
223{
224 INFOLOG( "to: " + m_sAlsaAudioDevice );
225 int nChannels = 2;
226
227 int err;
228
229 // provo ad aprire il device per verificare se e' libero ( non bloccante )
230 if ( ( err = snd_pcm_open( &m_pPlayback_handle,
231 m_sAlsaAudioDevice.toLocal8Bit(),
232 SND_PCM_STREAM_PLAYBACK,
233 SND_PCM_NONBLOCK ) ) < 0 ) {
234 ERRORLOG( QString( "Cannot open audio device [%1] (non-blocking): %2" )
235 .arg( m_sAlsaAudioDevice )
236 .arg( snd_strerror( err ) ) );
237
238 // Use the default device as a fallback.
239 m_sAlsaAudioDevice = "default";
240 if ( ( err = snd_pcm_open( &m_pPlayback_handle,
241 m_sAlsaAudioDevice.toLocal8Bit(),
242 SND_PCM_STREAM_PLAYBACK,
243 SND_PCM_NONBLOCK ) ) < 0 ) {
244 ERRORLOG( QString( "Cannot open default audio device [%1] (non-blocking) either: %2" )
245 .arg( m_sAlsaAudioDevice )
246 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
247 return 1;
248 }
249 WARNINGLOG( QString( "Using ALSA device [%1] instead." )
250 .arg( m_sAlsaAudioDevice ) );
251 }
252 if ( ( err = snd_pcm_close( m_pPlayback_handle ) ) < 0 ) {
253 ERRORLOG( QString( "Unable to close non-blocking playback stream of audio device [%1]: %2" )
254 .arg( m_sAlsaAudioDevice )
255 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
256 }
257
258 // Apro il device ( bloccante )
259 if ( ( err = snd_pcm_open( &m_pPlayback_handle,
260 m_sAlsaAudioDevice.toLocal8Bit(),
261 SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 ) {
262 ERRORLOG( QString( "Cannot open audio device [%1] (blocking): %2" )
263 .arg( m_sAlsaAudioDevice )
264 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
265 return 1;
266 }
267
268 snd_pcm_hw_params_t *hw_params;
269 snd_pcm_hw_params_alloca( &hw_params );
270 if ( hw_params == nullptr ) {
271 ERRORLOG( "error in snd_pcm_hw_params_alloca" );
272 return 1;
273 }
274
275 if ( ( err = snd_pcm_hw_params_any( m_pPlayback_handle, hw_params ) ) < 0 ) {
276 ERRORLOG( QString( "error in snd_pcm_hw_params_any: %1" )
277 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
278 return 1;
279 }
280// snd_pcm_hw_params_set_access( m_pPlayback_handle, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED );
281
282 if ( ( err = snd_pcm_hw_params_set_access( m_pPlayback_handle,
283 hw_params,
284 SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 ) {
285 ERRORLOG( QString( "error in snd_pcm_hw_params_set_access: %1" )
286 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
287 return 1;
288 }
289
290 if ( ( err = snd_pcm_hw_params_set_format( m_pPlayback_handle,
291 hw_params,
292 SND_PCM_FORMAT_S16_LE ) ) < 0 ) {
293 ERRORLOG( QString( "error in snd_pcm_hw_params_set_format: %1" )
294 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
295 return 1;
296 }
297
298 snd_pcm_hw_params_set_rate_near( m_pPlayback_handle,
299 hw_params,
301 nullptr );
302
303 if ( ( err = snd_pcm_hw_params_set_channels( m_pPlayback_handle,
304 hw_params, nChannels ) ) < 0 ) {
305 ERRORLOG( QString( "error in snd_pcm_hw_params_set_channels: %1" )
306 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
307 return 1;
308 }
309
310 // Configure buffer size, periods and period size. The user
311 // "BufferSize" setting defines the user's intention for the
312 // number of frames processed in a callback period. In ALSA, this
313 // is the "period", whereas the actual buffer (as reported by
314 // *_get_buffer_size) is sized to keep at least 2 periods' worth
315 // of data.
316 //
317 unsigned nPeriods = 2;
318 if ( ( err = snd_pcm_hw_params_set_periods_near( m_pPlayback_handle,
319 hw_params,
320 &nPeriods,
321 nullptr ) ) < 0 ) {
322 ERRORLOG( QString( "error in snd_pcm_hw_params_set_periods_near: %1" )
323 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
324 return 1;
325 }
326 INFOLOG( QString( "nPeriods: %1" ).arg( nPeriods ) );
327
328 snd_pcm_uframes_t period_size = m_nBufferSize;
329
330 if ( ( err = snd_pcm_hw_params_set_period_size_near( m_pPlayback_handle,
331 hw_params,
332 &period_size,
333 nullptr ) ) < 0 ) {
334 ERRORLOG( QString( "error in snd_pcm_hw_params_set_period_size_near: %1" )
335 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
336 return 1;
337 }
338 m_nBufferSize = period_size;
339
340 if ( ( err = snd_pcm_hw_params( m_pPlayback_handle, hw_params ) ) < 0 ) {
341 ERRORLOG( QString( "error in snd_pcm_hw_params: %1" )
342 .arg( QString::fromLocal8Bit(snd_strerror(err)) ) );
343 return 1;
344 }
345
346 snd_pcm_hw_params_get_rate( hw_params, &m_nSampleRate, nullptr );
347
348 INFOLOG( QString( "*** PERIOD SIZE: %1" ).arg( period_size ) );
349 INFOLOG( QString( "*** SAMPLE RATE: %1" ).arg( m_nSampleRate ) );
350 INFOLOG( QString( "*** BUFFER SIZE: %1" ).arg( nPeriods * m_nBufferSize ) );
351
352 //snd_pcm_hw_params_free( hw_params );
353
354 m_pOut_L = new float[ m_nBufferSize ];
355 m_pOut_R = new float[ m_nBufferSize ];
356
357 memset( m_pOut_L, 0, m_nBufferSize * sizeof( float ) );
358 memset( m_pOut_R, 0, m_nBufferSize * sizeof( float ) );
359
360 m_bIsRunning = true;
361
362 // start the main thread
363 pthread_attr_t attr;
364 pthread_attr_init( &attr );
365 pthread_create( &alsaAudioDriverThread, &attr, alsaAudioDriver_processCaller, this );
366
367 return 0; // OK
368}
369
370
371
372
374{
375 INFOLOG( "" );
376
377 m_bIsRunning = false;
378
379 pthread_join( alsaAudioDriverThread, nullptr );
380
381 snd_pcm_close( m_pPlayback_handle );
382
383 delete[] m_pOut_L;
384 m_pOut_L = nullptr;
385
386 delete[] m_pOut_R;
387 m_pOut_R = nullptr;
388}
389
391{
392 return m_nBufferSize;
393}
394
396{
397 return m_nSampleRate;
398}
399
401{
402 return m_pOut_L;
403}
404
406{
407 return m_pOut_R;
408}
409};
410
411#endif // H2CORE_HAVE_ALSA
#define sleep(SECONDS)
#define __ERRORLOG(x)
Definition Object.h:254
#define INFOLOG(x)
Definition Object.h:240
#define WARNINGLOG(x)
Definition Object.h:241
#define __INFOLOG(x)
Definition Object.h:252
#define ERRORLOG(x)
Definition Object.h:242
#define ___INFOLOG(x)
Definition Object.h:258
#define ___ERRORLOG(x)
Definition Object.h:260
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 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)