1 /* $Id: afaudio.c,v 5.1 2001/05/12 18:06:49 bertg Exp $
2 *
3 * XPilot, a multiplayer gravity war game. Copyright (C) 1991-2001 by
4 *
5 * Bj�rn Stabell <bjoern@xpilot.org>
6 * Ken Ronny Schouten <ken@xpilot.org>
7 * Bert Gijsbers <bert@xpilot.org>
8 * Dick Balaska <dick@xpilot.org>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24 /*
25 * History
26 * 1993: AFPlay audio driver by Tom De Pauw <tom@finning.ca>.
27 * 1994: Hacked on for AF3 & cached audio by Lance Berc <berc@src.dec.com>.
28 * 22 Jan 1995: Skip sound headers if present. Tom De Pauw <tom@finning.ca>.
29 */
30 /*
31 * DEC CRL's AudioFile is a public domain network-transparent system
32 * for distributed audio applications and supports Digital RISC
33 * systems running Ultrix, Digital Alpha AXP systems running OSF/1,
34 * Sun Microsystems SPARCstations running SunOS, and SGI Indigos.
35 * It is available via anonymous FTP from crl.dec.com and gatekeeper.dec.com.
36 *
37 * I have finally ported support for AF to xpilot. It works on my DECstation
38 * 5000/25 (a small X window MIPS 3000 based machine) with the following
39 * minor inconveniences (AF experts, please read on):
40 *
41 * 1) Sometimes a sound is lost (never played). Here is my opinion on the
42 * cause. AFPlaySamples needs the audio device time at which the sound
43 * is to be played. Since we want to play right away, we need to query
44 * the AF server for the current time. We then present the sound to be
45 * played with the current time. By that time, there is a chance (xpilot
46 * is quite cpu intensive on DECstation 5000/25) that the current time is
47 * in the past and the sound is thus never played. This should be a lesser
48 * concern on larger cpus. [ I added a 50msec delay to account for scheduling
49 * jitter - lance ]
50 *
51 * 2) Volume. The obvious AFSetOutputGain() control isn't used because
52 * (a) not all devices have hardware gain control, and (b) it doesn't
53 * allow setting the gain on a per-spurt basis. So we now use the software
54 * mixer by setting the ACPlayGain attribute for each spurt. The gain
55 * heuristic is good for the speaker on my J-Video, but your milage may
56 * vary.
57 *
58 * 3) This code skips a possible sound header in Sun or Inverted Sun
59 * format. The rest of the sound file describes samples in 8kHz u-law format.
60 * The sound files suggested for use with xpilot contain the headers.
61 * AF was never intended to read these headers, they would produce
62 * an audible click at the start of each sound. The headers are
63 * assumed of fixed length. This seems the case in the sounds I looked at.
64 * If this causes problem, the header length field will have to be read.
65 *
66 * Resemblance of this code to other programs is not coincidental.
67 *
68 */
69
70 #include <stdlib.h>
71 #include <string.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <AF/AFlib.h>
75
76 #ifndef _WINDOWS
77 # include <sys/file.h>
78 #endif
79
80 #include "version.h"
81 #include "audio.h"
82
83 char audio_version[] = VERSION;
84
85 /* Keep a cache of recently played audio. This really helps when the
86 * client has the sound files mounted from a heavily loaded NFS server.
87 */
88 #define CACHE_SIZE (512 * 1024)
89 #define CACHE_ENTRIES 64
90 #define SOUND_DELAY 50 /* delay playing a sound for 50msec for slow machines */
91
92 struct SoundCache {
93 char *fn;
94 unsigned char *sound;
95 struct timeval when;
96 int length;
97 };
98
99 typedef struct
100 {
101 unsigned magic; /* Magic number */
102 unsigned sample_rate; /* Samples per second */
103 unsigned samples_per_unit; /* Samples per unit */
104 unsigned bytes_per_unit; /* Bytes per sample unit */
105 unsigned channels; /* # interleaved channels */
106 unsigned encoding; /* Date encoding format */
107 unsigned info1; /* "info" field of unspecified nature */
108 unsigned info2; /* (totalling hdr_size - 24) */
109 } Sun_Audio_Hdr;
110 #define SUN_MAGIC ((unsigned long) 0x2e736e64) /* Really '.snd' */
111 #define SUN_INV_MAGIC ((unsigned long) 0x646e732e)
112 #define SUN_AUDIO_ENCODING_ULAW (1)
113
114 static struct SoundCache soundCache[CACHE_ENTRIES];
115
116 static int soundCacheEntries = 0;
117 static int soundCacheBytes = 0;
118
119 static AC ac;
120 static AFAudioConn *aud;
121 static struct timeval now;
122
FindDefaultDevice(aud)123 static int FindDefaultDevice(aud)
124 AFAudioConn *aud;
125 {
126 AFDeviceDescriptor *aDev;
127 int i;
128 char *s;
129
130 s = (char *) getenv("AF_DEVICE");
131 if (s != NULL) return(atoi(s));
132
133 /* Find the first 8kHz, mono device, non-phone device. */
134 for (i=0; i < ANumberOfAudioDevices(aud); i++) {
135 aDev = AAudioDeviceDescriptor(aud, i);
136 if ((aDev->inputsFromPhone == 0) &&
137 (aDev->outputsToPhone == 0) &&
138 (aDev->playSampleFreq == 8000) &&
139 (aDev->playNchannels == 1))
140 return i;
141 }
142 return -1;
143 }
144
145
146
audioDeviceInit(char * display)147 int audioDeviceInit(char *display)
148 {
149 AFSetACAttributes attributes;
150 int device;
151
152 attributes.preempt = Mix;
153 attributes.start_timeout = 0;
154 attributes.end_silence = 0;
155 attributes.play_gain = 0;
156 attributes.rec_gain = 0;
157 attributes.type = MU255;
158
159 if ((aud = AFOpenAudioConn("")) == NULL)
160 {
161 error ("Cannot open a connection to audio server.");
162 return 1;
163 }
164
165 device = FindDefaultDevice(aud);
166 if (device == -1) {
167 error ("Cannot find an 8kHz, mono, non-telephone device");
168 AFCloseAudioConn(aud);
169 return 1;
170 }
171
172 ac = AFCreateAC(aud, device, ACPlayGain | ACEncodingType, &attributes);
173
174 /* Success in opening audio device */
175 return 0;
176 }
177
tossOldestCacheEntry(void)178 tossOldestCacheEntry(void)
179 {
180 struct timeval t;
181 struct SoundCache *ce, *oldest;
182
183 oldest = soundCache;
184 if (oldest->when.tv_sec == 0) oldest->when = now;
185 for (ce = soundCache; ce < &soundCache[CACHE_ENTRIES]; ce++) {
186 if (ce->fn && timercmp(&ce->when, &oldest->when, <)) oldest = ce;
187 }
188
189 free(oldest->fn);
190 free(oldest->sound);
191 soundCacheBytes -= oldest->length;
192 soundCacheEntries--;
193 bzero((char *) oldest, sizeof(struct SoundCache));
194 }
195
196
newCacheEntry(char * fn)197 struct SoundCache *newCacheEntry(char *fn)
198 {
199 int fd;
200 struct stat sbuf;
201 struct SoundCache *ce;
202 Sun_Audio_Hdr header;
203
204 /* Open the sound file for reading */
205 if ((fd = open(fn, O_RDONLY, 0)) < 0) {
206 error("Unable to open sound file %s.", fn);
207 return NULL;
208 }
209 if (fstat(fd, &sbuf) == -1) {
210 error("Unable to stat sound file %s.", fn);
211 close(fd);
212 return NULL;
213 }
214
215 /* If we have a Sun audio file, strip the header and adjust size. */
216 if (read(fd, (char *)&header, sizeof(Sun_Audio_Hdr))
217 < sizeof(Sun_Audio_Hdr)) {
218 error("Warning: assuming no header in: %s.", fn);
219 close(fd);
220 fd = open(fn, O_RDONLY, 0);
221 }
222 else if (header.magic != SUN_MAGIC && header.magic != SUN_INV_MAGIC
223 /*|| header.encoding != SUN_AUDIO_ENCODING_ULAW*/ ) {
224 error("Warning: found %x, expected sound header %x or %x in %s.",
225 header.magic, SUN_MAGIC, SUN_INV_MAGIC, fn);
226 close(fd);
227 fd = open(fn, O_RDONLY, 0);
228 }
229 else /* We have in fact found a header */
230 sbuf.st_size -= sizeof(Sun_Audio_Hdr);
231
232 /* Truncate huge sound files to the cache size */
233 if (sbuf.st_size > CACHE_SIZE) sbuf.st_size = CACHE_SIZE;
234
235 /* If the cache is full, throw out the oldest entry. */
236 while ((soundCacheEntries == CACHE_ENTRIES) ||
237 (soundCacheBytes + sbuf.st_size > CACHE_SIZE))
238 tossOldestCacheEntry();
239
240 /* Find an empty cache entry */
241 for (ce = soundCache; ce < &soundCache[CACHE_ENTRIES]; ce++)
242 if (!ce->fn) break;
243
244 ce->fn = malloc(strlen(fn + 1));
245 strcpy(ce->fn, fn);
246 ce->sound = (unsigned char *) malloc(sbuf.st_size);
247 if (read(fd, ce->sound, sbuf.st_size) != sbuf.st_size) {
248 error("Unable to read sound file %s.", fn);
249 free(ce->sound);
250 free(ce->fn);
251 ce->fn = NULL;
252 ce->sound = NULL;
253 close(fd);
254 return(NULL);
255 }
256
257 close(fd);
258 ce->length = sbuf.st_size;
259 soundCacheBytes += sbuf.st_size;
260 soundCacheEntries++;
261
262 return(ce);
263 }
264
265
findCacheEntry(char * fn)266 struct SoundCache *findCacheEntry(char *fn)
267 {
268 struct SoundCache *ce;
269
270 for (ce = soundCache ; ce < &soundCache[CACHE_ENTRIES]; ce++)
271 if (ce->fn && !strcmp(fn, ce->fn))
272 return ce;
273
274 ce = newCacheEntry(fn);
275 return(ce);
276 }
277
audioDevicePlay(char * filename,int type,int volume,void ** private)278 void audioDevicePlay(char *filename, int type, int volume, void **private)
279 {
280 int gain;
281 AFSetACAttributes acAttributes;
282 struct SoundCache *ce;
283 ATime t;
284
285 if ((ce = findCacheEntry(filename)) == NULL) return;
286
287 gettimeofday(&now, NULL);
288 ce->when = now;
289 /* this is a hacky, kludgey, way of doing things. yuck. but it works */
290 /* Convert from 10 - 100 to -25 - -5 */
291 gain = (((volume - 10) * 20) / 90) - 25;
292 acAttributes.play_gain = gain;
293 AFChangeACAttributes(ac, ACPlayGain, &acAttributes);
294
295 t = AFGetTime(ac) + (8 * SOUND_DELAY); /* 8 samples/msec */
296 t = AFPlaySamples(ac, t, ce->length, ce->sound);
297 }
298
audioDeviceEvents(void)299 void audioDeviceEvents(void)
300 {
301 }
302