#if defined (__linux__) // ALSA sound path

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <alsa/asoundlib.h>
#include <pthread.h>

#include "../client/snd_local.h"
#include "../qcommon/q_shared.h"

#define USE_SPINLOCK

#define BUFFER_SIZE (64*1024)
#define NUM_PERIODS 3
#define PERIOD_TIME 20000 // wishable latency

/* engine variables */

extern cvar_t *s_khz;
extern cvar_t *s_device;

/* pthreads  private variables */

#ifdef USE_ALSA_STATIC

#ifdef USE_SPINLOCK
#define	_pthread_spin_init pthread_spin_init
#define	_pthread_spin_destroy pthread_spin_destroy
#define	_pthread_spin_lock pthread_spin_lock
#define	_pthread_spin_unlock pthread_spin_unlock
#else // mutex
#define _pthread_mutex_init pthread_mutex_init
#define _pthread_mutex_destroy pthread_mutex_destroy
#define	_pthread_mutex_lock pthread_mutex_lock
#define	_pthread_mutex_unlock pthread_mutex_unlock
#endif

#define	_pthread_join pthread_join
#define	_pthread_create pthread_create
#define	_pthread_exit pthread_exit

#define	_snd_strerror snd_strerror
#define	_snd_pcm_open snd_pcm_open
#define	_snd_pcm_drain snd_pcm_drain
#define	_snd_pcm_drop snd_pcm_drop
#define	_snd_pcm_close snd_pcm_close
#define	_snd_pcm_hw_params_sizeof snd_pcm_hw_params_sizeof
#define	_snd_pcm_sw_params_sizeof snd_pcm_sw_params_sizeof
#define	_snd_pcm_hw_params_any snd_pcm_hw_params_any
#define _snd_async_add_pcm_handler snd_async_add_pcm_handler
#define _snd_async_handler_get_pcm snd_async_handler_get_pcm
#define	_snd_pcm_hw_params_set_rate_resample snd_pcm_hw_params_set_rate_resample
#define	_snd_pcm_hw_params_set_access snd_pcm_hw_params_set_access
#define	_snd_pcm_hw_params_set_format snd_pcm_hw_params_set_format
#define	_snd_pcm_hw_params_set_channels snd_pcm_hw_params_set_channels
#define	_snd_pcm_hw_params_set_rate_near snd_pcm_hw_params_set_rate_near
#define	_snd_pcm_hw_params_set_period_time_near snd_pcm_hw_params_set_period_time_near
#define	_snd_pcm_hw_params_set_periods snd_pcm_hw_params_set_periods
#define	_snd_pcm_hw_params_set_periods_near snd_pcm_hw_params_set_periods_near
#define	_snd_pcm_hw_params_get_period_size_min snd_pcm_hw_params_get_period_size_min
#define	_snd_pcm_hw_params snd_pcm_hw_params
#define	_snd_pcm_sw_params_current snd_pcm_sw_params_current
#define	_snd_pcm_sw_params_set_start_threshold snd_pcm_sw_params_set_start_threshold
#define	_snd_pcm_sw_params_set_stop_threshold snd_pcm_sw_params_set_stop_threshold
#define	_snd_pcm_sw_params_set_avail_min snd_pcm_sw_params_set_avail_min
#define	_snd_pcm_sw_params snd_pcm_sw_params
#define	_snd_pcm_start snd_pcm_start
#define	_snd_pcm_prepare snd_pcm_prepare
#define	_snd_pcm_resume snd_pcm_resume
#define	_snd_pcm_wait snd_pcm_wait
#define	__snd_pcm_state snd_pcm_state
#define	_snd_pcm_avail snd_pcm_avail
#define	_snd_pcm_avail_update snd_pcm_avail_update
#define	_snd_pcm_mmap_begin snd_pcm_mmap_begin
#define	_snd_pcm_mmap_commit snd_pcm_mmap_commit
#define _snd_pcm_writei snd_pcm_writei

#else

/* pthreads private variables */

static void *t_lib = NULL;

#ifdef USE_SPINLOCK
static int (*_pthread_spin_init)(pthread_spinlock_t *lock, int pshared);
static int (*_pthread_spin_destroy)(pthread_spinlock_t *lock);
static int (*_pthread_spin_lock)(pthread_spinlock_t *lock);
static int (*_pthread_spin_unlock)(pthread_spinlock_t *lock);
#else
static int (*_pthread_mutex_init)(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
static int (*_pthread_mutex_destroy)(pthread_mutex_t *mutex);
static int (*_pthread_mutex_lock)(pthread_mutex_t *mutex);
static int (*_pthread_mutex_unlock)(pthread_mutex_t *mutex);
#endif
static int (*_pthread_join)(pthread_t __th, void **__thread_return);
static int (*_pthread_create)(pthread_t *thread, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
static void (*_pthread_exit)(void *retval);

/* alsa private variables */

static void *a_lib = NULL;

static const char *(*_snd_strerror)(int errnum);
static int (*_snd_pcm_open)(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
static int (*_snd_pcm_drain)(snd_pcm_t *pcm);
static int (*_snd_pcm_drop)(snd_pcm_t *pcm);
static int (*_snd_pcm_close)(snd_pcm_t *pcm);
static size_t (*_snd_pcm_hw_params_sizeof)(void);
static size_t (*_snd_pcm_sw_params_sizeof)(void);
static snd_pcm_t* (*_snd_async_handler_get_pcm)(snd_async_handler_t *handler);
static int (*_snd_async_add_pcm_handler)(snd_async_handler_t **handler, snd_pcm_t *pcm, snd_async_callback_t callback, void *private_data);
static int (*_snd_pcm_hw_params_any)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
static int (*_snd_pcm_hw_params_set_rate_resample)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
static int (*_snd_pcm_hw_params_set_access)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access);
static int (*_snd_pcm_hw_params_set_format)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
static int (*_snd_pcm_hw_params_set_channels)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
static int (*_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
static int (*_snd_pcm_hw_params_set_period_time_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
static int (*_snd_pcm_hw_params_set_periods)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
static int (*_snd_pcm_hw_params_set_periods_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
static int (*_snd_pcm_hw_params_get_period_size_min)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
static int (*_snd_pcm_hw_params)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
static int (*_snd_pcm_sw_params_current)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
static int (*_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
static int (*_snd_pcm_sw_params_set_stop_threshold)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
static int (*_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
static int (*_snd_pcm_sw_params)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
static int (*_snd_pcm_start)(snd_pcm_t *pcm);
static int (*_snd_pcm_prepare)(snd_pcm_t *pcm);
static int (*_snd_pcm_resume)(snd_pcm_t *pcm);
static int (*_snd_pcm_wait)(snd_pcm_t *pcm, int timeout);
static snd_pcm_state_t (*__snd_pcm_state)(snd_pcm_t *pcm);
static snd_pcm_sframes_t (*_snd_pcm_avail)(snd_pcm_t *pcm);
static snd_pcm_sframes_t (*_snd_pcm_avail_update)(snd_pcm_t *pcm);
static int (*_snd_pcm_mmap_begin)(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames);
static snd_pcm_sframes_t (*_snd_pcm_mmap_commit)(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames);
static snd_pcm_sframes_t (*_snd_pcm_writei)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);

typedef struct {
	void **symbol;
	const char *name;
} sym_t;

sym_t t_list[] = {
#ifdef USE_SPINLOCK
	{ (void**)&_pthread_spin_init, "pthread_spin_init" },
	{ (void**)&_pthread_spin_destroy, "pthread_spin_destroy" },
	{ (void**)&_pthread_spin_lock, "pthread_spin_lock" },
	{ (void**)&_pthread_spin_unlock, "pthread_spin_unlock" },
#else
	{ (void**)&_pthread_mutex_init, "pthread_mutex_init" },
	{ (void**)&_pthread_mutex_destroy, "pthread_mutex_destroy" },
	{ (void**)&_pthread_mutex_lock, "pthread_mutex_lock" },
	{ (void**)&_pthread_mutex_unlock, "pthread_mutex_unlock" },
#endif
	{ (void**)&_pthread_join, "pthread_join" },
	{ (void**)&_pthread_create, "pthread_create" },
	{ (void**)&_pthread_exit, "pthread_exit" }
};

sym_t a_list[] = {
	{ (void**)&_snd_strerror, "snd_strerror" },
	{ (void**)&_snd_pcm_open, "snd_pcm_open" },
	{ (void**)&_snd_pcm_drain, "snd_pcm_drain" },
	{ (void**)&_snd_pcm_drop, "snd_pcm_drop" },
	{ (void**)&_snd_pcm_close, "snd_pcm_close" },
	{ (void**)&_snd_pcm_hw_params_sizeof, "snd_pcm_hw_params_sizeof" },
	{ (void**)&_snd_pcm_sw_params_sizeof, "snd_pcm_sw_params_sizeof" },
	{ (void**)&_snd_async_handler_get_pcm, "snd_async_handler_get_pcm" },
	{ (void**)&_snd_async_add_pcm_handler, "snd_async_add_pcm_handler" },
	{ (void**)&_snd_pcm_hw_params_any, "snd_pcm_hw_params_any" },
	{ (void**)&_snd_pcm_hw_params_set_rate_resample, "snd_pcm_hw_params_set_rate_resample" },
	{ (void**)&_snd_pcm_hw_params_set_access, "snd_pcm_hw_params_set_access" },
	{ (void**)&_snd_pcm_hw_params_set_format, "snd_pcm_hw_params_set_format" },
	{ (void**)&_snd_pcm_hw_params_set_channels, "snd_pcm_hw_params_set_channels" },
	{ (void**)&_snd_pcm_hw_params_set_rate_near, "snd_pcm_hw_params_set_rate_near" },
	{ (void**)&_snd_pcm_hw_params_set_period_time_near, "snd_pcm_hw_params_set_period_time_near" },
	{ (void**)&_snd_pcm_hw_params_set_periods, "snd_pcm_hw_params_set_periods" },
	{ (void**)&_snd_pcm_hw_params_set_periods_near, "snd_pcm_hw_params_set_periods_near" },
	{ (void**)&_snd_pcm_hw_params_get_period_size_min, "snd_pcm_hw_params_get_period_size_min" },
	{ (void**)&_snd_pcm_hw_params, "snd_pcm_hw_params" },
	{ (void**)&_snd_pcm_sw_params_current, "snd_pcm_sw_params_current" },
	{ (void**)&_snd_pcm_sw_params_set_start_threshold, "snd_pcm_sw_params_set_start_threshold" },
	{ (void**)&_snd_pcm_sw_params_set_stop_threshold, "snd_pcm_sw_params_set_stop_threshold" },
	{ (void**)&_snd_pcm_sw_params_set_avail_min, "snd_pcm_sw_params_set_avail_min" },
	{ (void**)&_snd_pcm_sw_params, "snd_pcm_sw_params" },
	{ (void**)&_snd_pcm_start, "snd_pcm_start" },
	{ (void**)&_snd_pcm_prepare, "snd_pcm_prepare" },
	{ (void**)&_snd_pcm_resume, "snd_pcm_resume" },
	{ (void**)&_snd_pcm_wait, "snd_pcm_wait" },
	{ (void**)&__snd_pcm_state, "snd_pcm_state" },
	{ (void**)&_snd_pcm_avail, "snd_pcm_avail" },
	{ (void**)&_snd_pcm_avail_update, "snd_pcm_avail_update" },
	{ (void**)&_snd_pcm_mmap_begin, "snd_pcm_mmap_begin" },
	{ (void**)&_snd_pcm_mmap_commit, "snd_pcm_mmap_commit" },
	{ (void**)&_snd_pcm_writei, "snd_pcm_writei" }
};

#endif

qboolean alsa_used = qfalse; /* will be checked in oss engine */

static pthread_t thread;
#ifdef USE_SPINLOCK
static pthread_spinlock_t lock;
#else
static pthread_mutex_t mutex;
#endif

static qboolean snd_inited = qfalse;

/* we will use static dma buffer */
static unsigned char buffer[ BUFFER_SIZE ];
static unsigned int periods;
static unsigned int period_time;  // wishable latency
static snd_pcm_t *handle;


static volatile qboolean snd_loop;
static volatile qboolean snd_async;

static snd_pcm_uframes_t period_size;
static snd_pcm_sframes_t buffer_pos;	// buffer position, in mono samples
static int buffer_sz;					// buffers size, in bytes
static int frame_sz;					// frame size, in bytes

static void async_proc( snd_async_handler_t *ahandler );
static void thread_proc_mmap( void );
static void thread_proc_direct( void );


void Snd_Memset( void* dest, const int val, const size_t count )
{
    Com_Memset( dest, val, count );
}


void SNDDMA_BeginPainting( void )
{

}


void SNDDMA_Submit( void )
{

}


static void UnloadLibs( void )
{
#ifndef USE_ALSA_STATIC
	Sys_UnloadLibrary( a_lib );
	Sys_UnloadLibrary( t_lib );
	a_lib = NULL;
	t_lib = NULL;
#endif
}

typedef enum {
	SND_MODE_ASYNC,
	SND_MODE_MMAP,
	SND_MODE_DIRECT
} smode_t;

static qboolean setup_ALSA( smode_t mode )
{
	snd_async_handler_t *ahandler;
	snd_pcm_hw_params_t *hwparams;
	snd_pcm_sw_params_t *swparams;
	unsigned int speed, rrate;
	int err, dir, bps, channels;
	qboolean use_mmap;
	int i;

	if ( snd_inited == qtrue )
	{
		return qtrue;
	}

	alsa_used = qfalse;
	snd_async = qfalse;
	use_mmap = qfalse;

#ifndef USE_ALSA_STATIC
	if ( t_lib == NULL )
	{
		t_lib = Sys_LoadLibrary( "libpthread.so.0" );
		if ( t_lib == NULL )
		{
			t_lib = Sys_LoadLibrary( "libpthread.so" );
		}
		if ( t_lib == NULL )
		{
			Com_Printf( "Error loading pthread library, disabling ALSA support.\n" );
			return qfalse;
		}
	}

	for ( i = 0 ; i < ARRAY_LEN( t_list ) ; i++ )
	{
		*t_list[i].symbol = Sys_LoadFunction( t_lib, t_list[i].name );
		if ( *t_list[i].symbol == NULL )
		{
			Com_Printf( "Couldn't find '%s' symbol, disabling ALSA support.\n",
				t_list[i].name );
			UnloadLibs();
			return qfalse;
		}
	}

	if ( a_lib == NULL )
	{
		a_lib = Sys_LoadLibrary( "libasound.so.2" );
		if ( a_lib == NULL )
		{
			a_lib = Sys_LoadLibrary( "libasound.so" );
		}
		if ( a_lib == NULL )
		{
			Com_Printf( "Error loading ALSA library.\n" );
			goto __fail;
		}
	}

	for ( i = 0 ; i < ARRAY_LEN( a_list ) ; i++ )
	{
		*a_list[i].symbol = Sys_LoadFunction( a_lib, a_list[i].name );
		if ( *a_list[i].symbol == NULL )
		{
			Com_Printf( "Couldn't find '%s' symbol, disabling ALSA support.\n",
				a_list[i].name );
			goto __fail;
		}
	}
#endif

	err = _snd_pcm_open( &handle, s_device->string, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK );
	if ( err < 0 )
	{
		Com_Printf( "Playback device open error: %s\n", _snd_strerror( err ) );
		goto __fail;
	}

	hwparams = alloca( _snd_pcm_hw_params_sizeof() );
	if ( hwparams == NULL )
	{
		Com_Printf( "Error allocating %i bytes of memory for hwparams\n", (int)_snd_pcm_hw_params_sizeof() );
		goto __fail;
	}

	swparams = alloca( _snd_pcm_sw_params_sizeof() );
	if ( swparams == NULL )
	{
		Com_Printf( "Error allocating %i bytes of memory for swparams\n", (int)_snd_pcm_sw_params_sizeof() );
		goto __fail;
	}

	err = _snd_pcm_hw_params_any( handle, hwparams );
	if ( err < 0 )
	{
		Com_Printf( "Broken configuration for playback: " \
			"no configurations available: %s\n", _snd_strerror( err ) );
		goto __fail;
	}

	switch ( mode )
	{
		case SND_MODE_ASYNC:
					err = _snd_async_add_pcm_handler( &ahandler, handle, async_proc, NULL );
					if ( err < 0 )
						goto __fail;
					/* set the interleaved read/write format */
					err = _snd_pcm_hw_params_set_access( handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED );
					snd_async = qtrue;
					break;
		case SND_MODE_MMAP:
					err = _snd_pcm_hw_params_set_access( handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED );
					use_mmap = qtrue;
					break;
		case SND_MODE_DIRECT:
					err = _snd_pcm_hw_params_set_access( handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED );
					break;
	}

	if ( err < 0 )
	{
		Com_Printf( "Access type not available for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

	/* set hw resampling */
	err = _snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
	if ( err < 0 )
	{
		Com_Printf( "Resampling setup failed for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

	/* set the sample format */
	bps = 16;
	err = _snd_pcm_hw_params_set_format( handle, hwparams, SND_PCM_FORMAT_S16 );
	if ( err < 0 )
	{
		bps = 8;
		err = _snd_pcm_hw_params_set_format( handle, hwparams, SND_PCM_FORMAT_S8 );
	}
	if ( err < 0 )
	{
		Com_Printf( "Sample format not available for playback: " \
			"%s\n", _snd_strerror( err ) );
		goto __fail;
	}

	channels = 2;
	/* set the count of channels */
	err = _snd_pcm_hw_params_set_channels( handle, hwparams, channels );
	if ( err < 0 )
	{
		channels = 1;
		err = _snd_pcm_hw_params_set_channels( handle, hwparams, channels );
	}
	
	if ( err < 0 )
	{
		err = _snd_pcm_hw_params_set_channels( handle, hwparams, channels );
		Com_Printf( "Channels count (%i) not available for playbacks: %s\n",
			channels, _snd_strerror( err ) );
		goto __fail;
	}

	switch ( s_khz->integer )
	{
		case 48: speed = 48000; break;
		case 44: speed = 44100; break;
		case 11: speed = 11025; break;
		case  8: speed =  8000; break;
		case 22:
		default: speed = 22050; break;
	};

	rrate = speed;
	err = _snd_pcm_hw_params_set_rate_near( handle, hwparams, &rrate, 0 );
	if ( err < 0 )
	{
		Com_Printf("Rate %iHz not available for playback: %s\n",
			speed, _snd_strerror( err ) );
		goto __fail;
	}
	if ( rrate != speed )
	{
		Com_Printf( "Rate doesn't match (requested %iHz, get %iHz)\n",
			speed, err );
		goto __fail;
	}

	/* set the period time */
	period_time = PERIOD_TIME;
	err = _snd_pcm_hw_params_set_period_time_near( handle, hwparams,
		&period_time, &dir );
	if ( err < 0 )
	{
		Com_Printf( "Unable to set period time %i for playback: %s\n",
			period_time, _snd_strerror( err ) );
		goto __fail;
	}

	periods = NUM_PERIODS;
	err = _snd_pcm_hw_params_set_periods_near( handle, hwparams, &periods, &dir );
	if ( err < 0 )
	{
		Com_Printf( "Unable to set periods (%i): %s",
			periods, _snd_strerror( err ) );
		goto __fail;
	}

	/* get period size */
	err = _snd_pcm_hw_params_get_period_size_min( hwparams, &period_size, &dir );
	if ( err < 0 )
	{
		Com_Printf( "Unable to get period size for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

	/* write the parameters to device */
	err = _snd_pcm_hw_params( handle, hwparams );
	if ( err < 0 )
	{
		Com_Printf( "Unable to set hw params for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

	err = _snd_pcm_sw_params_current( handle, swparams );
	if ( err < 0 )
	{
		Com_Printf( "Unable to determine current swparams for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

	err = _snd_pcm_sw_params_set_start_threshold( handle, swparams, 1 /*period_size*/ );
	if ( err < 0 )
	{
		Com_Printf( "Unable to set start threshold mode for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

	/* disable XRUN */
	err = _snd_pcm_sw_params_set_stop_threshold( handle, swparams, -1 );
	if ( err < 0 )
	{
		Com_Printf( "Unable to set stop threshold for playback: " \
			"%s\n", _snd_strerror( err ) );
		goto __fail;
	}

	err = _snd_pcm_sw_params_set_avail_min( handle, swparams, period_size );
	if ( err < 0 )
	{
		Com_Printf( "Unable to set avail min for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

	err = _snd_pcm_sw_params( handle, swparams );
	if ( err < 0 )
	{
		Com_Printf( "Unable to set sw params for playback: %s\n",
			_snd_strerror( err ) );
		goto __fail;
	}

#ifdef INT
	Com_Printf( "period_time=%i\n", period_time );
	Com_Printf( "period_size=%i\n", (int)period_size );
#endif

	dma.isfloat = qfalse;
	dma.channels = channels;
	dma.speed = speed;
	dma.samples = sizeof( buffer ) * 8 / bps;
	dma.fullsamples = dma.samples / dma.channels;
	dma.samplebits = bps;
	dma.submission_chunk = 1;
	dma.buffer = buffer;

	buffer_pos = 0; // in monosamples
	buffer_sz = dma.samples * dma.samplebits / 8; // buffer size, in bytes
	frame_sz = dma.channels * dma.samplebits / 8; // frame size, in bytes

	memset( buffer, 0, sizeof( buffer ) );

	snd_inited = qtrue;

#ifdef USE_SPINLOCK
	_pthread_spin_init( &lock, 0 );
#else
	_pthread_mutex_init( &mutex, NULL );
#endif

	if ( snd_async )
	{
		err = _snd_pcm_writei( handle, dma.buffer, period_size * 2 );
		if ( err < 0 )
		{
			Com_Printf( S_COLOR_YELLOW "ALSA initial write error: %s\n", _snd_strerror( err ) );
			goto __fail;
		}
		if ( err != period_size * 2 )
		{
			Com_Printf( S_COLOR_YELLOW "ALSA initial write error: written %i expected %li\n", err,
				period_size * 2 );
			goto __fail;
		}
		if ( __snd_pcm_state( handle ) == SND_PCM_STATE_PREPARED )
		{
			err = _snd_pcm_start( handle );
			if ( err < 0 )
			{
				Com_Printf( "ALSA start error: %s\n", _snd_strerror( err ) );
				goto __fail;
			}
		}
	}
	else
	{
		snd_loop = qtrue;

		 /* will be unlocked after thread creation */
#ifdef USE_SPINLOCK
		_pthread_spin_lock( &lock );
#else
		_pthread_mutex_lock( &mutex );
#endif
	
		if ( use_mmap )
			err = _pthread_create( &thread, NULL, (void*)&thread_proc_mmap, NULL );
		else
			err = _pthread_create( &thread, NULL, (void*)&thread_proc_direct, NULL );

		if ( err != 0 )
		{
			Com_Printf( "Error creating sound thread (%i)\n", err );
#ifdef USE_SPINLOCK
			_pthread_spin_unlock( &lock );
#else
			_pthread_mutex_unlock( &mutex );
#endif
			goto __fail;
		}

		/* wait for thread creation */
#ifdef USE_SPINLOCK
		_pthread_spin_lock( &lock );
		_pthread_spin_unlock( &lock );
#else
		_pthread_mutex_lock( &mutex );
		_pthread_mutex_unlock( &mutex );
#endif
	}

	alsa_used = qtrue;
	return qtrue;

__fail:
	_snd_pcm_close( handle );
	UnloadLibs();
	alsa_used = qfalse;
	return qfalse;
}


qboolean SNDDMA_Init( void )
{
	//Com_Printf( "...trying ASYNC mode\n" );
	//if ( !setup_ALSA( SND_MODE_ASYNC ) )
	{
		Com_Printf( "...trying MMAP mode\n" );
		if ( !setup_ALSA( SND_MODE_MMAP ) )
		{
			Com_Printf( "...trying DIRECT mode\n" );
			if ( !setup_ALSA( SND_MODE_DIRECT ) )
			{
				Com_Printf( "...ALSA setup failed\n" );
				return qfalse;
			}
		}
	}
	return qtrue;
}


void SNDDMA_Shutdown( void )
{
	if ( snd_inited == qfalse )
		return;

	if ( !snd_async )
	{
		snd_loop = qfalse;

		/* wait for thread loop exit */
		_pthread_join( thread, NULL );
	}
	else
	{
		_snd_pcm_drain( handle );
	}

	snd_async = qfalse;
	snd_inited = qfalse;

	_snd_pcm_close( handle );

#ifdef USE_SPINLOCK
	_pthread_spin_destroy( &lock );
#else
	_pthread_mutex_destroy( &mutex );
#endif

	UnloadLibs();
}

#define CASE_STR(x) case (x): s = #x; break;

static void print_state( snd_pcm_state_t state )
{
	const char *s;

	switch( state )
	{
	CASE_STR(SND_PCM_STATE_OPEN);
	CASE_STR(SND_PCM_STATE_SETUP);
	CASE_STR(SND_PCM_STATE_PREPARED);
	CASE_STR(SND_PCM_STATE_RUNNING);
	CASE_STR(SND_PCM_STATE_XRUN);
	CASE_STR(SND_PCM_STATE_DRAINING);
	CASE_STR(SND_PCM_STATE_PAUSED);
	CASE_STR(SND_PCM_STATE_SUSPENDED);
	CASE_STR(SND_PCM_STATE_DISCONNECTED);
	default: s = "SND_PCM_STATE_PRIVATE1"; break;
	};

	Com_Printf( "%s\n", s );
}

static int xrun_recovery( snd_pcm_t *handle, int err )
{
	if ( err == -EPIPE ) /* underrun */
	{
		err = _snd_pcm_prepare( handle );
		if ( err < 0 )
		{
			fprintf( stderr, "Can't recovery from underrun, prepare failed: %s\n",
				_snd_strerror( err ) );
			return err;
		}
		return 0;
	}
	else if ( err == -ESTRPIPE )
	{
		int tries = 0;
		struct timespec req;
		req.tv_sec = period_time / 1000000;
		req.tv_nsec = ( period_time % 1000000 ) * 1000;

		/* wait until the suspend flag is released */
		while ( ( err = _snd_pcm_resume( handle ) ) == -EAGAIN )
		{
			nanosleep( &req, NULL );
			if ( tries++ < 16 )
			{
				break;
			}
		}
		if ( err < 0 )
		{
			err = _snd_pcm_prepare( handle );
			if ( err < 0 )
			{
				fprintf( stderr, "Can't recovery from suspend, prepare failed: %s\n",
					_snd_strerror( err ) );
				return err;
			}
		}
		return 0;
	}
//	Com_Printf( "error: %i\n", err );
	return err;
}


static int restore_transfer( void )
{
	snd_pcm_state_t state;
	int err;

	state = __snd_pcm_state( handle );

	if ( state == SND_PCM_STATE_XRUN )
	{
		//print_state( state );
		err = xrun_recovery( handle, -EPIPE );
		if ( err < 0 )
		{
			fprintf( stderr, "XRUN recovery failed: %s\n",
				_snd_strerror( err ) );
			return err;
		}
		buffer_pos = 0;
	}
	else if ( state == SND_PCM_STATE_SUSPENDED )
	{
		print_state( state );
		err = xrun_recovery( handle, -ESTRPIPE );
		if ( err < 0 )
		{
			fprintf( stderr, "SUSPEND recovery failed: %s\n",
				_snd_strerror( err ) );
			return err;
		}
		buffer_pos = 0;
	}
	return 0;
}


/*
==============
SNDDMA_GetDMAPos
return the current sample position (in mono samples read)
inside the recirculating dma buffer, so the mixing code will know
how many sample are required to fill it up.
===============
*/
int SNDDMA_GetDMAPos( void )
{
	int samples;

	if ( snd_inited == qfalse )
		return 0;

#ifdef USE_SPINLOCK
	_pthread_spin_lock( &lock );
#else
	_pthread_mutex_lock( &mutex );
#endif

	if ( dma.samples )
		samples = (buffer_pos) % dma.samples;
	else
		samples = 0;

#ifdef USE_SPINLOCK
	_pthread_spin_unlock( &lock );
#else
	_pthread_mutex_unlock( &mutex );
#endif

	return samples;
}


static void thread_proc_mmap( void )
{
	const snd_pcm_channel_area_t *areas;
	snd_pcm_sframes_t commitres;
	snd_pcm_uframes_t frames;
	snd_pcm_uframes_t offset;
	snd_pcm_sframes_t avail;
	snd_pcm_state_t state;
	unsigned char *addr;
	int sz0, sz1;
	int err, p;
	pid_t thread_id;

	// adjust thread priority
	thread_id = syscall( SYS_gettid );
	setpriority( PRIO_PROCESS, thread_id, -10 );

	// thread is running now
#ifdef USE_SPINLOCK
	_pthread_spin_unlock( &lock );
#else
	_pthread_mutex_unlock( &mutex );
#endif

	while ( snd_loop )
	{
		if ( restore_transfer() < 0 )
			break;

		_snd_pcm_wait( handle, period_time );
		avail = _snd_pcm_avail_update( handle );

		if ( avail < 0 )
			continue;

		state = __snd_pcm_state( handle );

		if ( state == SND_PCM_STATE_PREPARED )
		{
			_snd_pcm_start( handle );
			avail = _snd_pcm_avail( handle ); // sync with hardware
			buffer_pos = 0;
		}

		if ( avail <= 0 )
			continue;

#ifdef USE_SPINLOCK
		_pthread_spin_lock( &lock );
#else
		_pthread_mutex_lock( &mutex );
#endif
		frames = avail;

		err = _snd_pcm_mmap_begin( handle, &areas, &offset, &frames );

		if ( err < 0 )
		{
			if ( (err = xrun_recovery( handle, err ) ) < 0 )
			{
				fprintf( stderr, "MMAP begin error: %s\n",
					_snd_strerror( err ) );
#ifdef USE_SPINLOCK
				_pthread_spin_unlock( &lock );
#else
				_pthread_mutex_unlock( &mutex );
#endif
				continue;
			}
		}

		addr = areas[0].addr;
		addr += offset * frame_sz;
		sz0 = frames * frame_sz;

		p = buffer_pos * (dma.samplebits / 8);
		while ( sz0 > 0 )
		{
			sz1 = sz0;
			if ( p + sz1 > buffer_sz )
				sz1 = buffer_sz - p;
			memcpy( addr, dma.buffer + p, sz1 );
			p = (p + sz1) % buffer_sz;
			addr += sz1;
			sz0 -= sz1;
		}
		buffer_pos = p / ( dma.samplebits / 8 );

		commitres = _snd_pcm_mmap_commit( handle, offset, frames );
		if ( commitres < 0 || commitres != frames )
		{
			if ( ( err = xrun_recovery( handle, commitres >= 0 ? -EPIPE : commitres ) ) < 0 )
			{
				fprintf( stderr, "MMAP commit error: %s\n", _snd_strerror( err ) );
#ifdef USE_SPINLOCK
				_pthread_spin_unlock( &lock );
#else
				_pthread_mutex_unlock( &mutex );
#endif
				break;
			}
		}
#ifdef USE_SPINLOCK
		_pthread_spin_unlock( &lock );
#else
		_pthread_mutex_unlock( &mutex );
#endif
	}

	_snd_pcm_drop( handle );

	_pthread_exit( 0 );
}


static void thread_proc_direct( void )
{
	snd_pcm_uframes_t size;
	snd_pcm_uframes_t pos;
	snd_pcm_sframes_t avail, x;
	snd_pcm_state_t state;
	pid_t thread_id;
	int err;

	// adjust thread priority
	thread_id = syscall( SYS_gettid );
	setpriority( PRIO_PROCESS, thread_id, -10 );

	/* buffer size in full samples */
	size = dma.samples / dma.channels;

	// thread is running now
#ifdef USE_SPINLOCK
	_pthread_spin_unlock( &lock );
#else
	_pthread_mutex_unlock( &mutex );
#endif

	while ( snd_loop )
	{
		if ( restore_transfer() < 0 )
			break;

		_snd_pcm_wait( handle, period_time );
		avail = _snd_pcm_avail_update( handle );

		if ( avail < 0 )
			continue;

		if ( snd_loop == qfalse )
			break;

		state = __snd_pcm_state( handle );

		if ( state == SND_PCM_STATE_PREPARED )
		{
			_snd_pcm_start( handle );
			avail = _snd_pcm_avail( handle ); // sync with hardware
			//_snd_pcm_writei( handle, dma.buffer, size );
			buffer_pos = 0;
		}

		if ( avail <= 0 )
			continue;

#ifdef USE_SPINLOCK
		_pthread_spin_lock( &lock );
#else
		_pthread_mutex_lock( &mutex );
#endif

		// buffer position in full samples
		pos = buffer_pos / dma.channels;

		while ( avail > 0 ) {
			x = avail;
			if ( pos + x > size ) {
				x = size - pos;
			}
			err = _snd_pcm_writei( handle, dma.buffer + pos * frame_sz, x );
			if ( err >= 0 ) {
				pos = (pos + x) % size;
				avail -= x;
			} else {
				avail = 0;
			}
		}

		// buffer pos in mono samples again
		buffer_pos = pos * dma.channels;

#ifdef USE_SPINLOCK
		_pthread_spin_unlock( &lock );
#else
		_pthread_mutex_unlock( &mutex );
#endif
	}

	_snd_pcm_drop( handle );

	_pthread_exit( 0 );
}


static void async_proc( snd_async_handler_t *ahandler )
{
	snd_pcm_sframes_t avail;
	snd_pcm_sframes_t x;
	snd_pcm_uframes_t pos;
	snd_pcm_uframes_t size;
	int err;

	if ( !snd_async || !dma.samples )
		return;

	if ( restore_transfer() < 0 )
		return;

	size = dma.samples / dma.channels;

	while ( ( avail = _snd_pcm_avail_update( handle ) ) >= period_size )
	{
#ifdef USE_SPINLOCK
		_pthread_spin_lock( &lock );
#else
		_pthread_mutex_lock( &mutex );
#endif
		pos = buffer_pos / dma.channels; // buffer position in full samples

		while ( avail >= period_size )
		{
			if ( avail > period_size )
				x = period_size;
			else
				x = avail;

			if ( pos + x > size )
				x = size - pos;

			err = _snd_pcm_writei( handle, dma.buffer + pos * frame_sz, x );
			if ( err >= 0 )
			{
				pos = (pos + x) % size;
				avail -= x;
			}
			else
			{
				fprintf( stderr, "ALSA write error: %s\n", _snd_strerror( err ) );
				break;
			}
		}

		buffer_pos = pos * dma.channels; // buffer pos in mono samples again

#ifdef USE_SPINLOCK
		_pthread_spin_unlock( &lock );
#else
		_pthread_mutex_unlock( &mutex );
#endif
	}
}

#else // legacy OSS code path

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/wait.h>
#ifdef __linux__
#include <linux/soundcard.h>
#endif
#ifdef __FreeBSD__
#include <sys/soundcard.h>
#endif
#include <stdio.h>

#include "../client/snd_local.h"
#include "../qcommon/q_shared.h"

static qboolean snd_inited = qfalse;
static int audio_fd;
static int map_size;

static cvar_t *snddevice;

/* Some devices may work only with 48000 */
static int tryrates[] = { 22050, 11025, 44100, 48000, 8000 };

void Snd_Memset( void* dest, const int val, const size_t count )
{
	Com_Memset( dest, val, count );
}

qboolean SNDDMA_Init( void )
{
	cvar_t *sndbits;
	cvar_t *sndspeed;
	cvar_t *sndchannels;

	struct audio_buf_info info;
	int rc;
	int fmt;
	int tmp;
	int i;
	int caps;

	if (snd_inited)
		return qtrue;

	sndbits = Cvar_Get("sndbits", "16", CVAR_ARCHIVE_ND | CVAR_LATCH);
	Cvar_SetDescription( sndbits, "Bit resolution." );
	sndspeed = Cvar_Get("sndspeed", "0", CVAR_ARCHIVE_ND | CVAR_LATCH);
	sndchannels = Cvar_Get("sndchannels", "2", CVAR_ARCHIVE_ND |  CVAR_LATCH);
	Cvar_SetDescription( sndchannels, "Number of channels." );
	snddevice = Cvar_Get("snddevice", "/dev/dsp", CVAR_ARCHIVE_ND | CVAR_LATCH);

	map_size = 0;

	// open /dev/dsp, confirm capability to mmap, and get size of dma buffer
	if (!audio_fd) {
		audio_fd = open(snddevice->string, O_RDWR);

		if (audio_fd < 0) {
			perror(snddevice->string);
			Com_Printf("Could not open %s\n", snddevice->string);
			return qfalse;
		}
	}

	if (ioctl(audio_fd, SNDCTL_DSP_GETCAPS, &caps) == -1) {
		perror(snddevice->string);
		Com_Printf("Sound driver too old\n");
		close(audio_fd);
		return qfalse;
	}

	if (!(caps & DSP_CAP_TRIGGER) || !(caps & DSP_CAP_MMAP)) {
		Com_Printf("Sorry but your soundcard can't do this\n");
		close(audio_fd);
		return qfalse;
	}

	/* SNDCTL_DSP_GETOSPACE moved to be called later */

	// set sample bits & speed
	dma.samplebits = (int)sndbits->value;
	if (dma.samplebits != 16 && dma.samplebits != 8) {
		ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &fmt);
		if (fmt & AFMT_S16_LE) 
			dma.samplebits = 16;
		else if (fmt & AFMT_U8) 
			dma.samplebits = 8;
	}

	dma.speed = (int)sndspeed->value;
	if (!dma.speed) {
		for (i=0 ; i<sizeof(tryrates)/4 ; i++)
			if (!ioctl(audio_fd, SNDCTL_DSP_SPEED, &tryrates[i])) 
				break;
		dma.speed = tryrates[i];
	}

	dma.channels = (int)sndchannels->value;
	if (dma.channels < 1 || dma.channels > 2)
		dma.channels = 2;

	/*  mmap() call moved forward */

	tmp = 0;
	if (dma.channels == 2)
		tmp = 1;
	rc = ioctl(audio_fd, SNDCTL_DSP_STEREO, &tmp);
	if (rc < 0) {
		perror(snddevice->string);
		Com_Printf("Could not set %s to stereo=%d", snddevice->string, dma.channels);
		close(audio_fd);
		return qfalse;
	}

	if (tmp)
		dma.channels = 2;
	else
		dma.channels = 1;

	rc = ioctl(audio_fd, SNDCTL_DSP_SPEED, &dma.speed);
	if (rc < 0) {
		perror(snddevice->string);
		Com_Printf("Could not set %s speed to %d", snddevice->string, dma.speed);
		close(audio_fd);
		return qfalse;
	}

	if (dma.samplebits == 16) {
		rc = AFMT_S16_LE;
		rc = ioctl(audio_fd, SNDCTL_DSP_SETFMT, &rc);
		if (rc < 0) {
			perror(snddevice->string);
			Com_Printf("Could not support 16-bit data.  Try 8-bit.\n");
			close(audio_fd);
			return qfalse;
		}
	} else if (dma.samplebits == 8) {
		rc = AFMT_U8;
		rc = ioctl(audio_fd, SNDCTL_DSP_SETFMT, &rc);
		if (rc < 0) {
			perror(snddevice->string);
			Com_Printf("Could not support 8-bit data.\n");
			close(audio_fd);
			return qfalse;
		}
	} else {
		perror(snddevice->string);
		Com_Printf("%d-bit sound not supported.", dma.samplebits);
		close(audio_fd);
		return qfalse;
	}

	if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info)==-1) {
		perror("GETOSPACE");
		Com_Printf("Um, can't do GETOSPACE?\n");
		close(audio_fd);
		return qfalse;
	}

	dma.samples = info.fragstotal * info.fragsize / (dma.samplebits/8);
	dma.submission_chunk = 1;

	map_size = info.fragstotal * info.fragsize;

	// memory map the dma buffer

	// TTimo 2001/10/08 added PROT_READ to the mmap
	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371
	// checking Alsa bug, doesn't allow dma alloc with PROT_READ?

	if (dma.buffer == NULL)
		dma.buffer = (unsigned char *) mmap(NULL, map_size, PROT_WRITE|PROT_READ,
			MAP_FILE|MAP_SHARED, audio_fd, 0);

	if (dma.buffer == MAP_FAILED)
	{
		Com_Printf("Could not mmap dma buffer PROT_WRITE|PROT_READ\n");
		Com_Printf("trying mmap PROT_WRITE (with associated better compatibility / less performance code)\n");
		dma.buffer = (unsigned char *) mmap(NULL, map_size, PROT_WRITE,
			MAP_FILE|MAP_SHARED, audio_fd, 0);
	}

	if (dma.buffer == MAP_FAILED) {
		perror(snddevice->string);
		Com_Printf("Could not mmap %s\n", snddevice->string);
		dma.buffer = NULL;
		close(audio_fd);
		return qfalse;
	}

	// toggle the trigger & start her up
	tmp = 0;
	rc  = ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp);
	if (rc < 0) {
		perror(snddevice->string);
		Com_Printf("Could not toggle.\n");
		munmap(dma.buffer, map_size);
		close(audio_fd);
		return qfalse;
	}

	tmp = PCM_ENABLE_OUTPUT;
	rc = ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp);
	if (rc < 0) {
		perror(snddevice->string);
		Com_Printf("Could not toggle.\n");
		munmap(dma.buffer, map_size);
		close(audio_fd);
		return qfalse;
	}

	snd_inited = qtrue;
	return qtrue;
}


int SNDDMA_GetDMAPos( void )
{
	struct count_info count;

	if (!snd_inited)
		return 0;

	if (ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &count) == -1)
	{
		perror(snddevice->string);
		Com_Printf("Uh, sound dead.\n");
		close(audio_fd);
		snd_inited = qfalse;
		return 0;
	}

	return count.ptr / (dma.samplebits / 8);
}


void SNDDMA_Shutdown( void )
{
	if (dma.buffer)
	{
		munmap(dma.buffer, map_size);
		dma.buffer = NULL;
	}

	if (audio_fd)
	{
		close(audio_fd);
		audio_fd = 0;
	}

	snd_inited = qfalse;
}


/*
==============
SNDDMA_Submit

Send sound to device if buffer isn't really the dma buffer
===============
*/
void SNDDMA_Submit( void )
{
}


void SNDDMA_BeginPainting( void )
{
}

#endif // !defined (__linux__)
