#include "dat.h" #include "fns.h" #include "error.h" #define __EXTENSIONS__ #include #include #include #include #include #include "devaudio.h" #include #define Audio_Mic_Val AUDIO_MICROPHONE #define Audio_Linein_Val AUDIO_LINE_IN #define Audio_Speaker_Val AUDIO_SPEAKER #define Audio_Headphone_Val AUDIO_HEADPHONE #define Audio_Lineout_Val AUDIO_LINE_OUT #define Audio_Pcm_Val AUDIO_ENCODING_LINEAR #define Audio_Ulaw_Val AUDIO_ENCODING_ULAW #define Audio_Alaw_Val AUDIO_ENCODING_ALAW #include "devaudio-tbls.c" #define min(a,b) ((a) < (b) ? (a) : (b)) static int debug = 0; #define AUDIO_FILE_STRING "/dev/audio" enum { A_Pause, A_UnPause }; enum { A_In, A_Out }; static QLock inlock; static QLock outlock; static int audio_file_in = -1; /* file in */ static int audio_file_out = -1; /* file out */ static int audio_ctl_in = -1; /* ctl in */ static int audio_ctl_out = -1; /* ctl out */ static int audio_swap_flag = 0; /* endian swap */ static int audio_in_pause = A_UnPause; static Audio_t av; static int audio_enforce(Audio_t*); static int audio_open_in(void); static int audio_open_out(void); static int audio_pause_in(int, int); static int audio_flush(int, int); static int audio_pause_out(int); static int audio_set_blocking(int); static int audio_set_info(int, Audio_d*, int); static void audio_swap_endian(char*, int); void audio_file_init(void) { static ushort flag = 1; audio_swap_flag = *((uchar*)&flag) == 0; /* big-endian? */ audio_info_init(&av); } void audio_ctl_init(void) { } void audio_file_open(Chan *c, int omode) { char ebuf[ERRLEN]; switch(omode){ case OREAD: qlock(&inlock); if(waserror()){ qunlock(&inlock); nexterror(); } if(audio_file_in >= 0) error(Einuse); if((audio_file_in = audio_open_in()) < 0) { oserrstr(ebuf, sizeof ebuf); error(ebuf); } qunlock(&inlock); poperror(); break; case OWRITE: qlock(&outlock); if(waserror()){ qunlock(&outlock); nexterror(); } if(audio_file_out >= 0) error(Einuse); if((audio_file_out = audio_open_out() ) < 0){ oserrstr(ebuf, sizeof ebuf); error(ebuf); } qunlock(&outlock); poperror(); break; case ORDWR: qlock(&inlock); qlock(&outlock); if(waserror()){ qunlock(&inlock); qunlock(&outlock); nexterror(); } if(audio_file_in >= 0 || audio_file_out >= 0) error(Einuse); if((audio_file_in = audio_open_in()) < 0){ oserrstr(ebuf, sizeof ebuf); error(ebuf); } if(waserror()){ close(audio_file_in); audio_file_in = -1; nexterror(); } if((audio_file_out = audio_open_out()) < 0){ oserrstr(ebuf, sizeof ebuf); error(ebuf); } poperror(); qunlock(&inlock); qunlock(&outlock); poperror(); break; } } void audio_ctl_open(Chan *c, int omode) { USED(c); USED(omode); } void audio_file_close(Chan *c) { switch(c->mode){ case OREAD: qlock(&inlock); close(audio_file_in); audio_file_in = -1; qunlock(&inlock); break; case OWRITE: qlock(&outlock); close(audio_file_out); audio_file_out = -1; qunlock(&outlock); break; case ORDWR: qlock(&inlock); close(audio_file_in); audio_file_in = -1; qunlock(&inlock); qlock(&outlock); close(audio_file_out); audio_file_out = -1; qunlock(&outlock); break; } } void audio_ctl_close(Chan *c) { } long audio_file_read(Chan *c, void *va, long count, ulong offset) { struct timespec time; long ba, status, chunk, total; char *pva = (char *) va; qlock(&inlock); if(waserror()){ qunlock(&inlock); nexterror(); } if(audio_file_in < 0) error(Eperm); /* check block alignment */ ba = av.in.bits * av.in.chan / Bits_Per_Byte; if(count % ba) error(Ebadarg); if(! audio_pause_in(audio_file_in, A_UnPause)) error(Eio); total = 0; while(total < count) { chunk = count - total; osenter(); status = read(audio_file_in, pva + total, chunk); osleave(); if(status < 0) error(Eio); total += status; } if(total != count) error(Eio); if(audio_swap_flag && av.out.bits == 16) audio_swap_endian(pva, count); poperror(); qunlock(&inlock); time.tv_sec = 0; /* hack around broken thread scheduler in Solaris */ time.tv_nsec= 1; nanosleep(&time,nil); return count; } long audio_file_write(Chan *c, void *va, long count, ulong offset) { struct timespec time; long status = -1; long ba, total, chunk, bufsz; qlock(&outlock); if(waserror()){ qunlock(&outlock); nexterror(); } if(audio_file_out < 0) error(Eperm); /* check block alignment */ ba = av.out.bits * av.out.chan / Bits_Per_Byte; if(count % ba) error(Ebadarg); if(audio_swap_flag && av.out.bits == 16) audio_swap_endian(va, count); total = 0; bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val; if(bufsz == 0) error(Ebadarg); while(total < count) { chunk = min(bufsz, count - total); osenter(); status = write(audio_file_out, va, chunk); osleave(); if(status != count) error(Eio); total += chunk; } poperror(); qunlock(&outlock); time.tv_sec = 0; /* hack around broken thread scheduler in Solaris */ time.tv_nsec= 1; nanosleep(&time,nil); return count; } int audio_open_in(void) { int fd; /* open non-blocking in case someone already has it open */ /* otherwise we would block until they close! */ fd = open(AUDIO_FILE_STRING, O_RDONLY|O_NONBLOCK); if(fd < 0) error(Einuse); /* change device to be blocking */ if(! audio_set_blocking(fd)) { close(fd); error(Eio); } if(! audio_pause_in(fd, A_Pause)) { close(fd); error(Eio); } if(! audio_flush(fd, A_In)) { close(fd); error(Eio); } /* set audio info */ av.in.flags = ~0; av.out.flags = 0; if(! audio_set_info(fd, &av.in, A_In)) { close(fd); error(Ebadarg); } av.in.flags = 0; /* tada, we're open, blocking, paused and flushed */ return fd; } int audio_open_out(void) { int fd; struct audio_info hdr; /* open non-blocking in case someone already has it open */ /* otherwise we would block until they close! */ fd = open(AUDIO_FILE_STRING, O_WRONLY|O_NONBLOCK); if(fd < 0) error(Einuse); /* change device to be blocking */ if(! audio_set_blocking(fd)) { close(fd); error(Eio); } /* set audio info */ av.in.flags = 0; av.out.flags = ~0; if(! audio_set_info(fd, &av.out, A_Out)) { close(fd); error(Ebadarg); } av.out.flags = 0; return fd; } long audio_ctl_read(Chan *c, void *va, long count, ulong offset) { char buf[AUDIO_INFO_MAXBUF+1]; audio_info_t info; char *p = va; int status; qlock(&inlock); if (waserror()) { qunlock(&inlock); nexterror(); } /* check if buffer is big enough for all the info */ if (count < AUDIO_INFO_MAXBUF) { error(Ebadarg); return - 1; } if (audio_file_in >= 0) { /* initialize header */ AUDIO_INITINFO(&info); osenter(); status = ioctl(audio_file_in, AUDIO_GETINFO, &info); osleave(); if (status == -1) error(Ebadarg); } if ((count = audio_get_info(buf, &av.in, &av.out)) == 0) { error(Ebadarg); return - 1; } poperror(); qunlock(&inlock); return readstr(offset, p, count, buf); } long audio_ctl_write(Chan *c, void *va, long count, ulong offset) { int fd; int ff; Audio_t tmpav = av; tmpav.in.flags = 0; tmpav.out.flags = 0; if (!audioparse(va, count, &tmpav)) error(Ebadarg); if (!audio_enforce(&tmpav)) error(Ebadarg); qlock(&inlock); if (waserror()) { qunlock(&inlock); nexterror(); } if (audio_file_in >= 0 && (tmpav.in.flags & AUDIO_MOD_FLAG)) { if (!audio_pause_in(audio_file_in, A_Pause)) error(Ebadarg); if (!audio_flush(audio_file_in, A_In)) error(Ebadarg); if (!audio_set_info(audio_file_in, &tmpav.in, A_In)) error(Ebadarg); } poperror(); qunlock(&inlock); qlock(&outlock); if (waserror()) { qunlock(&outlock); nexterror(); } if (audio_file_out >= 0 && (tmpav.out.flags & AUDIO_MOD_FLAG)){ if (!audio_pause_out(audio_file_out)) error(Ebadarg); if (!audio_set_info(audio_file_out, &tmpav.out, A_Out)) error(Ebadarg); } poperror(); qunlock(&outlock); tmpav.in.flags = 0; tmpav.out.flags = 0; av = tmpav; return count; } static int audio_set_blocking(int fd) { int val; if((val = fcntl(fd, F_GETFL, 0)) == -1) return 0; val &= ~O_NONBLOCK; if(fcntl(fd, F_SETFL, val) < 0) return 0; return 1; } static int audio_set_info(int fd, Audio_d *i, int d) { int status; int unequal_stereo = 0; audio_info_t info; audio_prinfo_t *dev; if(fd < 0) return 0; /* devitialize header */ AUDIO_INITINFO(&info); if(d == A_In) dev = &info.record; else dev = &info.play; /* sample rate */ if(i->flags & AUDIO_RATE_FLAG) dev->sample_rate = i->rate; /* channels */ if(i->flags & AUDIO_CHAN_FLAG) dev->channels = i->chan; /* precision */ if(i->flags & AUDIO_BITS_FLAG) dev->precision = i->bits; /* encoding */ if(i->flags & AUDIO_ENC_FLAG) dev->encoding = i->enc; /* devices */ if(i->flags & AUDIO_DEV_FLAG) dev->port = i->dev; /* dev volume */ if(i->flags & (AUDIO_LEFT_FLAG|AUDIO_VOL_FLAG)) { dev->gain = (i->left * AUDIO_MAX_GAIN) / Audio_Max_Val; /* do left first then right later */ if(i->left == i->right) dev->balance = AUDIO_MID_BALANCE; else { dev->balance = AUDIO_LEFT_BALANCE; if(i->chan != 1) unequal_stereo = 1; } } osenter(); status = ioctl(fd, AUDIO_SETINFO, &info); /* qlock and load general stuff */ osleave(); if(status == -1) { if(debug) printf("audio_set_info 1 failed: fd = %d errno = %d\n", fd, errno); return 0; } /* check for different right and left for dev */ if(unequal_stereo) { /* re-init header */ AUDIO_INITINFO(&info); dev->gain = (i->right * AUDIO_MAX_GAIN) / Audio_Max_Val; dev->balance == AUDIO_RIGHT_BALANCE; osenter(); status = ioctl(fd, AUDIO_SETINFO, &info); osleave(); if(status == -1) { if(debug) printf("audio_set_info 2 failed: fd = %d errno = %d\n",fd, errno); return 0; } } return 1; } void audio_swap_endian(char *p, int n) { int b; while (n > 1) { b = p[0]; p[0] = p[1]; p[1] = b; p += 2; n -= 2; } } static int audio_pause_out(int fd) { audio_info_t info; int foo = 0; int status; osenter(); status = ioctl(fd, AUDIO_DRAIN, &foo); osleave(); if(status == -1) return 0; return 1; } static int audio_pause_in(int fd, int f) { audio_info_t info; int status; if(fd < 0) return 0; if(audio_in_pause == f) return 1; /* initialize header */ AUDIO_INITINFO(&info); /* unpause input */ if(f == A_Pause) info.record.pause = 1; else info.record.pause = 0; osenter(); status = ioctl(fd, AUDIO_SETINFO, &info); osleave(); if(status == -1) return 0; audio_in_pause = f; return 1; } static int audio_flush(int fd, int d) { int flag = d==A_In? FLUSHR: FLUSHW; int status; osenter(); status = ioctl(fd, I_FLUSH, flag); /* drain anything already put into buffer */ osleave(); if(status == -1) return 0; return 1; } static int audio_enforce(Audio_t *t) { if((t->in.enc == Audio_Ulaw_Val || t->in.enc == Audio_Alaw_Val) && (t->in.rate != 8000 || t->in.chan != 1)) return 0; if((t->out.enc == Audio_Ulaw_Val || t->out.enc == Audio_Alaw_Val) && (t->out.rate != 8000 || t->out.chan != 1)) return 0; return 1; }