I've now uploaded the source to the changes here:
http://www.teamted.net/freeaudioalsa.tar.gzThese are the files freeaudio.bmx and freeaudio.cpp from BlitzMax/mod/pub.mod/freeaudio.mod. I would suggest that you make a copy of freeaudio.bmx and freeaudio.cpp before you replace these files. You could also just Synchronize Modules to get back the original versions.
For those interested, I'll post the changes to the source for both files. I'll post the code around the changes for your reference.
freeaudio.bmx
?MacOS
Import "-framework AudioUnit"
Import "-framework AudioToolbox"
?
?Linux
Import "-lasound"
?
Added the Linux section with an import for ALSA libasound. MacOS stuff wasn't changed, just for reference.
freeaudio.cpp
#ifdef __linux
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <pthread.h>
void *mixerthread(void *dev);
#define LINUXFRAG 2048
struct linuxaudio:audiodevice
{
pthread_t audiothread;
int threadid;
snd_pcm_t *audio_fd;
int playback;
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-- && playback) sleep(1);
return 0;
}
};
audiodevice *OpenSystemAudio()
{
return new linuxaudio();
}
void *mixerthread(void *v)
{
sched_param sched;
linuxaudio *dev;
int policy;
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
pthread_getschedparam(pthread_self(),&policy,&sched);
sched.sched_priority++;//policy=SCHED_RR;
pthread_setschedparam(pthread_self(),policy,&sched);
//open audio device
dev=(linuxaudio*)v;
dev->playback=snd_pcm_open(&dev->audio_fd, strdup("default"), SND_PCM_STREAM_PLAYBACK, 0); //uses default audio device..
if (dev->playback==-1)
{
printf("linuxaudio failed\n");
dev->playback=0;
return 0;
}
//configure device
if (snd_pcm_hw_params_any(dev->audio_fd, hwparams) < 0) {
printf("linuxaudio failed at params any\n");
return 0;
}
if (snd_pcm_hw_params_set_access(dev->audio_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->audio_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->audio_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->audio_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->audio_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->audio_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->audio_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->audio_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->audio_fd, dev->buffer,LINUXFRAG/2)) < 0) { //Half buffer for two channels?
printf ("linuxaudio warning: buffer underrun occurred\n");
if (snd_pcm_prepare(dev->audio_fd) < 0) {
printf ("linuxaudio failed at preparing pcm\n");
dev->playing=0; //die gracefully
}
}
}
snd_pcm_drop(dev->audio_fd);
snd_pcm_close (dev->audio_fd);
dev->audio_fd=0;
dev->playback=0;
return 0;
}
#endif
All code from ifdef ___linux has been modified, scroll almost right down to the bottom of the file for the linux section.
I've tried to retain the method used with OSS for ALSA. Generally it would be recommended that we use interrupts and callbacks, but the thread stuff seems to work OK too. This should make clear the differences between ALSA and OSS - it shows you can use ALSA very much like OSS.
Tweaking the LINUXFRAG and fragment_count values might lead to lower latency.
Any suggestions or critique is appreciated.