doom3-gpl
Doom 3 GPL source release
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
sound_alsa.cpp
Go to the documentation of this file.
1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
8 
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 #include "../../idlib/precompiled.h"
29 #include "../../sound/snd_local.h"
30 #include "../posix/posix_public.h"
31 #include "sound.h"
32 
33 #include <dlfcn.h>
34 
35 static idCVar s_alsa_pcm( "s_alsa_pcm", "default", CVAR_SYSTEM | CVAR_ARCHIVE, "which alsa pcm device to use. default, hwplug, hw.. see alsa docs" );
36 static idCVar s_alsa_lib( "s_alsa_lib", "libasound.so.2", CVAR_SYSTEM | CVAR_ARCHIVE, "alsa client sound library" );
37 
38 /*
39 ===============
40 idAudioHardwareALSA::DLOpen
41 ===============
42 */
44  const char *version;
45 
46  if ( m_handle ) {
47  return true;
48  }
49  common->Printf( "dlopen(%s)\n", s_alsa_lib.GetString() );
50  if ( !( m_handle = dlopen( s_alsa_lib.GetString(), RTLD_NOW | RTLD_GLOBAL ) ) ) {
51  common->Printf( "dlopen(%s) failed: %s\n", s_alsa_lib.GetString(), dlerror() );
52  return false;
53  }
54  // print the version if available
55  id_snd_asoundlib_version = ( pfn_snd_asoundlib_version )dlsym( m_handle, "snd_asoundlib_version" );
56  if ( !id_snd_asoundlib_version ) {
57  common->Printf( "dlsym(\"snd_asoundlib_version\") failed: %s\n", dlerror() );
58  common->Warning( "please consider upgrading alsa to a more recent version." );
59  } else {
60  version = id_snd_asoundlib_version();
61  common->Printf( "asoundlib version: %s\n", version );
62  }
63  // dlsym the symbols
64  ALSA_DLSYM(snd_pcm_avail_update);
65  ALSA_DLSYM(snd_pcm_close);
66  ALSA_DLSYM(snd_pcm_hw_params);
67  ALSA_DLSYM(snd_pcm_hw_params_any);
68  ALSA_DLSYM(snd_pcm_hw_params_get_buffer_size);
69  ALSA_DLSYM(snd_pcm_hw_params_set_access);
70  ALSA_DLSYM(snd_pcm_hw_params_set_buffer_size_min);
71  ALSA_DLSYM(snd_pcm_hw_params_set_channels);
72  ALSA_DLSYM(snd_pcm_hw_params_set_format);
73  ALSA_DLSYM(snd_pcm_hw_params_set_rate);
74  ALSA_DLSYM(snd_pcm_hw_params_sizeof);
75  ALSA_DLSYM(snd_pcm_open);
76  ALSA_DLSYM(snd_pcm_prepare);
77  ALSA_DLSYM(snd_pcm_state);
78  ALSA_DLSYM(snd_pcm_writei);
79  ALSA_DLSYM(snd_strerror);
80  return true;
81 }
82 
83 /*
84 ===============
85 idAudioHardwareALSA::Release
86 ===============
87 */
89  if ( m_pcm_handle ) {
90  common->Printf( "close pcm\n" );
93  }
94  if ( m_buffer ) {
95  free( m_buffer );
96  m_buffer = NULL;
97  }
98  if ( m_handle ) {
99  common->Printf( "dlclose\n" );
100  dlclose( m_handle );
101  m_handle = NULL;
102  }
103 }
104 
105 /*
106 =================
107 idAudioHardwareALSA::InitFailed
108 =================
109 */
111  Release();
112  cvarSystem->SetCVarBool( "s_noSound", true );
113  common->Warning( "sound subsystem disabled\n" );
114  common->Printf( "--------------------------------------\n" );
115 }
116 
117 /*
118 =====================
119 idAudioHardwareALSA::Initialize
120 =====================
121 */
123  int err;
124 
125  common->Printf( "------ Alsa Sound Initialization -----\n" );
126  if ( !DLOpen() ) {
127  InitFailed();
128  return false;
129  }
130  if ( ( err = id_snd_pcm_open( &m_pcm_handle, s_alsa_pcm.GetString(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ) ) < 0 ) {
131  common->Printf( "snd_pcm_open SND_PCM_STREAM_PLAYBACK '%s' failed: %s\n", s_alsa_pcm.GetString(), id_snd_strerror( err ) );
132  InitFailed();
133  return false;
134  }
135  common->Printf( "opened Alsa PCM device %s for playback\n", s_alsa_pcm.GetString() );
136 
137  // set hardware parameters ----------------------------------------------------------------------
138 
139  // init hwparams with the full configuration space
140  snd_pcm_hw_params_t *hwparams;
141  // this one is a define
142  id_snd_pcm_hw_params_alloca( &hwparams );
143  if ( ( err = id_snd_pcm_hw_params_any( m_pcm_handle, hwparams ) ) < 0 ) {
144  common->Printf( "cannot configure the PCM device: %s\n", id_snd_strerror( err ) );
145  InitFailed();
146  return false;
147  }
148 
149  if ( ( err = id_snd_pcm_hw_params_set_access( m_pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 ) {
150  common->Printf( "SND_PCM_ACCESS_RW_INTERLEAVED failed: %s\n", id_snd_strerror( err ) );
151  InitFailed();
152  return false;
153  }
154 
155  if ( ( err = id_snd_pcm_hw_params_set_format( m_pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE ) ) < 0 ) {
156  common->Printf( "SND_PCM_FORMAT_S16_LE failed: %s\n", id_snd_strerror( err ) );
157  InitFailed();
158  return false;
159  }
160 
161  // channels
162 
163  // sanity over number of speakers
164  if ( idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 6 && idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 2 ) {
165  common->Warning( "invalid value for s_numberOfSpeakers. Use either 2 or 6" );
167  }
168 
170  if ( ( err = id_snd_pcm_hw_params_set_channels( m_pcm_handle, hwparams, m_channels ) ) < 0 ) {
171  common->Printf( "error setting %d channels: %s\n", m_channels, id_snd_strerror( err ) );
172  if ( idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 2 ) {
173  // fallback to stereo if that works
174  m_channels = 2;
175  if ( ( err = id_snd_pcm_hw_params_set_channels( m_pcm_handle, hwparams, m_channels ) ) < 0 ) {
176  common->Printf( "fallback to stereo failed: %s\n", id_snd_strerror( err ) );
177  InitFailed();
178  return false;
179  } else {
180  common->Printf( "fallback to stereo\n" );
182  }
183  } else {
184  InitFailed();
185  return false;
186  }
187  }
188 
189  // set sample rate (frequency)
190  if ( ( err = id_snd_pcm_hw_params_set_rate( m_pcm_handle, hwparams, PRIMARYFREQ, 0 ) ) < 0 ) {
191  common->Printf( "failed to set 44.1KHz rate: %s - try ( +set s_alsa_pcm plughw:0 ? )\n", id_snd_strerror( err ) );
192  InitFailed();
193  return false;
194  }
195 
196  // have enough space in the input buffer for our MIXBUFFER_SAMPLE feedings and async ticks
197  snd_pcm_uframes_t frames;
198  frames = MIXBUFFER_SAMPLES + MIXBUFFER_SAMPLES / 3;
199  if ( ( err = id_snd_pcm_hw_params_set_buffer_size_min( m_pcm_handle, hwparams, &frames ) ) < 0 ) {
200  common->Printf( "buffer size select failed: %s\n", id_snd_strerror( err ) );
201  InitFailed();
202  return false;
203  }
204 
205  // apply parameters
206  if ( ( err = id_snd_pcm_hw_params( m_pcm_handle, hwparams ) ) < 0 ) {
207  common->Printf( "snd_pcm_hw_params failed: %s\n", id_snd_strerror( err ) );
208  InitFailed();
209  return false;
210  }
211 
212  // check the buffer size
213  if ( ( err = id_snd_pcm_hw_params_get_buffer_size( hwparams, &frames ) ) < 0 ) {
214  common->Printf( "snd_pcm_hw_params_get_buffer_size failed: %s\n", id_snd_strerror( err ) );
215  } else {
216  common->Printf( "device buffer size: %lu frames ( %lu bytes )\n", ( long unsigned int )frames, frames * m_channels * 2 );
217  }
218 
219  // TODO: can use swparams to setup the device so it doesn't underrun but rather loops over
220  // snd_pcm_sw_params_set_stop_threshold
221  // To get alsa to just loop on underruns. set the swparam stop_threshold to equal buffer size. The sound buffer will just loop and never throw an xrun.
222 
223  // allocate the final mix buffer
225  m_buffer = malloc( m_buffer_size );
226  common->Printf( "allocated a mix buffer of %d bytes\n", m_buffer_size );
227 
228 #ifdef _DEBUG
229  // verbose the state
230  snd_pcm_state_t curstate = id_snd_pcm_state( m_pcm_handle );
231  assert( curstate == SND_PCM_STATE_PREPARED );
232 #endif
233 
234  common->Printf( "--------------------------------------\n" );
235  return true;
236 }
237 
238 /*
239 ===============
240 idAudioHardwareALSA::~idAudioHardwareALSA
241 ===============
242 */
244  common->Printf( "----------- Alsa Shutdown ------------\n" );
245  Release();
246  common->Printf( "--------------------------------------\n" );
247 }
248 
249 /*
250 =================
251 idAudioHardwareALSA::GetMixBufferSize
252 =================
253 */
255  return m_buffer_size;
256 }
257 
258 /*
259 =================
260 idAudioHardwareALSA::GetMixBuffer
261 =================
262 */
264  return (short *)m_buffer;
265 }
266 
267 /*
268 ===============
269 idAudioHardwareALSA::Flush
270 ===============
271 */
273  int ret;
274  snd_pcm_state_t state;
275  state = id_snd_pcm_state( m_pcm_handle );
276  if ( state != SND_PCM_STATE_RUNNING && state != SND_PCM_STATE_PREPARED ) {
277  if ( ( ret = id_snd_pcm_prepare( m_pcm_handle ) ) < 0 ) {
278  Sys_Printf( "failed to recover from SND_PCM_STATE_XRUN: %s\n", id_snd_strerror( ret ) );
279  cvarSystem->SetCVarBool( "s_noSound", true );
280  return false;
281  }
282  Sys_Printf( "preparing audio device for output\n" );
283  }
284  Write( true );
285 }
286 
287 /*
288 ===============
289 idAudioHardwareALSA::Write
290 rely on m_freeWriteChunks which has been set in Flush() before engine did the mixing for this MIXBUFFER_SAMPLE
291 ===============
292 */
293 void idAudioHardwareALSA::Write( bool flushing ) {
294  if ( !flushing && m_remainingFrames ) {
295  // if we write after a new mixing loop, we should have m_writeChunk == 0
296  // otherwise that last remaining chunk that was never flushed out to the audio device has just been overwritten
297  Sys_Printf( "idAudioHardwareALSA::Write: %d frames overflowed and dropped\n", m_remainingFrames );
298  }
299  if ( !flushing ) {
300  // if running after the mix loop, then we have a full buffer to write out
302  }
303  if ( m_remainingFrames == 0 ) {
304  return;
305  }
306  // write the max frames you can in one shot - we need to write it all out in Flush() calls before the next Write() happens
307  int pos = (int)m_buffer + ( MIXBUFFER_SAMPLES - m_remainingFrames ) * m_channels * 2;
308  snd_pcm_sframes_t frames = id_snd_pcm_writei( m_pcm_handle, (void*)pos, m_remainingFrames );
309  if ( frames < 0 ) {
310  if ( frames != -EAGAIN ) {
311  Sys_Printf( "snd_pcm_writei %d frames failed: %s\n", m_remainingFrames, id_snd_strerror( frames ) );
312  }
313  return;
314  }
315  m_remainingFrames -= frames;
316 }
pfn_snd_pcm_hw_params_set_buffer_size_min id_snd_pcm_hw_params_set_buffer_size_min
Definition: sound.h:173
assert(prefInfo.fullscreenBtn)
idCVarSystem * cvarSystem
Definition: CVarSystem.cpp:487
static idCVar s_numberOfSpeakers
Definition: snd_local.h:802
virtual ~idAudioHardwareALSA()
Definition: sound_alsa.cpp:243
snd_pcm_t * m_pcm_handle
Definition: sound.h:119
const int MIXBUFFER_SAMPLES
Definition: Simd.h:84
const char *(* pfn_snd_asoundlib_version)(void)
Definition: sound.h:94
void Sys_Printf(const char *msg,...)
void * m_handle
Definition: sound.h:127
case const int
Definition: Callbacks.cpp:52
pfn_snd_pcm_hw_params id_snd_pcm_hw_params
Definition: sound.h:169
pfn_snd_pcm_hw_params_set_rate id_snd_pcm_hw_params_set_rate
Definition: sound.h:176
pfn_snd_pcm_hw_params_any id_snd_pcm_hw_params_any
Definition: sound.h:170
short * GetMixBuffer(void)
Definition: sound_alsa.cpp:263
pfn_snd_pcm_hw_params_set_access id_snd_pcm_hw_params_set_access
Definition: sound.h:172
struct version_s version
#define id_snd_pcm_hw_params_alloca(ptr)
Definition: sound.h:92
int GetMixBufferSize(void)
Definition: sound_alsa.cpp:254
idCommon * common
Definition: Common.cpp:206
bool Initialize(void)
Definition: sound_alsa.cpp:122
#define NULL
Definition: Lib.h:88
void SetInteger(const int value)
Definition: CVarSystem.h:148
virtual void SetCVarBool(const char *name, const bool value, int flags=0)=0
int GetInteger(void) const
Definition: CVarSystem.h:143
pfn_snd_strerror id_snd_strerror
Definition: sound.h:168
pfn_snd_pcm_writei id_snd_pcm_writei
Definition: sound.h:181
pfn_snd_asoundlib_version id_snd_asoundlib_version
Definition: sound.h:164
virtual void Printf(const char *fmt,...) id_attribute((format(printf
pfn_snd_pcm_hw_params_set_format id_snd_pcm_hw_params_set_format
Definition: sound.h:175
#define ALSA_DLSYM(SYM)
Definition: sound.h:112
const char * GetString(void) const
Definition: CVarSystem.h:141
void Write(bool flushing)
Definition: sound_alsa.cpp:293
static WindowRef ValidModeCallbackProc inCallback OSStatus err
pfn_snd_pcm_open id_snd_pcm_open
Definition: sound.h:178
pfn_snd_pcm_hw_params_set_channels id_snd_pcm_hw_params_set_channels
Definition: sound.h:174
pfn_snd_pcm_hw_params_get_buffer_size id_snd_pcm_hw_params_get_buffer_size
Definition: sound.h:171
pfn_snd_pcm_prepare id_snd_pcm_prepare
Definition: sound.h:179
pfn_snd_pcm_state id_snd_pcm_state
Definition: sound.h:180
int m_remainingFrames
Definition: sound.h:125
const int PRIMARYFREQ
Definition: snd_local.h:67
virtual void virtual void Warning(const char *fmt,...) id_attribute((format(printf
pfn_snd_pcm_close id_snd_pcm_close
Definition: sound.h:167
unsigned int m_channels
Definition: sound.h:120
void * m_buffer
Definition: sound.h:121