#include "dat.h"#include "fns.h"#include "error.h"#include "audio.h"#include <sys/ioctl.h>#include <sys/soundcard.h> #define Audio_Mic_Val SOUND_MIXER_MIC#define Audio_Linein_Val SOUND_MIXER_LINE #define Audio_Speaker_Val SOUND_MIXER_PCM // SOUND_MIXER_VOLUME#define Audio_Headphone_Val SOUND_MIXER_ALTPCM#define Audio_Lineout_Val SOUND_MIXER_CD #define Audio_Pcm_Val AFMT_S16_LE#define Audio_Ulaw_Val AFMT_MU_LAW#define Audio_Alaw_Val AFMT_A_LAW #include "audio-tbls.c"#define min(a,b) ((a) < (b) ? (a) : (b)) #define DEVAUDIO "/dev/dsp"#define DEVMIXER "/dev/mixer" #define DPRINT if(1)print enum { A_Pause, A_UnPause, A_In, A_Out,}; static struct { int data; /* dsp data fd */ int ctl; /* mixer fd */ int pause; QLock lk;} afd = {.data = -1, .ctl = -1, .pause =A_UnPause }; static Audio_t av;static QLock inlock;static QLock outlock; static int audio_open(int);static int audio_pause(int, int);static int audio_set_info(int, Audio_d*, int); Audio_t*getaudiodev(void){ return &av;} void audio_file_init(void){ audio_info_init(&av);} voidaudio_file_open(Chan *c, int omode){ USED(c); DPRINT("audio_file_open %d %d %x\n", afd.data, afd.ctl, omode); qlock(&afd.lk); if(waserror()){ qunlock(&afd.lk); nexterror(); } if(afd.data >= 0) error(Einuse); if(afd.ctl < 0){ afd.ctl = open(DEVMIXER, ORDWR); if(afd.ctl < 0) oserror(); } afd.data = audio_open(omode); if(afd.data < 0) oserror(); poperror(); qunlock(&afd.lk);} void audio_file_close(Chan *c){ USED(c); DPRINT("audio_file_close %d %d\n", afd.data, afd.ctl); qlock(&afd.lk); if(waserror()){ qunlock(&afd.lk); nexterror(); } close(afd.data); afd.data = -1; qunlock(&afd.lk); poperror();} longaudio_file_read(Chan *c, void *va, long count, vlong offset){ long ba, status, chunk, total; USED(c); USED(offset); DPRINT("audio_file_read %d %d\n", afd.data, afd.ctl); qlock(&inlock); if(waserror()){ qunlock(&inlock); nexterror(); } if(afd.data < 0) error(Eperm); /* check block alignment */ ba = av.in.bits * av.in.chan / Bits_Per_Byte; if(count % ba) error(Ebadarg); if(!audio_pause(afd.data, A_UnPause)) error(Eio); total = 0; while(total < count){ chunk = count - total; status = read (afd.data, va + total, chunk); if(status < 0) error(Eio); total += status; } if(total != count) error(Eio); poperror(); qunlock(&inlock); return count;} long audio_file_write(Chan *c, void *va, long count, vlong offset){ long status = -1; long ba, total, chunk, bufsz; USED(c); USED(offset); DPRINT("audio_file_write %d %d\n", afd.data, afd.ctl); qlock(&outlock); if(waserror()){ qunlock(&outlock); nexterror(); } if(afd.data < 0) error(Eperm); /* check block alignment */ ba = av.out.bits * av.out.chan / Bits_Per_Byte; if(count % ba) error(Ebadarg); 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); status = write(afd.data, va, chunk); if(status <= 0) error(Eio); total += status; } poperror(); qunlock(&outlock); return count; } longaudio_ctl_write(Chan *c, void *va, long count, vlong offset){ Audio_t tmpav = av; int tfd; USED(c); USED(offset); tmpav.in.flags = 0; tmpav.out.flags = 0; DPRINT ("audio_ctl_write %X %X\n", afd.data, afd.ctl); if(!audioparse(va, count, &tmpav)) error(Ebadarg); if(!canqlock(&inlock)) error("device busy"); if(waserror()){ qunlock(&inlock); nexterror(); } if(!canqlock(&outlock)) error("device busy"); if(waserror()){ qunlock(&outlock); nexterror(); } /* DEVAUDIO needs to be open to issue an ioctl */ tfd = afd.data; if(tfd < 0){ tfd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK); if(tfd < 0) oserror(); } if(waserror()){ if(tfd != afd.data) close(tfd); nexterror(); } if(tmpav.in.flags & AUDIO_MOD_FLAG){ if(!audio_pause(tfd, A_Pause)) error(Ebadarg); if(!audio_set_info(tfd, &tmpav.in, A_In)) error(Ebadarg); } poperror(); if(tfd != afd.data) close(tfd); tmpav.in.flags = 0; av = tmpav; poperror(); qunlock(&outlock); poperror(); qunlock(&inlock); return count;} /* Linux/OSS specific stuff */ static intchoosefmt(Audio_d *i){ switch(i->bits){ case 8: switch(i->enc){ case Audio_Alaw_Val: return AFMT_A_LAW; case Audio_Ulaw_Val: return AFMT_MU_LAW; case Audio_Pcm_Val: return AFMT_U8; } break; case 16: if(i->enc == Audio_Pcm_Val) return AFMT_S16_LE; break; } return -1;} static intsetvolume(int fd, int what, int left, int right){ int can, v; if(ioctl(fd, SOUND_MIXER_READ_DEVMASK, &can) < 0) can = ~0; DPRINT("setvolume fd%d %X can mix 0x%X (mask %X)\n", fd, what, (can & (1<<what)), can); if(!(can & (1<<what))) return 0; v = left | (right<<8); if(ioctl(afd.ctl, MIXER_WRITE(what), &v) < 0) return 0; return 1;} static intaudio_set_info(int fd, Audio_d *i, int d){ int status, arg; int oldfmt, newfmt; USED(d); DPRINT("audio_set_info (%d) %d %d\n", fd, afd.data, afd.ctl); if(fd < 0) return 0; /* sample rate */ if(i->flags & AUDIO_RATE_FLAG){ arg = i->rate; if(ioctl(fd, SNDCTL_DSP_SPEED, &arg) < 0) return 0; } /* channels */ if(i->flags & AUDIO_CHAN_FLAG){ arg = i->chan; if(ioctl(fd, SNDCTL_DSP_CHANNELS, &arg) < 0) return 0; } /* precision */ if(i->flags & AUDIO_BITS_FLAG){ arg = i->bits; if(ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &arg) < 0) return 0; } /* encoding */ if(i->flags & AUDIO_ENC_FLAG){ ioctl(fd, SNDCTL_DSP_GETFMTS, &oldfmt); newfmt = choosefmt(i); if(newfmt < 0) return 0; if(newfmt != oldfmt){ status = ioctl(fd, SNDCTL_DSP_SETFMT, &arg); DPRINT ("enc oldfmt newfmt %x status %d\n", oldfmt, newfmt, status); } } /* dev volume */ if(i->flags & (AUDIO_LEFT_FLAG|AUDIO_VOL_FLAG)) return setvolume(afd.ctl, i->dev, i->left, i->right); return 1;} static intaudio_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 intaudio_open(int omode){ int fd; /* open non-blocking in case someone already has it open */ /* otherwise we would block until they close! */ switch (omode){ case OREAD: fd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK); break; case OWRITE: fd = open(DEVAUDIO, O_WRONLY|O_NONBLOCK); break; case ORDWR: fd = open(DEVAUDIO, O_RDWR|O_NONBLOCK); break; } DPRINT("audio_open %d\n", fd); if(fd < 0) oserror(); /* change device to be blocking */ if(!audio_set_blocking(fd)){ close(fd); error("cannot set blocking mode"); } if(!audio_pause(fd, A_Pause)){ 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;} static intdspsync(int fd){ return ioctl(fd, SNDCTL_DSP_RESET, NULL) >= 0 && ioctl(fd, SNDCTL_DSP_SYNC, NULL) >= 0;} static intaudio_pause(int fd, int f){ int status; // DPRINT ("audio_pause (%d) %d %d\n", fd, afd.data, afd.ctl); if(fd < 0) return 0; if(fd != afd.data) return dspsync(fd); qlock(&afd.lk); if(afd.pause == f){ qunlock(&afd.lk); return 1; } if(waserror()){ qunlock(&afd.lk); nexterror(); } status = dspsync(afd.data); if(status) afd.pause = f; poperror(); qunlock(&afd.lk); return status;}
#include "dat.h"
#include "fns.h"
#include "error.h"
#include "audio.h"
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#define Audio_Mic_Val SOUND_MIXER_MIC
#define Audio_Linein_Val SOUND_MIXER_LINE
#define Audio_Speaker_Val SOUND_MIXER_PCM // SOUND_MIXER_VOLUME
#define Audio_Headphone_Val SOUND_MIXER_ALTPCM
#define Audio_Lineout_Val SOUND_MIXER_CD
#define Audio_Pcm_Val AFMT_S16_LE
#define Audio_Ulaw_Val AFMT_MU_LAW
#define Audio_Alaw_Val AFMT_A_LAW
#include "audio-tbls.c"
#define min(a,b) ((a) < (b) ? (a) : (b))
#define DEVAUDIO "/dev/dsp"
#define DEVMIXER "/dev/mixer"
#define DPRINT if(1)print
enum {
A_Pause,
A_UnPause,
A_In,
A_Out,
};
static struct {
int data; /* dsp data fd */
int ctl; /* mixer fd */
int pause;
QLock lk;
} afd = {.data = -1, .ctl = -1, .pause =A_UnPause };
static Audio_t av;
static QLock inlock;
static QLock outlock;
static int audio_open(int);
static int audio_pause(int, int);
static int audio_set_info(int, Audio_d*, int);
Audio_t*
getaudiodev(void)
{
return &av;
}
void
audio_file_init(void)
{
audio_info_init(&av);
}
void
audio_file_open(Chan *c, int omode)
{
USED(c);
DPRINT("audio_file_open %d %d %x\n", afd.data, afd.ctl, omode);
qlock(&afd.lk);
if(waserror()){
qunlock(&afd.lk);
nexterror();
}
if(afd.data >= 0)
error(Einuse);
if(afd.ctl < 0){
afd.ctl = open(DEVMIXER, ORDWR);
if(afd.ctl < 0)
oserror();
}
afd.data = audio_open(omode);
if(afd.data < 0)
oserror();
poperror();
qunlock(&afd.lk);
}
void
audio_file_close(Chan *c)
{
USED(c);
DPRINT("audio_file_close %d %d\n", afd.data, afd.ctl);
qlock(&afd.lk);
if(waserror()){
qunlock(&afd.lk);
nexterror();
}
close(afd.data);
afd.data = -1;
qunlock(&afd.lk);
poperror();
}
long
audio_file_read(Chan *c, void *va, long count, vlong offset)
{
long ba, status, chunk, total;
USED(c);
USED(offset);
DPRINT("audio_file_read %d %d\n", afd.data, afd.ctl);
qlock(&inlock);
if(waserror()){
qunlock(&inlock);
nexterror();
}
if(afd.data < 0)
error(Eperm);
/* check block alignment */
ba = av.in.bits * av.in.chan / Bits_Per_Byte;
if(count % ba)
error(Ebadarg);
if(!audio_pause(afd.data, A_UnPause))
error(Eio);
total = 0;
while(total < count){
chunk = count - total;
status = read (afd.data, va + total, chunk);
if(status < 0)
error(Eio);
total += status;
}
if(total != count)
error(Eio);
poperror();
qunlock(&inlock);
return count;
}
long
audio_file_write(Chan *c, void *va, long count, vlong offset)
{
long status = -1;
long ba, total, chunk, bufsz;
USED(c);
USED(offset);
DPRINT("audio_file_write %d %d\n", afd.data, afd.ctl);
qlock(&outlock);
if(waserror()){
qunlock(&outlock);
nexterror();
}
if(afd.data < 0)
error(Eperm);
/* check block alignment */
ba = av.out.bits * av.out.chan / Bits_Per_Byte;
if(count % ba)
error(Ebadarg);
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);
status = write(afd.data, va, chunk);
if(status <= 0)
error(Eio);
total += status;
}
poperror();
qunlock(&outlock);
return count;
}
long
audio_ctl_write(Chan *c, void *va, long count, vlong offset)
{
Audio_t tmpav = av;
int tfd;
USED(c);
USED(offset);
tmpav.in.flags = 0;
tmpav.out.flags = 0;
DPRINT ("audio_ctl_write %X %X\n", afd.data, afd.ctl);
if(!audioparse(va, count, &tmpav))
error(Ebadarg);
if(!canqlock(&inlock))
error("device busy");
if(waserror()){
qunlock(&inlock);
nexterror();
}
if(!canqlock(&outlock))
error("device busy");
if(waserror()){
qunlock(&outlock);
nexterror();
}
/* DEVAUDIO needs to be open to issue an ioctl */
tfd = afd.data;
if(tfd < 0){
tfd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK);
if(tfd < 0)
oserror();
}
if(waserror()){
if(tfd != afd.data)
close(tfd);
nexterror();
}
if(tmpav.in.flags & AUDIO_MOD_FLAG){
if(!audio_pause(tfd, A_Pause))
error(Ebadarg);
if(!audio_set_info(tfd, &tmpav.in, A_In))
error(Ebadarg);
}
poperror();
if(tfd != afd.data)
close(tfd);
tmpav.in.flags = 0;
av = tmpav;
poperror();
qunlock(&outlock);
poperror();
qunlock(&inlock);
return count;
}
/* Linux/OSS specific stuff */
static int
choosefmt(Audio_d *i)
{
switch(i->bits){
case 8:
switch(i->enc){
case Audio_Alaw_Val:
return AFMT_A_LAW;
case Audio_Ulaw_Val:
return AFMT_MU_LAW;
case Audio_Pcm_Val:
return AFMT_U8;
}
break;
case 16:
if(i->enc == Audio_Pcm_Val)
return AFMT_S16_LE;
break;
}
return -1;
}
static int
setvolume(int fd, int what, int left, int right)
{
int can, v;
if(ioctl(fd, SOUND_MIXER_READ_DEVMASK, &can) < 0)
can = ~0;
DPRINT("setvolume fd%d %X can mix 0x%X (mask %X)\n", fd, what, (can & (1<<what)), can);
if(!(can & (1<<what)))
return 0;
v = left | (right<<8);
if(ioctl(afd.ctl, MIXER_WRITE(what), &v) < 0)
return 0;
return 1;
}
static int
audio_set_info(int fd, Audio_d *i, int d)
{
int status, arg;
int oldfmt, newfmt;
USED(d);
DPRINT("audio_set_info (%d) %d %d\n", fd, afd.data, afd.ctl);
if(fd < 0)
return 0;
/* sample rate */
if(i->flags & AUDIO_RATE_FLAG){
arg = i->rate;
if(ioctl(fd, SNDCTL_DSP_SPEED, &arg) < 0)
return 0;
}
/* channels */
if(i->flags & AUDIO_CHAN_FLAG){
arg = i->chan;
if(ioctl(fd, SNDCTL_DSP_CHANNELS, &arg) < 0)
return 0;
}
/* precision */
if(i->flags & AUDIO_BITS_FLAG){
arg = i->bits;
if(ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &arg) < 0)
return 0;
}
/* encoding */
if(i->flags & AUDIO_ENC_FLAG){
ioctl(fd, SNDCTL_DSP_GETFMTS, &oldfmt);
newfmt = choosefmt(i);
if(newfmt < 0)
return 0;
if(newfmt != oldfmt){
status = ioctl(fd, SNDCTL_DSP_SETFMT, &arg);
DPRINT ("enc oldfmt newfmt %x status %d\n", oldfmt, newfmt, status);
}
}
/* dev volume */
if(i->flags & (AUDIO_LEFT_FLAG|AUDIO_VOL_FLAG))
return setvolume(afd.ctl, i->dev, i->left, i->right);
return 1;
}
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_open(int omode)
{
int fd;
/* open non-blocking in case someone already has it open */
/* otherwise we would block until they close! */
switch (omode){
case OREAD:
fd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK);
break;
case OWRITE:
fd = open(DEVAUDIO, O_WRONLY|O_NONBLOCK);
break;
case ORDWR:
fd = open(DEVAUDIO, O_RDWR|O_NONBLOCK);
break;
}
DPRINT("audio_open %d\n", fd);
if(fd < 0)
oserror();
/* change device to be blocking */
if(!audio_set_blocking(fd)){
close(fd);
error("cannot set blocking mode");
}
if(!audio_pause(fd, A_Pause)){
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;
}
static int
dspsync(int fd)
{
return ioctl(fd, SNDCTL_DSP_RESET, NULL) >= 0 &&
ioctl(fd, SNDCTL_DSP_SYNC, NULL) >= 0;
}
static int
audio_pause(int fd, int f)
{
int status;
// DPRINT ("audio_pause (%d) %d %d\n", fd, afd.data, afd.ctl);
if(fd < 0)
return 0;
if(fd != afd.data)
return dspsync(fd);
qlock(&afd.lk);
if(afd.pause == f){
qunlock(&afd.lk);
return 1;
}
if(waserror()){
qunlock(&afd.lk);
nexterror();
}
status = dspsync(afd.data);
if(status)
afd.pause = f;
poperror();
qunlock(&afd.lk);
return status;
}