XEmacs 21.2.29 "Hestia".
[chise/xemacs-chise.git.1] / src / linuxplay.c
1 /* linuxplay.c - play a sound file on the speaker
2  **
3  ** Copyright (C) 1995,96 by Markus Gutschke (gutschk@math.uni-muenster.de)
4  ** This is version 1.3 of linuxplay.c, with platform-independent functions
5  ** moved to a different file by Robert Bihlmeyer <robbe@orcus.priv.at>.
6  **
7  ** Parts of this code were inspired by sunplay.c, which is copyright 1989 by
8  ** Jef Poskanzer and 1991,92 by Jamie Zawinski; c.f. sunplay.c for further
9  ** information.
10  **
11  ** Permission to use, copy, modify, and distribute this software and its
12  ** documentation for any purpose and without fee is hereby granted, provided
13  ** that the above copyright notice appear in all copies and that both that
14  ** copyright notice and this permission notice appear in supporting
15  ** documentation.  This software is provided "as is" without express or
16  ** implied warranty.
17  **
18  ** Changelog:
19  **  1.0  --  first release; supports SunAudio, Wave and RAW file formats
20  **           detects (and rejects) VOC file format
21  **           tested with PC-Speaker driver only
22  **  1.1  --  fixed bug with playback of stereo Wave files
23  **           fixed VOC file detection
24  **           fixed mono/8bit conversion
25  **           cleaned up mixer programming (c.f. VoxWare-SDK)
26  **           tested with PC-Speaker driver and with PAS16 soundcard
27  **  1.2  --  first (incompatible) attempt at fixing reliable signal handling
28  **  1.3  --  changed signal handling to use reliable signals; this is done
29  **           by including "syssignal.h"; it fixes nasty program crashes
30  **           when using native sound in TTY mode.
31  **           added support for DEC audio file format (this is basically the
32  **           same as Sun audio, but uses little endian format, instead).
33  **           strip the header from Sun audio and DEC audio files in order to
34  **           prevent noise at beginning of samples (thanks to Thomas Pundt
35  **           <pundtt@math.uni-muenster.de> for pointing out this bug and
36  **           providing information on the file format).
37  **           added a few more conversion routines.
38  **           made the code even more tolerant to the limits imposed by some
39  **           soundcards and try to accept soundfiles even if they are not
40  **           fully conformant to the standard.
41  **  1.4  --  increased header size to 256; I hope there is no sample software
42  **           that requires this much.
43  **           added code for converting from signed to unsigned format as
44  **           some soundcards cannot handle signed 8bit data.
45  */
46
47 /* Synched up with: Not in FSF. */
48
49 /* XEmacs beta testers say:  undef this by default. */
50 #undef NOVOLUMECTRLFORMULAW /* Changing the volume for uLaw-encoded
51                                samples sounds very poor; possibly,
52                                this is true only for the PC-Snd
53                                driver, so undefine this symbol at your
54                                discretion */
55
56 #ifdef HAVE_CONFIG_H
57 #include <config.h>
58 #endif
59
60 #include "miscplay.h"
61
62 #include <errno.h>
63 #include <fcntl.h>
64 #include SOUNDCARD_H_PATH /* Path computed by configure */
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <fcntl.h>
69 #include <sys/file.h>
70 #include <sys/ioctl.h>
71 #include <sys/signal.h>
72 #include <unistd.h>
73
74 #ifdef LINUXPLAYSTANDALONE
75 #define perror(str) fprintf(stderr,"audio: %s %s\n",str,strerror(errno));
76 #define warn(str)   fprintf(stderr,"audio: %s\n",str);
77 #else
78 #include "lisp.h"
79 #include "syssignal.h"
80 #include "sysfile.h"
81 #define perror(str) message("audio: %s, %s ",str,strerror(errno))
82 #define warn(str)   message("audio: %s ",GETTEXT(str))
83 #endif
84
85 static  SIGTYPE (*sighup_handler) (int);
86 static  SIGTYPE (*sigint_handler) (int);
87
88 static int           mix_fd;
89 static int           audio_vol;
90 static int           audio_fd;
91 static char          *audio_dev = "/dev/dsp";
92
93 /* Intercept SIGINT and SIGHUP in order to close the audio and mixer
94    devices before terminating sound output; this requires reliable
95    signals as provided by "syssignal.h" */
96 static SIGTYPE
97 sighandler (int sig)
98 {
99   if (mix_fd > 0) {
100     if (audio_vol >= 0) {
101       ioctl(mix_fd,SOUND_MIXER_WRITE_PCM,&audio_vol);
102       audio_vol = -1; }
103     if (mix_fd != audio_fd)
104       close(mix_fd);
105     mix_fd = -1; }
106   if (audio_fd > 0) {
107     ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
108     ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
109     close(audio_fd);
110     audio_fd = -1; }
111   if (sig == SIGHUP && sighup_handler)      sighup_handler(sig);
112   else if (sig == SIGINT && sigint_handler) sigint_handler(sig);
113   else exit(1);
114 }
115
116 /* Initialize the soundcard and mixer device with the parameters that we
117    found in the header of the sound file. If the soundcard is not capable of
118    natively supporting the required parameters, then try to set up conversion
119    routines.
120    The difficulty with setting up the sound card is that the parameters are
121    not fully orthogonal; changing one of them might affect some of the
122    others, too. Thus we do quite a lot of double checking; actually most of
123    this is not needed right now, but it will come in handy, if the kernel's
124    sounddriver ever changes or if third-party sounddrivers are used. */
125 static int audio_init(int mixx_fd, int auddio_fd, int fmt, int speed,
126                       int tracks, int *volume,
127                       size_t (**sndcnv) (void **, size_t *sz, void **))
128 {
129   int i,the_speed,the_stereo,the_fmt;
130
131   *sndcnv = sndcnvnop;
132
133   if (ioctl(auddio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
134     perror("SNDCTL_DSP_SYNC");
135     return(0); }
136
137   /* Initialize sound hardware with preferred parameters */
138
139   /* If the sound hardware cannot support 16 bit format or requires a
140      different byte sex then try to drop to 8 bit format */
141
142   the_fmt = fmt;
143   if(ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt) < 0) {
144         perror("SNDCTL_DSP_SETFMT");
145         return(0);
146   }
147
148   if (fmt != the_fmt) {
149     if (fmt == AFMT_S16_LE || fmt == AFMT_S16_BE) {
150       *sndcnv = fmt == AFMT_S16_BE ? sndcnv2byteBE : sndcnv2byteLE;
151       if (((i=fmt=AFMT_U8),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
152           fmt != i || ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt) < 0 ||
153           fmt != the_fmt) {
154         perror("SNDCTL_DSP_SETFMT");
155         return(0); } }
156     else if (fmt == AFMT_MU_LAW && the_fmt == AFMT_U8 ) {
157       /* the kernel will convert for us */ }
158     else {
159       perror("SNDCTL_DSP_SETFMT");
160       return(0); } }
161   else if (fmt == AFMT_S8) {
162     *sndcnv = sndcnv2unsigned;
163     if (((i=fmt=AFMT_U8),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
164         fmt != i || ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt) < 0 ||
165         fmt != the_fmt) {
166       perror("SNDCTRL_DSP_SETFMT");
167       return(0); } }
168
169   /* The PCSP driver does not support reading of the sampling rate via the
170      SOUND_PCM_READ_RATE ioctl; determine "the_speed" here */
171   the_speed = speed; ioctl(audio_fd,SNDCTL_DSP_SPEED,&the_speed);
172   /* The PCSP driver does not support reading of the mono/stereo flag, thus
173      we assume, that failure to change this mode means we are in mono mode  */
174   if (((i = (the_stereo = tracks)-1),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0)
175     the_stereo = 1;
176
177   /* Try to request stereo playback (if needed); if this cannot be supported
178      by the hardware, then install conversion routines for mono playback */
179
180   /* This ioctl will fail if we use the PCSP driver; thus the value of
181      "the_stereo" is still unchanged */
182   ioctl(audio_fd,SOUND_PCM_READ_CHANNELS,&the_stereo);
183   if (tracks != the_stereo) {
184     if (tracks == 2) {
185       tracks = 1;
186       *sndcnv = *sndcnv == sndcnv2byteLE   ? sndcnv2monobyteLE :
187               *sndcnv == sndcnv2byteBE   ? sndcnv2monobyteBE :
188               *sndcnv == sndcnv2unsigned ? sndcnv2monounsigned :
189         the_fmt == AFMT_S16_LE ? sndcnv16_2monoLE :
190         the_fmt == AFMT_S16_BE ? sndcnv16_2monoBE :
191         the_fmt == AFMT_S8     ? sndcnv8S_2mono :
192         the_fmt == AFMT_U8     ? sndcnv8U_2mono :
193         the_fmt == AFMT_MU_LAW ? sndcnvULaw_2mono : NULL;
194       if (*sndcnv == NULL) { /* this should not happen */
195         perror("SNDCTL_DSP_STEREO");
196         return(0); }
197       /* Switch to mono mode */
198       if (((i = 0),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 || i) {
199         perror("SNDCTL_DSP_STEREO");
200         return(0); }
201       /* Now double check that everything is set as expected */
202       if (((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
203           (i != the_fmt &&
204            (((i=the_fmt),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
205             i != the_fmt ||
206             ((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
207             i != the_fmt)) ||
208           (ioctl(audio_fd,SOUND_PCM_READ_CHANNELS,&i) >= 0 &&
209            i != 1)) {
210         /* There was no way that we could set the soundcard to a meaningful
211            mode */
212         perror("SNDCTL_DSP_SETFMT and SNDCTL_DSP_STEREO");
213         return(0); } }
214     else {
215       /* Somebody set the soundcard to stereo even though we requested
216          mono; this should not happen... */
217       if (((i = the_stereo = tracks),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i))<0 ||
218           i != the_stereo-1) {
219         perror("SNDCTL_DSP_STEREO");
220         return(0); }
221       if (((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
222           i != the_fmt) {
223         perror("SNDCTL_DSP_SETFMT");
224         return(0); } } }
225
226   /* Fail if deviations from desired sampling frequency are too big */
227
228   /* This ioctl will fail if we use the PCSP driver; thus the value of
229      "the_speed" is still unchanged */
230   ioctl(audio_fd,SOUND_PCM_READ_RATE,&the_speed);
231   if (speed*14 < the_speed*10 || speed*6 > the_speed*10) {
232     char buffer[256];
233     sprintf(buffer,"SNDCTL_DSP_SPEED (req: %d, rtn: %d)",speed,the_speed);
234     perror(buffer);
235     return(0); }
236
237   /* Use the mixer device for setting the playback volume */
238   if (mixx_fd > 0) {
239     int vol = *volume & 0xFF;
240     if (ioctl(mixx_fd,SOUND_MIXER_READ_PCM,volume) < 0)
241       *volume = -1;
242     if (vol < 0) vol = 0; else if (vol > 100) vol = 100;
243 #ifdef NOVOLUMECTRLFORMULAW
244     if (fmt == AFMT_MU_LAW)
245       vol = 100;
246 #endif
247     vol |= 256*vol;
248     /* Do not signal an error, if volume control is unavailable! */
249     ioctl(mixx_fd,SOUND_MIXER_WRITE_PCM,&vol); }
250
251 #if defined(LINUXPLAYSTANDALONE) && 1
252   /* Debugging output is displayed only when compiled as stand-alone version */
253   {int the_volume;
254   the_fmt = AFMT_QUERY;
255   ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt);
256   ioctl(auddio_fd,SOUND_PCM_READ_CHANNELS,&the_stereo);
257   ioctl(auddio_fd,SOUND_PCM_READ_RATE,&the_speed);
258   ioctl(mixx_fd,SOUND_MIXER_READ_PCM,&the_volume);
259   fprintf(stderr,"%s, %s, %dHz, L:%d/R:%d\n",
260           the_fmt == AFMT_MU_LAW ? "AFMT_MU_LAW" :
261           the_fmt == AFMT_A_LAW ? "AFMT_A_LAW" :
262           the_fmt == AFMT_IMA_ADPCM ? "AFMT_IMA_ADPCM" :
263           the_fmt == AFMT_U8 ? "AFMT_U8" :
264           the_fmt == AFMT_S16_LE ? "AFMT_S16_LE" :
265           the_fmt == AFMT_S16_BE ? "AFMT_S16_BE" :
266           the_fmt == AFMT_S8 ? "AFMT_S8" :
267           the_fmt == AFMT_U16_LE ? "AFMT_U16_LE" :
268           the_fmt == AFMT_U16_BE ? "AFMT_U16_BE" :
269           the_fmt == AFMT_MPEG ? "AFMT_MPEG" :
270           "AFMT_???",
271           the_stereo == 2 ? "stereo" : "mono",
272           the_speed,
273           the_volume / 256, the_volume % 256); }
274 #endif
275
276   return(1);
277 }
278
279 /* XEmacs requires code both for playback of pre-loaded data and for playback
280    from a soundfile; we use one function for both cases */
281 static void linux_play_data_or_file(int fd,unsigned char *data,
282                                     int length,int volume)
283 {
284   size_t         (*parsesndfile)(void **dayta,size_t *sz,void **outbuf);
285   size_t         (*sndcnv)(void **dayta,size_t *sz,void **);
286   fmtType        ffmt;
287   int            fmt,speed,tracks;
288   unsigned char *pptr,*optr,*cptr,*sptr;
289   int            wrtn,rrtn,crtn,prtn;
290   unsigned char         sndbuf[SNDBUFSZ];
291
292   /* We need to read at least the header information before we can start
293      doing anything */
294   if (!data || length < HEADERSZ) {
295     if (fd < 0) return;
296     else {
297       length = read(fd,sndbuf,SNDBUFSZ);
298       if (length < HEADERSZ)
299         return;
300       data   = sndbuf;
301       length = SNDBUFSZ; }
302   }
303
304   ffmt = analyze_format(data,&fmt,&speed,&tracks,&parsesndfile);
305
306   if (ffmt != fmtRaw && ffmt != fmtSunAudio && ffmt != fmtWave) {
307     warn("Unsupported file format (neither RAW, nor Sun/DECAudio, nor WAVE)");
308       return; }
309
310   /* The VoxWare-SDK discourages opening /dev/audio; opening /dev/dsp and
311      properly initializing it via ioctl() is preferred */
312   if ((audio_fd=open(audio_dev, O_WRONLY | O_NONBLOCK, 0)) < 0) {
313     perror(audio_dev);
314     if (mix_fd > 0 && mix_fd != audio_fd) { close(mix_fd); mix_fd = -1; }
315     return; }
316
317   /* The VoxWare-SDK discourages direct manipulation of the mixer device as
318      this could lead to problems, when multiple sound cards are installed */
319   mix_fd = audio_fd;
320
321   sighup_handler = signal(SIGHUP, sighandler);
322   sigint_handler = signal(SIGINT, sighandler);
323
324   if (!audio_init(mix_fd,audio_fd,fmt,speed,tracks,&volume,&sndcnv))
325     goto END_OF_PLAY;
326   audio_vol = volume;
327
328   reset_parsestate();
329
330   /* Mainloop: read a block of data, parse its contents, perform all
331                the necessary conversions and output it to the sound
332                device; repeat until all data has been processed */
333   rrtn = length;
334   do {
335     for (pptr = data; (prtn = parsesndfile((void **)&pptr,(size_t *)&rrtn,
336                                            (void **)&optr)) > 0; )
337       for (cptr = optr; (crtn = sndcnv((void **)&cptr,(size_t *) &prtn,
338                                        (void **)&sptr)) > 0; ) {
339         for (;;) {
340           if ((wrtn = write(audio_fd,sptr,crtn)) < 0) {
341             perror("write"); goto END_OF_PLAY; }
342           else if (wrtn) break;
343           else if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
344             perror("SNDCTL_DSP_SYNC"); goto END_OF_PLAY; } }
345         if (wrtn != crtn) {
346           char buf[255];
347           sprintf(buf,"play: crtn = %d, wrtn = %d",crtn,wrtn);
348           warn(buf);
349           goto END_OF_PLAY; } }
350     if (fd >= 0) {
351       if ((rrtn = read(fd,sndbuf,SNDBUFSZ)) < 0) {
352         perror("read"); goto END_OF_PLAY; } }
353     else
354       break;
355   } while (rrtn > 0);
356
357   if (ffmt == fmtWave)
358     parse_wave_complete();
359  
360
361 END_OF_PLAY:
362   /* Now cleanup all used resources */
363
364   ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
365   ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
366
367   signal(SIGHUP,sighup_handler);
368   signal(SIGINT,sigint_handler);
369
370   if (mix_fd > 0) {
371     if (audio_vol >= 0) {
372       ioctl(mix_fd,SOUND_MIXER_WRITE_PCM,&audio_vol);
373       audio_vol = -1; }
374     if (mix_fd != audio_fd)
375       close(mix_fd);
376     mix_fd = -1; }
377
378   close(audio_fd);
379   audio_fd = -1;
380
381   return;
382 }
383
384 /* Call "linux_play_data_or_file" with the appropriate parameters for
385    playing a soundfile */
386 void play_sound_file (char *sound_file, int volume);
387 void play_sound_file (char *sound_file, int volume)
388 {
389   int fd;
390
391   if ((fd=open(sound_file,O_RDONLY,0)) < 0) {
392     perror(sound_file);
393     return; }
394   linux_play_data_or_file(fd,NULL,0,volume);
395   close(fd);
396   return;
397 }
398
399 /* Call "linux_play_data_or_file" with the appropriate parameters for
400    playing pre-loaded data */
401 void play_sound_data (unsigned char *data, int length, int volume);
402 void play_sound_data (unsigned char *data, int length, int volume)
403 {
404   linux_play_data_or_file(-1,data,length,volume);
405   return;
406 }