1 /* linuxplay.c - play a sound file on the speaker
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>.
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
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
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.
47 /* Synched up with: Not in FSF. */
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
64 #include SOUNDCARD_H_PATH /* Path computed by configure */
70 #include <sys/ioctl.h>
71 #include <sys/signal.h>
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);
79 #include "syssignal.h"
81 #define perror(str) message("audio: %s, %s ",str,strerror(errno))
82 #define warn(str) message("audio: %s ",GETTEXT(str))
85 static SIGTYPE (*sighup_handler) (int);
86 static SIGTYPE (*sigint_handler) (int);
91 static char *audio_dev = "/dev/dsp";
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" */
100 if (audio_vol >= 0) {
101 ioctl(mix_fd,SOUND_MIXER_WRITE_PCM,&audio_vol);
103 if (mix_fd != audio_fd)
107 ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
108 ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
111 if (sig == SIGHUP && sighup_handler) sighup_handler(sig);
112 else if (sig == SIGINT && sigint_handler) sigint_handler(sig);
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
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 **))
129 int i,the_speed,the_stereo,the_fmt;
133 if (ioctl(auddio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
134 perror("SNDCTL_DSP_SYNC");
137 /* Initialize sound hardware with preferred parameters */
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 */
143 if(ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt) < 0) {
144 perror("SNDCTL_DSP_SETFMT");
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 ||
154 perror("SNDCTL_DSP_SETFMT");
156 else if (fmt == AFMT_MU_LAW && the_fmt == AFMT_U8 ) {
157 /* the kernel will convert for us */ }
159 perror("SNDCTL_DSP_SETFMT");
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 ||
166 perror("SNDCTRL_DSP_SETFMT");
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)
177 /* Try to request stereo playback (if needed); if this cannot be supported
178 by the hardware, then install conversion routines for mono playback */
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) {
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");
197 /* Switch to mono mode */
198 if (((i = 0),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 || i) {
199 perror("SNDCTL_DSP_STEREO");
201 /* Now double check that everything is set as expected */
202 if (((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
204 (((i=the_fmt),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
206 ((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
208 (ioctl(audio_fd,SOUND_PCM_READ_CHANNELS,&i) >= 0 &&
210 /* There was no way that we could set the soundcard to a meaningful
212 perror("SNDCTL_DSP_SETFMT and SNDCTL_DSP_STEREO");
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 ||
219 perror("SNDCTL_DSP_STEREO");
221 if (((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
223 perror("SNDCTL_DSP_SETFMT");
226 /* Fail if deviations from desired sampling frequency are too big */
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) {
233 sprintf(buffer,"SNDCTL_DSP_SPEED (req: %d, rtn: %d)",speed,the_speed);
237 /* Use the mixer device for setting the playback volume */
239 int vol = *volume & 0xFF;
240 if (ioctl(mixx_fd,SOUND_MIXER_READ_PCM,volume) < 0)
242 if (vol < 0) vol = 0; else if (vol > 100) vol = 100;
243 #ifdef NOVOLUMECTRLFORMULAW
244 if (fmt == AFMT_MU_LAW)
248 /* Do not signal an error, if volume control is unavailable! */
249 ioctl(mixx_fd,SOUND_MIXER_WRITE_PCM,&vol); }
251 #if defined(LINUXPLAYSTANDALONE) && 1
252 /* Debugging output is displayed only when compiled as stand-alone version */
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" :
271 the_stereo == 2 ? "stereo" : "mono",
273 the_volume / 256, the_volume % 256); }
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)
284 size_t (*parsesndfile)(void **dayta,size_t *sz,void **outbuf);
285 size_t (*sndcnv)(void **dayta,size_t *sz,void **);
287 int fmt,speed,tracks;
288 unsigned char *pptr,*optr,*cptr,*sptr;
289 int wrtn,rrtn,crtn,prtn;
290 unsigned char sndbuf[SNDBUFSZ];
292 /* We need to read at least the header information before we can start
294 if (!data || length < HEADERSZ) {
297 length = read(fd,sndbuf,SNDBUFSZ);
298 if (length < HEADERSZ)
304 ffmt = analyze_format(data,&fmt,&speed,&tracks,&parsesndfile);
306 if (ffmt != fmtRaw && ffmt != fmtSunAudio && ffmt != fmtWave) {
307 warn("Unsupported file format (neither RAW, nor Sun/DECAudio, nor WAVE)");
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) {
314 if (mix_fd > 0 && mix_fd != audio_fd) { close(mix_fd); mix_fd = -1; }
317 /* The VoxWare-SDK discourages direct manipulation of the mixer device as
318 this could lead to problems, when multiple sound cards are installed */
321 sighup_handler = signal(SIGHUP, sighandler);
322 sigint_handler = signal(SIGINT, sighandler);
324 if (!audio_init(mix_fd,audio_fd,fmt,speed,tracks,&volume,&sndcnv))
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 */
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; ) {
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; } }
347 sprintf(buf,"play: crtn = %d, wrtn = %d",crtn,wrtn);
349 goto END_OF_PLAY; } }
351 if ((rrtn = read(fd,sndbuf,SNDBUFSZ)) < 0) {
352 perror("read"); goto END_OF_PLAY; } }
358 parse_wave_complete();
362 /* Now cleanup all used resources */
364 ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
365 ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
367 signal(SIGHUP,sighup_handler);
368 signal(SIGINT,sigint_handler);
371 if (audio_vol >= 0) {
372 ioctl(mix_fd,SOUND_MIXER_WRITE_PCM,&audio_vol);
374 if (mix_fd != audio_fd)
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)
391 if ((fd=open(sound_file,O_RDONLY,0)) < 0) {
394 linux_play_data_or_file(fd,NULL,0,volume);
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)
404 linux_play_data_or_file(-1,data,length,volume);