mirror of
https://github.com/Q3Rally-Team/rallyunlimited-engine.git
synced 2024-11-22 04:12:11 +00:00
1368 lines
34 KiB
C
1368 lines
34 KiB
C
|
#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 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;
|
||
|
/* wait until the suspend flag is released */
|
||
|
while ( ( err = _snd_pcm_resume( handle ) ) == -EAGAIN )
|
||
|
{
|
||
|
usleep( period_time );
|
||
|
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__)
|