]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/sys/linux/sound.cpp
Various Mac OS X tweaks to get this to build. Probably breaking things.
[icculus/iodoom3.git] / neo / sys / linux / sound.cpp
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 <stdio.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <malloc.h>
33 #include <sys/ioctl.h>
34 #include <sys/mman.h>
35 // OSS sound interface
36 // http://www.opensound.com/
37 #include <sys/soundcard.h>
38
39 #include "../../idlib/precompiled.h"
40 #include "../../sound/snd_local.h"
41 #include "../posix/posix_public.h"
42 #include "sound.h"
43
44 const char      *s_driverArgs[] = { "best", "oss", "alsa", NULL };
45
46 #ifndef NO_ALSA
47 static idCVar s_driver( "s_driver", s_driverArgs[0], CVAR_SYSTEM | CVAR_ARCHIVE, "sound driver. 'best' will attempt to use alsa and fallback to OSS if not available", s_driverArgs, idCmdSystem::ArgCompletion_String<s_driverArgs> );
48 #else
49 static idCVar s_driver( "s_driver", "oss", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_ROM, "sound driver. only OSS is supported in this build" );
50 #endif
51
52 idAudioHardware *idAudioHardware::Alloc() {
53 #ifndef NO_ALSA
54         if ( !strcmp( s_driver.GetString(), "best" ) ) {
55                 idAudioHardwareALSA *test = new idAudioHardwareALSA;
56                 if ( test->DLOpen() ) {
57                         common->Printf( "Alsa is available\n" );
58                         return test;
59                 }
60                 common->Printf( "Alsa is not available\n" );
61                 delete test;
62                 return new idAudioHardwareOSS;
63         }
64         if ( !strcmp( s_driver.GetString(), "alsa" ) ) {
65                 return new idAudioHardwareALSA;
66         }
67 #endif
68         return new idAudioHardwareOSS;
69 }
70
71 // OSS sound ----------------------------------------------------
72
73 /*
74 ===============
75 idAudioHardware::~idAudioHardware
76 ===============
77 */
78 idAudioHardware::~idAudioHardware() { }
79         
80 /*
81 =================
82 idAudioHardwareOSS::~idAudioHardwareOSS
83 =================       
84 */      
85 idAudioHardwareOSS::~idAudioHardwareOSS() {
86         Release();
87 }
88
89 /*
90 =================
91 idAudioHardwareOSS::Release
92 =================       
93 */      
94 void idAudioHardwareOSS::Release( bool bSilent ) {
95         if (m_audio_fd) {
96                 if (!bSilent) {
97                         common->Printf("------ OSS Sound Shutdown ------\n");
98                 }
99                 if (m_buffer) {
100                         free( m_buffer );
101                         m_buffer = NULL;
102                         m_buffer_size = 0;
103                 }
104                 common->Printf("close sound device\n"); 
105                 if (close(m_audio_fd) == -1) {
106                         common->Warning( "failed to close sound device: %s", strerror(errno) );
107                 }
108                 m_audio_fd = 0;
109                 if (!bSilent) {
110                         common->Printf("--------------------------------\n");
111                 }
112         }
113 }       
114
115 /*
116 =================
117 idAudioHardwareOSS::InitFailed
118 =================       
119 */      
120 void idAudioHardwareOSS::InitFailed() {
121         Release( true );
122         cvarSystem->SetCVarBool( "s_noSound", true );
123         common->Warning( "sound subsystem disabled" );
124         common->Printf( "--------------------------------------\n" );
125 }
126
127 /*
128 =================
129 idAudioHardwareOSS::ExtractOSSVersion
130 =================       
131 */      
132 void idAudioHardwareOSS::ExtractOSSVersion( int version, idStr &str ) const {
133         sprintf( str, "%d.%d.%d", ( version & 0xFF0000 ) >> 16, ( version & 0xFF00 ) >> 8, version & 0xFF );
134 }
135
136 /*
137 =================
138 idAudioHardwareOSS::Initialize
139
140 http://www.4front-tech.com/pguide/index.html
141 though OSS API docs (1.1) advertise AFMT_S32_LE, AFMT_S16_LE is the only output format I've found in kernel emu10k1 headers
142
143 BSD NOTE: With the GNU library, you can use free to free the blocks that memalign, posix_memalign, and valloc return.
144 That does not work in BSD, however--BSD does not provide any way to free such blocks.
145 =================       
146 */
147 idCVar s_device( "s_dsp", "/dev/dsp", CVAR_SYSTEM | CVAR_ARCHIVE, "" );
148
149 bool idAudioHardwareOSS::Initialize( ) {
150         common->Printf("------ OSS Sound Initialization ------\n");
151
152         int requested_sample_format, caps, oss_version;
153         idStr s_compiled_oss_version, s_oss_version;
154         struct audio_buf_info info;
155
156         memset( &info, 0, sizeof( info ) );
157         
158         if (m_audio_fd) {
159                 Release();
160         }
161         
162         // open device ------------------------------------------------
163         if ((m_audio_fd = open( s_device.GetString(), O_WRONLY | O_NONBLOCK, 0)) == -1) {
164                 m_audio_fd = 0;
165                 common->Warning( "failed to open sound device '%s': %s", s_device.GetString(), strerror(errno) );
166                 InitFailed();
167                 return false;
168         }
169         // make it blocking - so write overruns don't fail with 'Resource temporarily unavailable'
170         int flags;
171         if ( ( flags = fcntl( m_audio_fd, F_GETFL ) ) == -1 ) {
172                 common->Warning( "failed fcntl F_GETFL on sound device '%s': %s", s_device.GetString(), strerror( errno ) );
173                 InitFailed();
174                 return false;
175         }
176         flags &= ~O_NONBLOCK;
177         if ( fcntl( m_audio_fd, F_SETFL, flags ) == -1 ) {
178                 common->Warning( "failed to clear O_NONBLOCK on sound device '%s': %s", s_device.GetString(), strerror( errno ) );
179                 InitFailed();
180                 return false;
181         }
182         
183         common->Printf("opened sound device '%s'\n", s_device.GetString());
184         
185         // verify capabilities -----------------------------------------
186
187         // may only be available starting with OSS API v4.0
188         // http://www.fi.opensound.com/developer/SNDCTL_SYSINFO.html
189         // NOTE: at OSS API 4.0 headers, replace OSS_SYSINFO with SNDCTL_SYSINFO
190         oss_sysinfo si;
191         if ( ioctl( m_audio_fd, OSS_SYSINFO, &si ) == -1 ) {
192                 common->Printf( "ioctl SNDCTL_SYSINFO failed: %s\nthis ioctl is only available in OSS/Linux implementation. If you run OSS/Free, don't bother.", strerror( errno ) );
193         } else {
194                 common->Printf( "%s: %s %s\n", s_device.GetString(), si.product, si.version );
195         }
196
197         if ( ioctl( m_audio_fd, SNDCTL_DSP_GETCAPS, &caps ) == -1 ) {
198                 common->Warning( "ioctl SNDCTL_DSP_GETCAPS failed - driver too old?" );
199                 InitFailed();
200                 return false;
201         }
202         common->DPrintf("driver rev %d - capabilities %d\n", caps & DSP_CAP_REVISION, caps);
203         if (ioctl( m_audio_fd, OSS_GETVERSION, &oss_version ) == -1) {
204                 common->Warning( "ioctl OSS_GETVERSION failed" );
205                 InitFailed();
206                 return false;
207         }
208         ExtractOSSVersion( oss_version, s_oss_version );
209         ExtractOSSVersion( SOUND_VERSION, s_compiled_oss_version );
210         common->DPrintf( "OSS interface version %s - compile time %s\n", s_oss_version.c_str(), s_compiled_oss_version.c_str() );
211         if (!(caps & DSP_CAP_MMAP)) {
212                 common->Warning( "driver doesn't have DSP_CAP_MMAP capability" );
213                 InitFailed();
214                 return false;
215         }
216         if (!(caps & DSP_CAP_TRIGGER)) {
217                 common->Warning( "driver doesn't have DSP_CAP_TRIGGER capability" );
218                 InitFailed();
219                 return false;
220         }
221         
222         // sample format -----------------------------------------------
223         requested_sample_format = AFMT_S16_LE;
224         m_sample_format = requested_sample_format;
225         if (ioctl(m_audio_fd, SNDCTL_DSP_SETFMT, &m_sample_format) == -1) {
226                 common->Warning( "ioctl SNDCTL_DSP_SETFMT %d failed: %s", requested_sample_format, strerror(errno) );
227                 InitFailed();
228                 return false;
229         }
230         if ( m_sample_format != requested_sample_format ) {
231                 common->Warning( "ioctl SNDCTL_DSP_SETFMT failed to get the requested sample format %d, got %d", requested_sample_format, m_sample_format );
232                 InitFailed();
233                 return false;
234         }
235         
236         // channels ----------------------------------------------------
237
238         // sanity over number of speakers
239         if ( idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 6 && idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 2 ) {
240                 common->Warning( "invalid value for s_numberOfSpeakers. Use either 2 or 6" );
241                 idSoundSystemLocal::s_numberOfSpeakers.SetInteger( 2 );
242         }
243
244         m_channels = idSoundSystemLocal::s_numberOfSpeakers.GetInteger();
245         if ( ioctl( m_audio_fd, SNDCTL_DSP_CHANNELS, &m_channels ) == -1 ) {
246                 common->Warning( "ioctl SNDCTL_DSP_CHANNELS %d failed: %s", idSoundSystemLocal::s_numberOfSpeakers.GetInteger(), strerror(errno) );
247                 InitFailed();
248                 return false;
249         }
250         if ( m_channels != (unsigned int)idSoundSystemLocal::s_numberOfSpeakers.GetInteger() ) {
251                 common->Warning( "ioctl SNDCTL_DSP_CHANNELS failed to get the %d requested channels, got %d", idSoundSystemLocal::s_numberOfSpeakers.GetInteger(), m_channels );
252                 if ( m_channels != 2 && idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 2 ) {
253                         // we didn't request 2 channels, some drivers reply 1 channel on error but may still let us still get 2 if properly asked
254                         m_channels = 2;
255                         if ( ioctl( m_audio_fd, SNDCTL_DSP_CHANNELS, &m_channels ) == -1 ) {
256                                 common->Warning( "ioctl SNDCTL_DSP_CHANNELS fallback to 2 failed: %s", strerror(errno) );
257                                 InitFailed();
258                                 return false;
259                         }
260                 }
261                 if ( m_channels == 2 ) {
262                         // tell the system to mix 2 channels
263                         common->Warning( "falling back to stereo" );
264                         idSoundSystemLocal::s_numberOfSpeakers.SetInteger( 2 );
265                 } else {
266                         // disable sound
267                         InitFailed();
268                         return false;
269                 }
270         }
271         assert( (int)m_channels == idSoundSystemLocal::s_numberOfSpeakers.GetInteger() );
272         
273         // sampling rate ------------------------------------------------
274         m_speed = PRIMARYFREQ;
275         if ( ioctl( m_audio_fd, SNDCTL_DSP_SPEED, &m_speed ) == -1 ) {
276                 common->Warning( "ioctl SNDCTL_DSP_SPEED %d failed: %s", PRIMARYFREQ, strerror(errno) );
277                 InitFailed();
278                 return false;
279         }
280         // instead of an exact match, do a very close to
281         // there is some horrible Ensonic ES1371 which replies 44101 for a 44100 request
282         if ( abs( m_speed - PRIMARYFREQ ) > 5 ) {
283                 common->Warning( "ioctl SNDCTL_DSP_SPEED failed to get the requested frequency %d, got %d", PRIMARYFREQ, m_speed );
284                 InitFailed();
285                 return false;
286         }
287         common->Printf("%s - bit rate: %d, channels: %d, frequency: %d\n", s_device.GetString(), m_sample_format, m_channels, m_speed);
288         
289         // output buffer ------------------------------------------------
290         // allocate a final buffer target, the sound engine locks, writes, and we write back to the device
291         // we want m_buffer_size ( will have to rename those )
292         // ROOM_SLICES_IN_BUFFER is fixed ( system default, 10 )
293         // MIXBUFFER_SAMPLES is the number of samples found in a slice
294         // each sample is m_channels * sizeof( float ) bytes
295         // in AsyncUpdate we only write one block at a time, so we'd only need to have a final mix buffer sized of a single block
296         m_buffer_size = MIXBUFFER_SAMPLES * m_channels * 2;
297         m_buffer = malloc( m_buffer_size );
298         common->Printf( "allocated a mix buffer of %d bytes\n", m_buffer_size );
299
300         // toggle sound -------------------------------------------------
301         
302         // toggle off before toggling on. that's what OSS source code samples recommends
303         int flag = 0;
304         if (ioctl(m_audio_fd, SNDCTL_DSP_SETTRIGGER, &flag) == -1) {
305                 common->Warning( "ioctl SNDCTL_DSP_SETTRIGGER 0 failed: %s", strerror(errno) );
306         }
307         flag = PCM_ENABLE_OUTPUT;
308         if (ioctl(m_audio_fd, SNDCTL_DSP_SETTRIGGER, &flag) == -1) {
309                 common->Warning( "ioctl SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed: %s", strerror(errno) );
310         }
311
312         common->Printf("--------------------------------------\n");
313         return true;
314 }
315
316 /*
317 ===============
318 idAudioHardwareOSS::Flush
319 ===============
320 */
321 bool idAudioHardwareOSS::Flush( void ) {
322         audio_buf_info ospace;
323         if ( ioctl( m_audio_fd, SNDCTL_DSP_GETOSPACE, &ospace ) == -1 ) {
324                 Sys_Printf( "ioctl SNDCTL_DSP_GETOSPACE failed: %s\n", strerror( errno ) );
325                 return false;
326         }
327         // how many chunks can we write to the audio device right now
328         m_freeWriteChunks = ( ospace.bytes * MIXBUFFER_CHUNKS ) / ( MIXBUFFER_SAMPLES * m_channels * 2 );
329         if ( m_writeChunks ) {
330                 // flush out any remaining chunks we could now
331                 Write( true );
332         }
333         return ( m_freeWriteChunks > 0 );
334 }
335
336 /*
337 =================
338 idAudioHardwareOSS::GetMixBufferSize
339 =================
340 */      
341 int idAudioHardwareOSS::GetMixBufferSize() {
342         //      return MIXBUFFER_SAMPLES * 2 * m_channels;
343         return m_buffer_size;
344 }
345
346 /*
347 =================
348 idAudioHardwareOSS::GetMixBuffer
349 =================
350 */      
351 short* idAudioHardwareOSS::GetMixBuffer() {
352         return (short *)m_buffer;
353 }
354
355 /*
356 ===============
357 idAudioHardwareOSS::Write
358 rely on m_freeWriteChunks which has been set in Flush() before engine did the mixing for this MIXBUFFER_SAMPLE
359 ===============
360 */
361 void idAudioHardwareOSS::Write( bool flushing ) {
362         assert( m_audio_fd );
363         int ret;
364         if ( !flushing && m_writeChunks ) {
365                 // if we write after a new mixing loop, we should have m_writeChunk == 0
366                 // otherwise that last remaining chunk that was never flushed out to the audio device has just been overwritten
367                 Sys_Printf( "idAudioHardwareOSS::Write: %d samples were overflowed and dropped\n", m_writeChunks * MIXBUFFER_SAMPLES / MIXBUFFER_CHUNKS );
368         }
369         if ( !flushing ) {
370                 // if running after the mix loop, then we have a full buffer to write out
371                 m_writeChunks = MIXBUFFER_CHUNKS;
372         }
373         if ( m_freeWriteChunks == 0 ) {
374                 return;
375         }
376         // what to write and how much
377         int pos = (int)m_buffer + ( MIXBUFFER_CHUNKS - m_writeChunks ) * m_channels * 2 * MIXBUFFER_SAMPLES / MIXBUFFER_CHUNKS;
378         int len = Min( m_writeChunks, m_freeWriteChunks ) * m_channels * 2 * MIXBUFFER_SAMPLES / MIXBUFFER_CHUNKS;
379         assert( len > 0 );
380         if ( ( ret = write( m_audio_fd, (void*)pos, len ) ) == -1 ) {
381                 Sys_Printf( "write to audio fd failed: %s\n", strerror( errno ) );
382                 return;
383         }
384         if ( len != ret ) {
385                 Sys_Printf( "short write to audio fd: wrote %d out of %d\n", ret, m_buffer_size );
386                 return;
387         }
388         m_writeChunks -= Min( m_writeChunks, m_freeWriteChunks );
389 }
390
391 /*
392  ===============
393  Sys_LoadOpenAL
394  -===============
395  */
396 bool Sys_LoadOpenAL( void ) {
397         return false;
398 }
399