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