This is linuxplay.c in view mode; [Download] [Up]
/* linuxplay.c - play a sound file on the speaker ** ** Copyright (C) 1995 by Markus Gutschke (gutschk@math.uni-muenster.de) ** ** Parts of this code were inspired by sunplay.c, which is copyright 1989 by ** Jef Poskanzer and 1991,92 by Jamie Zawinski; c.f. sunplay.c for further ** information. ** ** Permission to use, copy, modify, and distribute this software and its ** documentation for any purpose and without fee is hereby granted, provided ** that the above copyright notice appear in all copies and that both that ** copyright notice and this permission notice appear in supporting ** documentation. This software is provided "as is" without express or ** implied warranty. ** ** Currently, only SunAudio, Wave, and RAW file formats are actively ** supported; VOC file format is detected (and rejected :-( ) */ #define HEADERSZ 48 /* has to be at least as big as the biggest header */ #define SNDBUFSZ 2048 /* has to be at least as big as HEADERSZ */ /* XEmacs beta testers say: undef this by default. */ #undef NOVOLUMECTRLFORMULAW /* Changing the volume for uLaw-encoded samples sounds very poor; possibly, this is true only for the PC-Snd driver, so undefine this symbol at your discretion */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <errno.h> #include <fcntl.h> #include <linux/soundcard.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/fcntl.h> #include <sys/file.h> #include <sys/ioctl.h> #include <sys/signal.h> #include <unistd.h> #ifdef LINUXPLAYSTANDALONE #define perror(str) fprintf(stderr,"audio: %s %s\n",str,strerror(errno)); #define warn(str) fprintf(stderr,"audio: %s\n",str); #else #include "lisp.h" #define perror(str) message("audio: %s, %s ",str,strerror(errno)) #define warn(str) message("audio: %s ",GETTEXT(str)) #endif #ifdef __GNUC__ #define UNUSED(x) ((void)(x)) #else #define UNUSED(x) #define __inline__ #endif static __sighandler_t sighup_handler; static __sighandler_t sigint_handler; static union { struct { int align; enum {wvMain,wvSubchunk,wvOutOfBlock,wvSkipChunk, wvSoundChunk,wvFatal,wvFatalNotify} state; size_t left; unsigned char leftover[HEADERSZ]; unsigned long chunklength; } wave; } parsestate; static unsigned char sndbuf[SNDBUFSZ]; static int mix_fd = -1; static int audio_vol = -1; static int audio_fd = -1; static char *audio_dev = ""; typedef enum {fmtIllegal,fmtRaw,fmtVoc,fmtWave,fmtSunAudio} fmtType; static void sighandler(int sig) { if (audio_fd > 0) { ioctl(audio_fd,SNDCTL_DSP_RESET,NULL); close(audio_fd); audio_fd = -1; } if (mix_fd > 0) { if (audio_vol >= 0) { ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&audio_vol); audio_vol = -1; } close(mix_fd); mix_fd = -1; } if (sig == SIGHUP && sighup_handler) sighup_handler(sig); else if (sig == SIGINT && sigint_handler) sigint_handler(sig); else exit(1); } static size_t parseraw(void **data,size_t *sz,void **outbuf) { int rc = *sz; *outbuf = *data; *sz = 0; return(rc); } static size_t parsevoc(void **data,size_t *sz,void **outbuf) { UNUSED(data); UNUSED(sz); UNUSED(outbuf); return(0); } static __inline__ int waverequire(void **data,size_t *sz,size_t rq) { int rc = 1; if (rq > HEADERSZ) { warn("Header size exceeded while parsing WAVE file"); parsestate.wave.state = wvFatal; *sz = 0; return(0); } if ((rq -= parsestate.wave.left) <= 0) return(rc); if (rq > *sz) {rq = *sz; rc = 0;} memcpy(parsestate.wave.leftover+parsestate.wave.left, *data,rq); parsestate.wave.left += rq; ((unsigned char *)*data) += rq; *sz -= rq; return(rc); } static __inline__ void waveremove(size_t rq) { if (parsestate.wave.left <= rq) parsestate.wave.left = 0; else { parsestate.wave.left -= rq; memmove(parsestate.wave.leftover, parsestate.wave.leftover+rq, parsestate.wave.left); } return; } static size_t parsewave(void **data,size_t *sz,void **outbuf) { for (;;) switch (parsestate.wave.state) { case wvMain: if (!waverequire(data,sz,20)) return(0); /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */ parsestate.wave.chunklength = parsestate.wave.leftover[16] + 256*(parsestate.wave.leftover[17] + 256*(parsestate.wave.leftover[18] + 256*parsestate.wave.leftover[19])); waveremove(20); parsestate.wave.state = wvSubchunk; break; case wvSubchunk: if (!waverequire(data,sz,parsestate.wave.chunklength)) return(0); parsestate.wave.align = parsestate.wave.chunklength < 14 ? 1 : parsestate.wave.leftover[2] * parsestate.wave.leftover[12]; if (parsestate.wave.align != 1 && parsestate.wave.align != 2 && parsestate.wave.align != 4) { warn("Illegal datawidth detected while parsing WAVE file"); parsestate.wave.state = wvFatal; } else parsestate.wave.state = wvOutOfBlock; waveremove(parsestate.wave.chunklength); break; case wvOutOfBlock: if (!waverequire(data,sz,8)) return(0); /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */ parsestate.wave.chunklength = parsestate.wave.leftover[4] + 256*(parsestate.wave.leftover[5] + 256*(parsestate.wave.leftover[6] + 256*parsestate.wave.leftover[7])); if (memcmp(parsestate.wave.leftover,"data",4)) parsestate.wave.state = wvSkipChunk; else if (parsestate.wave.chunklength % parsestate.wave.align) { warn("Datawidth does not match chunksize in WAVE file"); parsestate.wave.state = wvFatal; } else parsestate.wave.state = wvSoundChunk; waveremove(8); break; case wvSkipChunk: if (*sz < parsestate.wave.chunklength) { parsestate.wave.chunklength -= *sz; *sz = 0; } else { *sz -= parsestate.wave.chunklength; ((unsigned char *)*data) += parsestate.wave.chunklength; parsestate.wave.state = wvOutOfBlock; } break; case wvSoundChunk: { size_t count,rq; if (parsestate.wave.left) { /* handle leftover bytes from last alignment operation */ count = parsestate.wave.left; rq = HEADERSZ-count; if (rq > parsestate.wave.chunklength) rq = parsestate.wave.chunklength; if (!waverequire(data,sz,rq)) { parsestate.wave.chunklength -= parsestate.wave.left - count; return(0); } parsestate.wave.chunklength -= rq; *outbuf = parsestate.wave.leftover; parsestate.wave.left = 0; return(rq); } if (*sz >= parsestate.wave.chunklength) { count = parsestate.wave.chunklength; rq = 0; } else { count = *sz; count -= rq = count % parsestate.wave.align; } *outbuf = *data; ((unsigned char *)*data) += count; *sz -= count; if ((parsestate.wave.chunklength -= count) <= 0) parsestate.wave.state = wvOutOfBlock; else if (rq) /* align data length to a multiple of datasize; keep additional data in "leftover" buffer --- this is neccessary to ensure proper functioning of the sndcnv... routines */ waverequire(data,sz,rq); return(count); } case wvFatalNotify: warn("Irrecoverable error while parsing WAVE file"); parsestate.wave.state = wvFatal; break; case wvFatal: default: *sz = 0; return(0); } } static size_t sndcnvnop(void **data,size_t *sz,void **outbuf) { int rc = *sz; *outbuf = *data; *sz = 0; return(rc); } static size_t sndcnv2mono(void **data,size_t *sz,void **outbuf) { register unsigned char *src; register unsigned char *dest; int rc,count; count = *sz / 2; if (count > SNDBUFSZ) { *sz -= 2*SNDBUFSZ; count = SNDBUFSZ; } else *sz = 0; rc = count; src = *data; *outbuf = dest = sndbuf; while (count--) *dest++ = (unsigned char)(((int)*((unsigned char *)src)++ + (int)*((unsigned char *)src)++) / 2); *data = src; return(rc); } static size_t sndcnv2byte(void **data,size_t *sz,void **outbuf) { register unsigned char *src; register unsigned char *dest; int rc,count; count = *sz / 2; if (count > SNDBUFSZ) { *sz -= 2*SNDBUFSZ; count = SNDBUFSZ; } else *sz = 0; rc = count; src = *data; *outbuf = dest = sndbuf; while (count--) { *dest++ = (unsigned char)(((signed char *)src)[1] + 0x80); ((char *)src) += 2; } *data = src; return(rc); } static size_t sndcnv2monobyte(void **data,size_t *sz,void **outbuf) { register unsigned char *src; register unsigned char *dest; int rc,count; count = *sz / 4; if (count > SNDBUFSZ) { *sz -= 4*SNDBUFSZ; count = SNDBUFSZ; } else *sz = 0; rc = count; src = *data; *outbuf = dest = sndbuf; while (count--) { *dest++ = (unsigned char)(((int)((signed char *)src)[1] + (int)((signed char *)src)[3]) / 2 + 0x80); ((char *)src) += 4; } *data = src; return(rc); } static fmtType analyze_format(unsigned char *format,int *fmt,int *speed, int *tracks, size_t (**parsesndfile)(void **,size_t *sz, void **)) { /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */ if (!memcmp(format,"Creative Voice File\0x1A\0x1A\x00",22) && (format[22]+256*format[23]+0x1233)&0xFFFF == (format[24]+256*format[25])) { /* VOC */ *fmt = AFMT_U8; *speed = 8000; *tracks = 2; *parsesndfile = parsevoc; return(fmtVoc); } else if (!memcmp(format,"RIFF",4) && !memcmp(format+8,"WAVEfmt ",8)) { /* WAVE */ if (memcmp(format+20,"\001\000\001"/* PCM mono */,4) && memcmp(format+20,"\001\000\002"/* PCM stereo */,4)) return(fmtIllegal); *fmt = format[32] == 1 ? AFMT_U8 : AFMT_S16_LE; *tracks = format[22]; /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */ *speed = format[24]+256*(format[25]+256* (format[26]+256*format[27])); *parsesndfile = parsewave; return(fmtWave); } else if (!memcmp(format,".snd",4)) { /* Sun Audio */ *fmt = AFMT_MU_LAW; *speed = 8000; *tracks = 1; *parsesndfile = parseraw; return(fmtSunAudio); } else { *fmt = AFMT_U8; *speed = 8000; *tracks = 1; *parsesndfile = parseraw; return(fmtRaw); } } static int audio_init(int mix_fd,int audio_fd,int fmt,int speed, int tracks,int *volume, size_t (**sndcnv)(void **,size_t *sz,void **)) { int i; *sndcnv = sndcnvnop; if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) { perror("SNDCTL_DSP_SYNC"); return(0); } if (((i=fmt),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 || fmt != i) { if (fmt == AFMT_S16_LE) { *sndcnv = sndcnv2mono; if (((i=fmt=AFMT_U8),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 || fmt != i) { perror("SNDCTL_DSP_SETFMT"); return(0); } } else { perror("SNDCTL_DSP_SETFMT"); return(0); } } if (((i=tracks-1),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 || tracks-1 != i) { if (tracks == 2) { *sndcnv = *sndcnv == sndcnv2mono ? sndcnv2monobyte : sndcnv2byte; if (((i = 0),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 || i) { perror("SNDCTL_DSP_STEREO"); return(0); } } else { perror("SNDCTL_DSP_STEREO"); return(0); } } if (((i=speed),ioctl(audio_fd,SNDCTL_DSP_SPEED,&i)) < 0 || speed*11 < i*10 || speed*9 > i*10) { char buffer[256]; sprintf(buffer,"SNDCTL_DSP_SPEED (req: %d, rtn: %d)",speed,i); perror(buffer); return(0); } if (mix_fd > 0) { int vol = *volume & 0xFF; if (ioctl(mix_fd,SOUND_MIXER_READ_VOLUME,volume) < 0) *volume = -1; if (vol < 0) vol = 0; else if (vol > 100) vol = 100; #ifdef NOVOLUMECTRLFORMULAW if (fmt == AFMT_MU_LAW) vol = 100; #endif /* vol = (127*vol)/100; */ /* not using full volume sounds a lot better */ vol |= 256*vol; /* Do not signal an error, if volume control is unavailable! */ ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&vol); } return(1); } static void linux_play_data_or_file(int fd,unsigned char *data, int length,int volume) { size_t (*parsesndfile)(void **data,size_t *sz,void **outbuf); size_t (*sndcnv)(void **data,size_t *sz,void **); fmtType ffmt; int fmt,speed,tracks; unsigned char *pptr,*optr,*cptr,*sptr; int wrtn,rrtn,crtn,prtn; if (!data || length < HEADERSZ) if (fd < 0) return; else { length = read(fd,sndbuf,SNDBUFSZ); if (length < HEADERSZ) return; data = sndbuf; length = SNDBUFSZ; } ffmt = analyze_format(data,&fmt,&speed,&tracks,&parsesndfile); if (ffmt != fmtRaw && ffmt != fmtSunAudio && ffmt != fmtWave) { warn("Unsupported file format (neither RAW, nor SunAudio, nor WAVE)"); return; } mix_fd = open("/dev/mixer",(O_WRONLY|O_NDELAY),0); if ((audio_fd = open((audio_dev = ffmt==fmtSunAudio?"/dev/audio":"/dev/dsp"), (O_WRONLY|O_NDELAY),0)) < 0) { perror(audio_dev); if (mix_fd > 0) { close(mix_fd); mix_fd = -1; } return; } sighup_handler = signal(SIGHUP,(__sighandler_t)sighandler); sigint_handler = signal(SIGINT,(__sighandler_t)sighandler); if (!audio_init(mix_fd,audio_fd,fmt,speed,tracks,&volume,&sndcnv)) goto END_OF_PLAY; audio_vol = volume; memset(&parsestate,0,sizeof(parsestate)); rrtn = length; do { for (pptr = data; (prtn = parsesndfile((void **)&pptr,&rrtn, (void **)&optr)) > 0; ) for (cptr = optr; (crtn = sndcnv((void **)&cptr,&prtn, (void **)&sptr)) > 0; ) { for (;;) { if ((wrtn = write(audio_fd,sptr,crtn)) < 0) { perror("write"); goto END_OF_PLAY; } else if (wrtn) break; else if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) { perror("SNDCTL_DSP_SYNC"); goto END_OF_PLAY; } } if (wrtn != crtn) { char buf[255]; sprintf(buf,"play: crtn = %d, wrtn = %d",crtn,wrtn); warn(buf); goto END_OF_PLAY; } } if (fd >= 0) { if ((rrtn = read(fd,sndbuf,SNDBUFSZ)) < 0) { perror("read"); goto END_OF_PLAY; } } else break; } while (rrtn > 0); if (ffmt == fmtWave && parsestate.wave.state != wvOutOfBlock && parsestate.wave.state != wvFatal) warn("Unexpected end of WAVE file"); ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL); END_OF_PLAY: ioctl(audio_fd,SNDCTL_DSP_RESET,NULL); signal(SIGHUP,sighup_handler); signal(SIGINT,sigint_handler); close(audio_fd); audio_fd = -1; if (mix_fd > 0) { if (audio_vol >= 0) { ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&audio_vol); audio_vol = -1; } close(mix_fd); mix_fd = -1; } return; } void play_sound_file (char *sound_file, int volume) { int fd; if ((fd=open(sound_file,O_RDONLY,0)) < 0) { perror(sound_file); return; } linux_play_data_or_file(fd,NULL,0,volume); close(fd); return; } void play_sound_data (unsigned char *data, int length, int volume) { linux_play_data_or_file(-1,data,length,volume); return; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.