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.