Community Projects

Miscellaneous Forums/Linux Discussion/Community Projects

I've now uploaded the source to the changes here: http://www.teamted.net/freeaudioalsa.tar.gz

These 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.