ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /emu/OpenBSD/audio.c/
#include "dat.h"
#include "fns.h"
#include "error.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/filio.h>
#include "audio.h"
#include <soundcard.h>
#define Audio_Mic_Val SOUND_MIXER_MIC
#define Audio_Linein_Val SOUND_MIXER_LINE
#define Audio_Speaker_Val SOUND_MIXER_SPEAKER
#define Audio_Headphone_Val SOUND_MIXER_PHONEOUT
#define Audio_Lineout_Val SOUND_MIXER_VOLUME
#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))
static int debug;
#define AUDIO_FILE_STRING "/dev/dsp"
enum {
A_Pause,
A_UnPause
};
enum {
A_In,
A_Out
};
static QLock inlock;
static QLock outlock;
static int audio_file = -1; /* file in/out */
static int audio_file_in = -1; /* copy of above when opened O_READ/O_RDWR */
static int audio_file_out = -1; /* copy of above when opened O_WRITE/O_RDWR */
static int audio_swap_flag = 0; /* endian swap */
static int audio_in_pause = A_UnPause;
static Audio_t av;
static int mixerleftvol[32];
static int mixerrightvol[32];
static int audio_enforce(Audio_t*);
static int audio_open(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)
{
int i;
static ushort flag = 1;
audio_swap_flag = *((uchar*)&flag) == 0; /* big-endian? */
audio_info_init(&av);
for (i = 0; i < 32; i++)
mixerleftvol[i] = mixerrightvol[i] = 100;
}
void
audio_ctl_init(void)
{
}
void
audio_file_open(Chan *c, int omode)
{
char ebuf[ERRMAX];
if (debug)
print("audio_file_open(0x%.8lux, %d)\n", c, omode);
switch(omode){
case OREAD:
qlock(&inlock);
if(waserror()){
qunlock(&inlock);
nexterror();
}
if(audio_file_in >= 0)
error(Einuse);
if (audio_file < 0)
audio_file = audio_open();
audio_file_in = audio_file;
poperror();
qunlock(&inlock);
break;
case OWRITE:
qlock(&outlock);
if(waserror()){
qunlock(&outlock);
nexterror();
}
if(audio_file_out >= 0)
error(Einuse);
if (audio_file < 0)
audio_file = audio_open();
audio_file_out = audio_file;
poperror();
qunlock(&outlock);
break;
case ORDWR:
qlock(&inlock);
qlock(&outlock);
if(waserror()){
qunlock(&outlock);
qunlock(&inlock);
nexterror();
}
if(audio_file_in >= 0 || audio_file_out >= 0)
error(Einuse);
if (audio_file < 0)
audio_file = audio_open();
audio_file_in = audio_file_out = audio_file;
poperror();
qunlock(&outlock);
qunlock(&inlock);
break;
}
if (debug)
print("audio_file_open: success\nin %d out %d both %d\n",
audio_file_out, audio_file_in, audio_file);
}
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);
qlock(&outlock);
if (audio_file_out < 0) {
close(audio_file);
audio_file = -1;
}
qunlock(&outlock);
audio_file_in = -1;
qunlock(&inlock);
break;
case OWRITE:
qlock(&inlock);
qlock(&outlock);
if (audio_file_in < 0) {
close(audio_file);
audio_file = -1;
}
audio_file_out = -1;
qunlock(&outlock);
qunlock(&inlock);
break;
case ORDWR:
qlock(&inlock);
qlock(&outlock);
close(audio_file);
audio_file_in = audio_file_out = audio_file = -1;
qunlock(&outlock);
qunlock(&inlock);
break;
}
}
void
audio_ctl_close(Chan *c)
{
}
long
audio_file_read(Chan *c, void *va, long count, vlong 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);
return count;
}
long
audio_file_write(Chan *c, void *va, long count, vlong offset)
{
struct timespec time;
long status = -1;
long ba, total, chunk, bufsz;
if (debug > 1)
print("audio_file_write(0x%.8lux, 0x%.8lux, %ld, %uld)\n",
c, va, count, offset);
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 <= 0)
error(Eio);
total += status;
}
poperror();
qunlock(&outlock);
return count;
}
static int
audio_open(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_RDWR|O_NONBLOCK);
if(fd < 0)
oserror();
/* change device to be blocking */
if(!audio_set_blocking(fd)) {
if (debug)
print("audio_open: failed to set blocking\n");
close(fd);
error("cannot set blocking mode");
}
if (debug)
print("audio_open: blocking set\n");
/* 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;
}
long
audio_ctl_write(Chan *c, void *va, long count, vlong 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
doioctl(int fd, int ctl, int *info)
{
int status;
osenter();
status = ioctl(fd, ctl, info); /* qlock and load general stuff */
osleave();
if (status < 0)
print("doioctl(0x%.8lux, 0x%.8lux) failed %d\n", ctl, *info, errno);
return status;
}
static int
choosefmt(Audio_d *i)
{
int newbits, newenc;
newbits = i->bits;
newenc = i->enc;
switch (newenc) {
case Audio_Alaw_Val:
if (newbits == 8)
return AFMT_A_LAW;
break;
case Audio_Ulaw_Val:
if (newbits == 8)
return AFMT_MU_LAW;
break;
case Audio_Pcm_Val:
if (newbits == 8)
return AFMT_U8;
else if (newbits == 16)
return AFMT_S16_LE;
break;
}
return -1;
}
static int
audio_set_info(int fd, Audio_d *i, int d)
{
int status;
int unequal_stereo = 0;
if(fd < 0)
return 0;
/* fmt */
if(i->flags & (AUDIO_BITS_FLAG || AUDIO_ENC_FLAG)) {
int oldfmt, newfmt;
oldfmt = AFMT_QUERY;
if (doioctl(fd, SNDCTL_DSP_SETFMT, &oldfmt) < 0)
return 0;
if (debug)
print("audio_set_info: current format 0x%.8lux\n", oldfmt);
newfmt = choosefmt(i);
if (debug)
print("audio_set_info: new format 0x%.8lux\n", newfmt);
if (newfmt == -1 || newfmt != oldfmt && doioctl(fd, SNDCTL_DSP_SETFMT, &newfmt) < 0)
return 0;
}
/* channels */
if(i->flags & AUDIO_CHAN_FLAG) {
int channels = i->chan;
if (debug)
print("audio_set_info: new channels %d\n", channels);
if (doioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0
|| channels != i->chan)
return 0;
}
/* sample rate */
if(i->flags & AUDIO_RATE_FLAG) {
int speed = i->rate;
if (debug)
print("audio_set_info: new speed %d\n", speed);
if (doioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0 || speed != i->rate)
return 0;
}
/* dev volume */
if(i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG | AUDIO_RIGHT_FLAG)) {
int val;
if (i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG))
mixerleftvol[i->dev] = (i->left * 100) / Audio_Max_Val;
if (i->flags & (AUDIO_RIGHT_FLAG | AUDIO_VOL_FLAG))
mixerrightvol[i->dev] = (i->right * 100) / Audio_Max_Val;
val = mixerleftvol[i->dev] | (mixerrightvol[i->dev] << 8);
doioctl(fd, MIXER_WRITE(i->dev), &val);
}
if (i->flags & AUDIO_DEV_FLAG) {
}
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)
{
USED(fd);
return 1;
}
static int
audio_pause_in(int fd, int f)
{
USED(fd);
USED(f);
return 1;
}
static int
audio_flush(int fd, int d)
{
int x;
return doioctl(fd, SNDCTL_DSP_SYNC, &x) >= 0;
}
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;
}
Audio_t*
getaudiodev(void)
{
return &av;
}