OK, I've wrapped up the work on ALSA for now.
As above:
And:
I've tried to separate OSS and ALSA functionality. The system will try ALSA then fallback to OSS, and if that fails too, fail completely. The way it is structured it should be relatively easy to add new sound system support.
I should point out this is the first time I've really tried to program anything in C in about six years. Please feel free to offer any improvements. The code I added is completely public domain.
As above:
freeaudio.bmx ?MacOS Import "-framework AudioUnit" Import "-framework AudioToolbox" ? ?Linux Import "-lasound" ?
And:
#ifdef __linux #include <sys/ioctl.h> #include <unistd.h> #include <fcntl.h> #include <sys/soundcard.h> #include <pthread.h> #include <alsa/asoundlib.h> void *mixerthread(void *dev); #define LINUXFRAG 2048 struct linuxaudio:audiodevice { pthread_t audiothread; int threadid; int audio_fd; snd_pcm_t *alsaaudio_fd; int playing; int fragsize,fragcount; int buffersize; //in bytes short *buffer; int reset() { playing=0; mix=new mixer(LINUXFRAG); mix->freq=44100; mix->channels=2; buffer=new short[LINUXFRAG]; buffersize=LINUXFRAG*2; pthread_attr_t attr; pthread_attr_init(&attr); threadid=pthread_create(&audiothread,&attr,mixerthread,(void*)this); return 0; } int close() { int timeout; playing=0; timeout=5; while (timeout-- && audio_fd) sleep(1); return 0; } }; audiodevice *OpenSystemAudio() { return new linuxaudio(); } void initAudioALSA(linuxaudio *dev) { if (snd_pcm_open(&dev->alsaaudio_fd, strdup("default"), SND_PCM_STREAM_PLAYBACK, 0)==-1) //uses default audio device.. { dev->alsaaudio_fd=0; } } void initAudioOSS(linuxaudio *dev) { dev->audio_fd=open("/dev/dsp",O_WRONLY,0); //|O_NONBLOCK if (dev->audio_fd==-1) { dev->audio_fd=0; } } void *playAudioOSS(linuxaudio *dev) { int data,res; data=0x03000c; //2 fragments of 4096 samples res=ioctl(dev->audio_fd,SNDCTL_DSP_SETFRAGMENT,&data); data=AFMT_S16_LE; res=ioctl(dev->audio_fd,SNDCTL_DSP_SETFMT,&data); data=2; res=ioctl(dev->audio_fd,SNDCTL_DSP_CHANNELS,&data); data=44100; res=ioctl(dev->audio_fd,SNDCTL_DSP_SPEED,&data); dev->playing=1; while (dev->playing) { dev->mix->mix16(dev->buffer); int n=write(dev->audio_fd,dev->buffer,dev->buffersize); } close(dev->audio_fd); } void *playAudioALSA(linuxaudio *dev) { unsigned int val; snd_pcm_uframes_t periodsize; snd_pcm_hw_params_t *hwparams; snd_pcm_hw_params_alloca(&hwparams); int output_rate; int channels; int fragment_size; int fragment_count; fragment_size=LINUXFRAG; //overall buffer size fragment_count=2; //2 - 16 fragment count - 2 minimum, the lower it is potentially the lower the latency //configure device if (snd_pcm_hw_params_any(dev->alsaaudio_fd, hwparams) < 0) { //printf("linuxaudio failed at params any\n"); return 0; } if (snd_pcm_hw_params_set_access(dev->alsaaudio_fd, hwparams,SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { //printf("linuxaudio failed at set access\n"); return 0; } if (snd_pcm_hw_params_set_format(dev->alsaaudio_fd, hwparams,SND_PCM_FORMAT_S16_LE) < 0) { //printf("linuxaudio failed at set format\n"); return 0; } val = 44100; if (snd_pcm_hw_params_set_rate_near(dev->alsaaudio_fd, hwparams,&val, 0) < 0) { /* Try 48KHZ too */ //printf("linuxaudio - %d HZ not available, trying 48000HZ\n", output_rate); val = 48000; if (snd_pcm_hw_params_set_rate_near(dev->alsaaudio_fd, hwparams,&val, 0) < 0) { //printf("linuxaudio failed at setting output rate (%d)\n", output_rate); return 0; } dev->mix->freq=val; } channels=2; if (snd_pcm_hw_params_set_channels(dev->alsaaudio_fd, hwparams, channels) < 0) { //printf("linuxaudio failed at set channels (%d)\n", channels); return 0; } periodsize = (fragment_size) / 4; // bytes -> frames for 16-bit,stereo - should be a minimum of 512 if (snd_pcm_hw_params_set_period_size_near(dev->alsaaudio_fd, hwparams,&periodsize, 0) < 0) { //printf("linuxaudio failed at set period size (%d)\n", (int)periodsize); return 0; } val = fragment_count; if (snd_pcm_hw_params_set_periods_near(dev->alsaaudio_fd, hwparams,&val, 0) < 0) { //printf("linuxaudio failed at set periods (%d)\n", val); //should attempt a one by one period increase up to 16? return 0; } if (snd_pcm_hw_params(dev->alsaaudio_fd, hwparams) < 0) { //printf("linuxaudio failed at installing hw params\n"); return 0; } //loop while playing sound dev->playing=1; while (dev->playing) { dev->mix->mix16(dev->buffer); if ((snd_pcm_writei (dev->alsaaudio_fd, dev->buffer,LINUXFRAG/2)) < 0) { //Half buffer for two channels? //printf ("linuxaudio warning: buffer underrun occurred\n"); if (snd_pcm_prepare(dev->alsaaudio_fd) < 0) { //printf ("linuxaudio failed at preparing pcm\n"); dev->playing=0; //die gracefully } } } snd_pcm_drop(dev->alsaaudio_fd); snd_pcm_close (dev->alsaaudio_fd); dev->alsaaudio_fd=0; } void *mixerthread(void *v) { sched_param sched; linuxaudio *dev; int policy; bool usealsa; usealsa=1; pthread_getschedparam(pthread_self(),&policy,&sched); sched.sched_priority++;//policy=SCHED_RR; pthread_setschedparam(pthread_self(),policy,&sched); dev=(linuxaudio*)v; //try alsa initAudioALSA(dev); if (dev->alsaaudio_fd==0) //uses default audio device.. { //try OSS initAudioOSS(dev); if (dev->audio_fd==0) { printf("linuxaudio failed\n"); return 0; } usealsa=0; } if (usealsa=1) { playAudioALSA(dev); } else { playAudioOSS(dev); } dev->audio_fd=0; return 0; } #endif
I've tried to separate OSS and ALSA functionality. The system will try ALSA then fallback to OSS, and if that fails too, fail completely. The way it is structured it should be relatively easy to add new sound system support.
I should point out this is the first time I've really tried to program anything in C in about six years. Please feel free to offer any improvements. The code I added is completely public domain.