#include #include #include /*****************************************************************/ /*** ***/ /*** Play out a file on Linux ***/ /*** ***/ /*** H.F. Silverman 1/4/91 ***/ /*** Modified: H.F. Silverman 1/16/91 for amax parameter ***/ /*** Modified: A. Smith 2/14/91 for 8kHz for klatt synth ***/ /*** Modified: Rob W. W. Hooft (hooft@EMBL-Heidelberg.DE) ***/ /*** adapted for linux soundpackage Version 2.0 ***/ /*** Merged FreeBSD version - 11/11/94 NIS ***/ /*** Perl Port - 27/01/97 NIS ***/ /*** ***/ /*****************************************************************/ #include #include #include #include #include #ifdef HAVE_SYS_SOUNDCARD_H /* linux style */ #include #endif #ifdef HAVE_MACHINE_SOUNDCARD_H /* FreeBSD style */ #include #endif /* Nested dynamic loaded extension magic ... */ #include "../../Data/Audio.m" AudioVtab *AudioVptr; #ifndef AFMT_S16_LE #define AFMT_S16_LE 16 #endif #ifndef AFMT_U8 #define AFMT_U8 8 #endif #ifndef AFMT_MU_LAW #define AFMT_MU_LAW 1 #endif #if defined(HAVE_DEV_DSP) char *dev_file = "/dev/dsp"; static int dev_fmt = AFMT_U8; #else /* DSP */ #if defined(HAVE_DEV_AUDIO) char *dev_file = "/dev/audio"; static int dev_fmt = AFMT_MU_LAW; #else #if defined(HAVE_DEV_DSPW) || !defined(HAVE_DEV_SBDSP) char *dev_file = "/dev/dspW"; static int dev_fmt = AFMT_S16_LE; #else #if defined(HAVE_DEV_SBDSP) char *dev_file = "/dev/sbdsp"; static int dev_fmt = AFMT_U8; #endif /* SBDSP */ #endif /* DSPW */ #endif /* AUDIO */ #endif /* DSP */ #define SAMP_RATE 8000 typedef struct { long samp_rate; int fd; int fmt; float gain; } play_audio_t; static const short endian = 0x1234; static int audio_init(play_audio_t *dev,int wait) { int try; int flags = (wait) ? 0 : O_NDELAY; for (try = 0; try < 5; try++) { dev->fd = open(dev_file, O_WRONLY | O_EXCL | flags); if (dev->fd >= 0 || errno != EBUSY || wait) break; usleep(10000); } if (dev->fd >= 0) { /* Modern /dev/dsp honours O_NONBLOCK and O_NDELAY for write which leads to data being dropped if we try and write and device isn't ready we would either have to retry or we can just turn it off ... */ int fl = fcntl(dev->fd,F_GETFL,0); if (fl != -1) { if (fcntl(dev->fd,F_SETFL,fl & ~(O_NONBLOCK|O_NDELAY)) == 0) { dev->samp_rate = SAMP_RATE; #ifdef SNDCTL_DSP_RESET if (ioctl(dev->fd, SNDCTL_DSP_RESET, 0) != 0) { return 0; } #endif #ifdef SOUND_PCM_READ_RATE if (ioctl(dev->fd, SOUND_PCM_READ_RATE, &dev->samp_rate) != 0) { return 0; } #else #ifdef SNDCTL_DSP_SPEED if (ioctl(dev->fd, SNDCTL_DSP_SPEED, &dev->samp_rate) != 0) { return 0; } #endif #endif /* can read rate */ #ifdef SNDCTL_DSP_GETFMTS if (ioctl(dev->fd,SNDCTL_DSP_GETFMTS,&fl) == 0) { int fmts = fl; if (*((const char *)(&endian)) == 0x34) { if ((fl = fmts & AFMT_S16_LE) && ioctl(dev->fd,SNDCTL_DSP_SETFMT,&fl) == 0) { dev->fmt = fl; return 1; } } else { if ((fl = fmts & AFMT_S16_BE) && ioctl(dev->fd,SNDCTL_DSP_SETFMT,&fl) == 0) { dev->fmt = fl; return 1; } } if ((fl = fmts & AFMT_MU_LAW) && ioctl(dev->fd,SNDCTL_DSP_SETFMT,&fl) == 0) { dev->fmt = fl; return 1; } } #endif warn("Using %s on %d fl=%X\n",dev_file,dev->fd,fl); return 1; } } } perror(dev_file); return 0; } IV audio_rate(play_audio_t *dev, IV rate) {IV old = dev->samp_rate; if (rate) { int want = dev->samp_rate = rate; ioctl(dev->fd, SNDCTL_DSP_SPEED, &dev->samp_rate); if (dev->samp_rate != want) printf("Actual sample rate: %ld\n", dev->samp_rate); } return old; } void audio_flush(play_audio_t *dev) { if (dev->fd >= 0) { int dummy; ioctl(dev->fd, SNDCTL_DSP_SYNC, &dummy); } } void audio_DESTROY(play_audio_t *dev) { audio_flush(dev); /* Close audio system */ if (dev->fd >= 0) { close(dev->fd); dev->fd = -1; } } void audio_play16(play_audio_t *dev,int n, short *data) { if (n > 0) { if (dev->fmt == AFMT_S16_LE || dev->fmt == AFMT_S16_BE) { n *= 2; if (dev->fd >= 0) { if (write(dev->fd, data, n) != n) perror("write"); } } else if (dev->fmt == AFMT_U8) { unsigned char *converted = (unsigned char *) malloc(n); int i; if (converted == NULL) { croak("Could not allocate memory for conversion\n"); } for (i = 0; i < n; i++) converted[i] = (data[i] - 32767) / 256; if (dev->fd >= 0) { if (write(dev->fd, converted, n) != n) perror("write"); } free(converted); } else if (dev->fmt == AFMT_MU_LAW) { unsigned char *plabuf = (unsigned char *) malloc(n); if (plabuf) { int w; unsigned char *p = plabuf; unsigned char *e = p + n; while (p < e) { *p++ = short2ulaw(*data++); } p = plabuf; while ((w = write(dev->fd, p, n)) != n) { if (w == -1 && errno != EINTR) { croak("%d,%s:%d\n", errno, __FILE__, __LINE__); } else { warn("Writing %u, only wrote %u\n", n, w); p += w; n -= w; } } free(plabuf); } else { croak("No memory for ulaw data"); } } else { croak("unknown audio format"); } } } float audio_gain(play_audio_t *dev,float gain) { float prev_gain = dev->gain; if (gain >= 0.0) { if (gain != 1.0) warn("Cannot change audio gain"); /* If you can tell me how, otherwise we could multiply out during conversion to short. ... NI-S */ } return prev_gain; } /* API level Play function - volume may go from the interface - it is un-natural - convert to 'short' should be done at Audio::Play level - likewise rate-matching needs to be higher level */ void audio_play(play_audio_t *dev, Audio *au, float volume) { STRLEN samp = Audio_samples(au); SV *tmp = Audio_shorts(au); if (volume >= 0) audio_gain(dev, volume); if (au->rate != audio_rate(dev,0)) audio_rate(dev, au->rate); /* Or re-sample to dev's rate ??? */ audio_play16(dev, samp, (short *) SvPVX(tmp)); SvREFCNT_dec(tmp); } MODULE = Audio::Play::#OSNAME# PACKAGE=Audio::Play::#OSNAME# PREFIX = audio_ PROTOTYPES: DISABLE play_audio_t * audio_new(class,wait = 1) char * class IV wait CODE: {static play_audio_t buf; if (!audio_init(RETVAL = &buf,wait)) { XSRETURN_NO; } } OUTPUT: RETVAL void audio_DESTROY(dev) play_audio_t * dev void audio_flush(dev) play_audio_t * dev double audio_gain(dev,val = -1.0) play_audio_t * dev double val IV audio_rate(dev,rate = 0) play_audio_t * dev IV rate void audio_play(dev, au, vol = -1.0) play_audio_t * dev Audio * au; float vol BOOT: { /* Nested dynamic loaded extension magic ... */ AudioVptr = (AudioVtab *) SvIV(perl_get_sv("Audio::Data::AudioVtab",5)); }