1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 
23 #include <stdlib.h>
24 #include <stdio.h>
25 
26 #ifdef USE_LOCAL_HEADERS
27 #	include "SDL.h"
28 #else
29 #	include <SDL.h>
30 #endif
31 
32 #include "../qcommon/q_shared.h"
33 #include "../client/snd_local.h"
34 
35 qboolean snd_inited = qfalse;
36 
37 cvar_t *s_sdlBits;
38 cvar_t *s_sdlSpeed;
39 cvar_t *s_sdlChannels;
40 cvar_t *s_sdlDevSamps;
41 cvar_t *s_sdlMixSamps;
42 
43 /* The audio callback. All the magic happens here. */
44 static int dmapos = 0;
45 static int dmasize = 0;
46 
47 /*
48 ===============
49 SNDDMA_AudioCallback
50 ===============
51 */
SNDDMA_AudioCallback(void * userdata,Uint8 * stream,int len)52 static void SNDDMA_AudioCallback(void *userdata, Uint8 *stream, int len)
53 {
54 	int pos = (dmapos * (dma.samplebits/8));
55 	if (pos >= dmasize)
56 		dmapos = pos = 0;
57 
58 	if (!snd_inited)  /* shouldn't happen, but just in case... */
59 	{
60 		memset(stream, '\0', len);
61 		return;
62 	}
63 	else
64 	{
65 		int tobufend = dmasize - pos;  /* bytes to buffer's end. */
66 		int len1 = len;
67 		int len2 = 0;
68 
69 		if (len1 > tobufend)
70 		{
71 			len1 = tobufend;
72 			len2 = len - len1;
73 		}
74 		memcpy(stream, dma.buffer + pos, len1);
75 		if (len2 <= 0)
76 			dmapos += (len1 / (dma.samplebits/8));
77 		else  /* wraparound? */
78 		{
79 			memcpy(stream+len1, dma.buffer, len2);
80 			dmapos = (len2 / (dma.samplebits/8));
81 		}
82 	}
83 
84 	if (dmapos >= dmasize)
85 		dmapos = 0;
86 }
87 
88 static struct
89 {
90 	Uint16	enumFormat;
91 	char		*stringFormat;
92 } formatToStringTable[ ] =
93 {
94 	{ AUDIO_U8,     "AUDIO_U8" },
95 	{ AUDIO_S8,     "AUDIO_S8" },
96 	{ AUDIO_U16LSB, "AUDIO_U16LSB" },
97 	{ AUDIO_S16LSB, "AUDIO_S16LSB" },
98 	{ AUDIO_U16MSB, "AUDIO_U16MSB" },
99 	{ AUDIO_S16MSB, "AUDIO_S16MSB" }
100 };
101 
102 static int formatToStringTableSize = ARRAY_LEN( formatToStringTable );
103 
104 /*
105 ===============
106 SNDDMA_PrintAudiospec
107 ===============
108 */
SNDDMA_PrintAudiospec(const char * str,const SDL_AudioSpec * spec)109 static void SNDDMA_PrintAudiospec(const char *str, const SDL_AudioSpec *spec)
110 {
111 	int		i;
112 	char	*fmt = NULL;
113 
114 	Com_Printf("%s:\n", str);
115 
116 	for( i = 0; i < formatToStringTableSize; i++ ) {
117 		if( spec->format == formatToStringTable[ i ].enumFormat ) {
118 			fmt = formatToStringTable[ i ].stringFormat;
119 		}
120 	}
121 
122 	if( fmt ) {
123 		Com_Printf( "  Format:   %s\n", fmt );
124 	} else {
125 		Com_Printf( "  Format:   " S_COLOR_RED "UNKNOWN\n");
126 	}
127 
128 	Com_Printf( "  Freq:     %d\n", (int) spec->freq );
129 	Com_Printf( "  Samples:  %d\n", (int) spec->samples );
130 	Com_Printf( "  Channels: %d\n", (int) spec->channels );
131 }
132 
133 /*
134 ===============
135 SNDDMA_Init
136 ===============
137 */
SNDDMA_Init(void)138 qboolean SNDDMA_Init(void)
139 {
140 	char drivername[128];
141 	SDL_AudioSpec desired;
142 	SDL_AudioSpec obtained;
143 	int tmp;
144 
145 	if (snd_inited)
146 		return qtrue;
147 
148 	if (!s_sdlBits) {
149 		s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE);
150 		s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE);
151 		s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE);
152 		s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE);
153 		s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE);
154 	}
155 
156 	Com_Printf( "SDL_Init( SDL_INIT_AUDIO )... " );
157 
158 	if (!SDL_WasInit(SDL_INIT_AUDIO))
159 	{
160 		if (SDL_Init(SDL_INIT_AUDIO) == -1)
161 		{
162 			Com_Printf( "FAILED (%s)\n", SDL_GetError( ) );
163 			return qfalse;
164 		}
165 	}
166 
167 	Com_Printf( "OK\n" );
168 
169 	if (SDL_AudioDriverName(drivername, sizeof (drivername)) == NULL)
170 		strcpy(drivername, "(UNKNOWN)");
171 	Com_Printf("SDL audio driver is \"%s\".\n", drivername);
172 
173 	memset(&desired, '\0', sizeof (desired));
174 	memset(&obtained, '\0', sizeof (obtained));
175 
176 	tmp = ((int) s_sdlBits->value);
177 	if ((tmp != 16) && (tmp != 8))
178 		tmp = 16;
179 
180 	desired.freq = (int) s_sdlSpeed->value;
181 	if(!desired.freq) desired.freq = 22050;
182 	desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8);
183 
184 	// I dunno if this is the best idea, but I'll give it a try...
185 	//  should probably check a cvar for this...
186 	if (s_sdlDevSamps->value)
187 		desired.samples = s_sdlDevSamps->value;
188 	else
189 	{
190 		// just pick a sane default.
191 		if (desired.freq <= 11025)
192 			desired.samples = 256;
193 		else if (desired.freq <= 22050)
194 			desired.samples = 512;
195 		else if (desired.freq <= 44100)
196 			desired.samples = 1024;
197 		else
198 			desired.samples = 2048;  // (*shrug*)
199 	}
200 
201 	desired.channels = (int) s_sdlChannels->value;
202 	desired.callback = SNDDMA_AudioCallback;
203 
204 	if (SDL_OpenAudio(&desired, &obtained) == -1)
205 	{
206 		Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError());
207 		SDL_QuitSubSystem(SDL_INIT_AUDIO);
208 		return qfalse;
209 	}
210 
211 	SNDDMA_PrintAudiospec("SDL_AudioSpec", &obtained);
212 
213 	// dma.samples needs to be big, or id's mixer will just refuse to
214 	//  work at all; we need to keep it significantly bigger than the
215 	//  amount of SDL callback samples, and just copy a little each time
216 	//  the callback runs.
217 	// 32768 is what the OSS driver filled in here on my system. I don't
218 	//  know if it's a good value overall, but at least we know it's
219 	//  reasonable...this is why I let the user override.
220 	tmp = s_sdlMixSamps->value;
221 	if (!tmp)
222 		tmp = (obtained.samples * obtained.channels) * 10;
223 
224 	if (tmp & (tmp - 1))  // not a power of two? Seems to confuse something.
225 	{
226 		int val = 1;
227 		while (val < tmp)
228 			val <<= 1;
229 
230 		tmp = val;
231 	}
232 
233 	dmapos = 0;
234 	dma.samplebits = obtained.format & 0xFF;  // first byte of format is bits.
235 	dma.channels = obtained.channels;
236 	dma.samples = tmp;
237 	dma.submission_chunk = 1;
238 	dma.speed = obtained.freq;
239 	dmasize = (dma.samples * (dma.samplebits/8));
240 	dma.buffer = calloc(1, dmasize);
241 
242 	Com_Printf("Starting SDL audio callback...\n");
243 	SDL_PauseAudio(0);  // start callback.
244 
245 	Com_Printf("SDL audio initialized.\n");
246 	snd_inited = qtrue;
247 	return qtrue;
248 }
249 
250 /*
251 ===============
252 SNDDMA_GetDMAPos
253 ===============
254 */
SNDDMA_GetDMAPos(void)255 int SNDDMA_GetDMAPos(void)
256 {
257 	return dmapos;
258 }
259 
260 /*
261 ===============
262 SNDDMA_Shutdown
263 ===============
264 */
SNDDMA_Shutdown(void)265 void SNDDMA_Shutdown(void)
266 {
267 	Com_Printf("Closing SDL audio device...\n");
268 	SDL_PauseAudio(1);
269 	SDL_CloseAudio();
270 	SDL_QuitSubSystem(SDL_INIT_AUDIO);
271 	free(dma.buffer);
272 	dma.buffer = NULL;
273 	dmapos = dmasize = 0;
274 	snd_inited = qfalse;
275 	Com_Printf("SDL audio device shut down.\n");
276 }
277 
278 /*
279 ===============
280 SNDDMA_Submit
281 
282 Send sound to device if buffer isn't really the dma buffer
283 ===============
284 */
SNDDMA_Submit(void)285 void SNDDMA_Submit(void)
286 {
287 	SDL_UnlockAudio();
288 }
289 
290 /*
291 ===============
292 SNDDMA_BeginPainting
293 ===============
294 */
SNDDMA_BeginPainting(void)295 void SNDDMA_BeginPainting (void)
296 {
297 	SDL_LockAudio();
298 }
299