1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #include <stropts.h>
26 #include <sys/types.h>
27 #include <sys/audioio.h>
28 
29 #include "../client/client.h"
30 #include "../client/snd_loc.h"
31 
32 #define	SND_DEBUG	0
33 
34 #if	SND_DEBUG
35 #define	DPRINTF(...)	printf(__VA_ARGS__)
36 #else
37 #define	DPRINTF(...)	/**/
38 #endif
39 
40 static int audio_fd = -1;
41 static int snd_inited;
42 
43 static cvar_t *sndbits;
44 static cvar_t *sndspeed;
45 static cvar_t *sndchannels;
46 static cvar_t *snddevice;
47 
48 
49 static int tryrates[] = { 11025, 22051, 44100, 8000 };
50 
51 #define	QSND_NUM_CHUNKS	2
52 
53 /*
54 ==================
55 SNDDMA_Init
56 
57 Try to find a sound device to mix for.
58 Returns false if nothing is found.
59 Returns true and fills in the "dma" structure with information for the mixer.
60 ==================
61 */
SNDDMA_Init(void)62 qboolean SNDDMA_Init(void)
63 {
64     int i;
65     int samples;
66     audio_info_t au_info;
67 
68     if (snd_inited)
69 	return 1;
70 
71     if (!snddevice) {
72 	sndbits = Cvar_Get("sndbits", "16", CVAR_ARCHIVE);
73 	sndspeed = Cvar_Get("sndspeed", "0", CVAR_ARCHIVE);
74 	sndchannels = Cvar_Get("sndchannels", "2", CVAR_ARCHIVE);
75 	snddevice = Cvar_Get("snddevice", "/dev/audio", CVAR_ARCHIVE);
76     }
77 
78 // open /dev/audio
79 
80     if (audio_fd < 0) {
81 
82 	audio_fd = open(snddevice->string, O_WRONLY);
83 
84 	if (audio_fd < 0) {
85 	    Com_Printf("Could not open %s: %s\n", snddevice->string, strerror(errno));
86 	    return 0;
87 		}
88 	}
89 
90 // set sample bits & speed
91 
92     if ((int)sndspeed->value > 0) {
93 	AUDIO_INITINFO(&au_info);
94 
95 	au_info.play.precision = (int)sndbits->value;
96 	au_info.play.encoding =
97 	    ( au_info.play.precision == 8
98 	      ? AUDIO_ENCODING_LINEAR8
99 	      : AUDIO_ENCODING_LINEAR );
100 	au_info.play.sample_rate = (int)sndspeed->value;
101 	au_info.play.channels = (int)sndchannels->value;
102 
103 	if (ioctl(audio_fd, AUDIO_SETINFO, &au_info) == -1) {
104 	    Com_Printf("AUDIO_SETINFO failed: %s\n", strerror(errno));
105 		return 0;
106 	}
107     } else {
108 	for (i=0 ; i<sizeof(tryrates)/sizeof(tryrates[0]) ; i++) {
109 	    AUDIO_INITINFO(&au_info);
110 
111 	    au_info.play.precision = (int)sndbits->value;
112 	    au_info.play.encoding =
113 		( au_info.play.precision == 8
114 		  ? AUDIO_ENCODING_LINEAR8
115 		  : AUDIO_ENCODING_LINEAR );
116 	    au_info.play.sample_rate = tryrates[i];
117 	    au_info.play.channels = (int)sndchannels->value;
118 
119 	    if (ioctl(audio_fd, AUDIO_SETINFO, &au_info) == 0)
120 		break;
121 
122 	    Com_Printf("AUDIO_SETINFO failed: %s\n", strerror(errno));
123 	}
124 	if (i >= sizeof(tryrates)/sizeof(tryrates[0]))
125 		return 0;
126 	}
127     dma.samplebits = au_info.play.precision;
128     dma.channels = au_info.play.channels;
129     dma.speed = au_info.play.sample_rate;
130 
131     /*
132      * submit some sound data every ~ 0.1 seconds, and try to buffer 2*0.1
133      * seconds in sound driver
134      */
135     samples = dma.channels * dma.speed / 10;
136     for (i = 0; (1 << i) < samples; i++)
137 	;
138     dma.submission_chunk = 1 << (i-1);
139     DPRINTF("channels %d, speed %d, log2(samples) %d, submission chunk %d\n",
140 	    dma.channels, dma.speed, i-1,
141 	    dma.submission_chunk);
142 
143     dma.samples = QSND_NUM_CHUNKS * dma.submission_chunk;
144     dma.buffer = calloc(dma.samples, dma.samplebits/8);
145     if (dma.buffer == NULL) {
146 	Com_Printf("Could not alloc sound buffer\n");
147 	return 0;
148     }
149 
150     AUDIO_INITINFO(&au_info);
151     au_info.play.eof = 0;
152     au_info.play.samples = 0;
153     ioctl(audio_fd, AUDIO_SETINFO, &au_info);
154 
155     dma.samplepos = 0;
156 
157 	snd_inited = 1;
158 
159 	return 1;
160 }
161 
162 /*
163 ==============
164 SNDDMA_GetDMAPos
165 
166 return the current sample position (in mono samples, not stereo)
167 inside the recirculating dma buffer, so the mixing code will know
168 how many sample are required to fill it up.
169 ===============
170 */
SNDDMA_GetDMAPos(void)171 int SNDDMA_GetDMAPos(void)
172 {
173     int s_pos;
174     audio_info_t au_info;
175 
176     if (!snd_inited)
177 	return 0;
178 
179     if (ioctl(audio_fd, AUDIO_GETINFO, &au_info) == -1) {
180 	Com_Printf("AUDIO_GETINFO failed: %s\n", strerror(errno));
181 	return 0;
182  	}
183 
184     s_pos = au_info.play.samples * dma.channels;
185     return s_pos & (dma.samples - 1);
186 }
187 
188 /*
189 ==============
190 SNDDMA_Shutdown
191 
192 Reset the sound device for exiting
193 ===============
194 */
SNDDMA_Shutdown(void)195 void SNDDMA_Shutdown(void)
196 {
197     if (snd_inited) {
198 	if (audio_fd >= 0) {
199 	    ioctl(audio_fd, I_FLUSH, FLUSHW);
200 	    close(audio_fd);
201 	    audio_fd = -1;
202 		}
203 	snd_inited = 0;
204 	}
205 }
206 
207 /*
208 ==============
209 SNDDMA_Submit
210 
211 Send sound to device if buffer isn't really the dma buffer
212 ===============
213 */
SNDDMA_Submit(void)214 void SNDDMA_Submit(void)
215 {
216     int samplebytes = dma.samplebits/8;
217     audio_info_t au_info;
218     int s_pos;
219     int chunk_idx;
220     static int last_chunk_idx = -1;
221 
222 	if (!snd_inited)
223 	return;
224 
225     if (last_chunk_idx == -1) {
226 	if (write(audio_fd, dma.buffer, dma.samples * samplebytes) != dma.samples * samplebytes)
227 	    Com_Printf("initial write on audio device failed\n");
228 	last_chunk_idx = 0;
229 	dma.samplepos = 0;
230 	return;
231     }
232 
233     if (ioctl(audio_fd, AUDIO_GETINFO, &au_info) == -1) {
234 	Com_Printf("AUDIO_GETINFO failed: %s\n", strerror(errno));
235 	return;
236     }
237 
238     if (au_info.play.error) {
239 
240 	/*
241 	 * underflow? clear the error flag and reset the HW sample counter
242 	 * and send the whole dma_buffer, to get sound output working again
243 	 */
244 
245 	DPRINTF("audio data underflow\n");
246 
247 	AUDIO_INITINFO(&au_info);
248 	au_info.play.error = 0;
249 	au_info.play.samples = 0;
250 	ioctl(audio_fd, AUDIO_SETINFO, &au_info);
251 
252 	if (write(audio_fd, dma.buffer, dma.samples * samplebytes) != dma.samples * samplebytes)
253 	    Com_Printf("refill sound driver after underflow failed\n");
254 	last_chunk_idx = 0;
255 	dma.samplepos = 0;
256 	return;
257     }
258 
259     s_pos = au_info.play.samples * dma.channels;
260     chunk_idx = (s_pos % dma.samples) / dma.submission_chunk;
261 
262     DPRINTF("HW DMA Pos=%u (%u), dma.samplepos=%u, play in=%d, last=%d\n",
263 	    au_info.play.samples, s_pos, dma.samplepos,
264 	    chunk_idx, last_chunk_idx);
265 
266     while (chunk_idx != last_chunk_idx) {
267 
268 	if (write(audio_fd,
269 		  dma.buffer + dma.samplepos * samplebytes,
270 		  dma.submission_chunk * samplebytes) != dma.submission_chunk * samplebytes) {
271 	    Com_Printf("write error on audio device\n");
272 	}
273 
274 	if ((dma.samplepos += dma.submission_chunk) >= dma.samples)
275 	    dma.samplepos = 0;
276 
277 	if (++last_chunk_idx >= QSND_NUM_CHUNKS)
278 	    last_chunk_idx = 0;
279     }
280 }
281 
282 
SNDDMA_BeginPainting(void)283 void SNDDMA_BeginPainting (void)
284 {
285 }
286 
287