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