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 =
103   sizeof( formatToStringTable ) / sizeof( formatToStringTable[ 0 ] );
104 
105 /*
106 ===============
107 SNDDMA_PrintAudiospec
108 ===============
109 */
SNDDMA_PrintAudiospec(const char * str,const SDL_AudioSpec * spec)110 static void SNDDMA_PrintAudiospec(const char *str, const SDL_AudioSpec *spec)
111 {
112 	int		i;
113 	char	*fmt = NULL;
114 
115 	Com_Printf("%s:\n", str);
116 
117 	for( i = 0; i < formatToStringTableSize; i++ ) {
118 		if( spec->format == formatToStringTable[ i ].enumFormat ) {
119 			fmt = formatToStringTable[ i ].stringFormat;
120 		}
121 	}
122 
123 	if( fmt ) {
124 		Com_Printf( "  Format:   %s\n", fmt );
125 	} else {
126 		Com_Printf( "  Format:   " S_COLOR_RED "UNKNOWN\n");
127 	}
128 
129 	Com_Printf( "  Freq:     %d\n", (int) spec->freq );
130 	Com_Printf( "  Samples:  %d\n", (int) spec->samples );
131 	Com_Printf( "  Channels: %d\n", (int) spec->channels );
132 }
133 
134 /*
135 ===============
136 SNDDMA_Init
137 ===============
138 */
SNDDMA_Init(void)139 qboolean SNDDMA_Init(void)
140 {
141 	char drivername[128];
142 	SDL_AudioSpec desired;
143 	SDL_AudioSpec obtained;
144 	int tmp;
145 
146 	if (snd_inited)
147 		return qtrue;
148 
149 	if (!s_sdlBits) {
150 		s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE);
151 		s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE);
152 		s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE);
153 		s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE);
154 		s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE);
155 	}
156 
157 	Com_Printf( "SDL_Init( SDL_INIT_AUDIO )... " );
158 
159 	if (!SDL_WasInit(SDL_INIT_AUDIO))
160 	{
161 		if (SDL_Init(SDL_INIT_AUDIO) == -1)
162 		{
163 			Com_Printf( "FAILED (%s)\n", SDL_GetError( ) );
164 			return qfalse;
165 		}
166 	}
167 
168 	Com_Printf( "OK\n" );
169 
170 	if (SDL_AudioDriverName(drivername, sizeof (drivername)) == NULL)
171 		strcpy(drivername, "(UNKNOWN)");
172 	Com_Printf("SDL audio driver is \"%s\".\n", drivername);
173 
174 	memset(&desired, '\0', sizeof (desired));
175 	memset(&obtained, '\0', sizeof (obtained));
176 
177 	tmp = ((int) s_sdlBits->value);
178 	if ((tmp != 16) && (tmp != 8))
179 		tmp = 16;
180 
181 	desired.freq = (int) s_sdlSpeed->value;
182 	if(!desired.freq) desired.freq = 22050;
183 	desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8);
184 
185 	// I dunno if this is the best idea, but I'll give it a try...
186 	//  should probably check a cvar for this...
187 	if (s_sdlDevSamps->value)
188 		desired.samples = s_sdlDevSamps->value;
189 	else
190 	{
191 		// just pick a sane default.
192 		if (desired.freq <= 11025)
193 			desired.samples = 256;
194 		else if (desired.freq <= 22050)
195 			desired.samples = 512;
196 		else if (desired.freq <= 44100)
197 			desired.samples = 1024;
198 		else
199 			desired.samples = 2048;  // (*shrug*)
200 	}
201 
202 	desired.channels = (int) s_sdlChannels->value;
203 	desired.callback = SNDDMA_AudioCallback;
204 
205 	if (SDL_OpenAudio(&desired, &obtained) == -1)
206 	{
207 		Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError());
208 		SDL_QuitSubSystem(SDL_INIT_AUDIO);
209 		return qfalse;
210 	}
211 
212 	SNDDMA_PrintAudiospec("SDL_AudioSpec", &obtained);
213 
214 	// dma.samples needs to be big, or id's mixer will just refuse to
215 	//  work at all; we need to keep it significantly bigger than the
216 	//  amount of SDL callback samples, and just copy a little each time
217 	//  the callback runs.
218 	// 32768 is what the OSS driver filled in here on my system. I don't
219 	//  know if it's a good value overall, but at least we know it's
220 	//  reasonable...this is why I let the user override.
221 	tmp = s_sdlMixSamps->value;
222 	if (!tmp)
223 		tmp = (obtained.samples * obtained.channels) * 10;
224 
225 	if (tmp & (tmp - 1))  // not a power of two? Seems to confuse something.
226 	{
227 		int val = 1;
228 		while (val < tmp)
229 			val <<= 1;
230 
231 		tmp = val;
232 	}
233 
234 	dmapos = 0;
235 	dma.samplebits = obtained.format & 0xFF;  // first byte of format is bits.
236 	dma.channels = obtained.channels;
237 	dma.samples = tmp;
238 	dma.submission_chunk = 1;
239 	dma.speed = obtained.freq;
240 	dmasize = (dma.samples * (dma.samplebits/8));
241 	dma.buffer = calloc(1, dmasize);
242 
243 	Com_Printf("Starting SDL audio callback...\n");
244 	SDL_PauseAudio(0);  // start callback.
245 
246 	Com_Printf("SDL audio initialized.\n");
247 	snd_inited = qtrue;
248 	return qtrue;
249 }
250 
251 /*
252 ===============
253 SNDDMA_GetDMAPos
254 ===============
255 */
SNDDMA_GetDMAPos(void)256 int SNDDMA_GetDMAPos(void)
257 {
258 	return dmapos;
259 }
260 
261 /*
262 ===============
263 SNDDMA_Shutdown
264 ===============
265 */
SNDDMA_Shutdown(void)266 void SNDDMA_Shutdown(void)
267 {
268 	Com_Printf("Closing SDL audio device...\n");
269 	SDL_PauseAudio(1);
270 	SDL_CloseAudio();
271 	SDL_QuitSubSystem(SDL_INIT_AUDIO);
272 	free(dma.buffer);
273 	dma.buffer = NULL;
274 	dmapos = dmasize = 0;
275 	snd_inited = qfalse;
276 	Com_Printf("SDL audio device shut down.\n");
277 }
278 
279 /*
280 ===============
281 SNDDMA_Submit
282 
283 Send sound to device if buffer isn't really the dma buffer
284 ===============
285 */
SNDDMA_Submit(void)286 void SNDDMA_Submit(void)
287 {
288 	SDL_UnlockAudio();
289 }
290 
291 /*
292 ===============
293 SNDDMA_BeginPainting
294 ===============
295 */
SNDDMA_BeginPainting(void)296 void SNDDMA_BeginPainting (void)
297 {
298 	SDL_LockAudio();
299 }
300