medfall

A super great game engine
Log | Files | Refs

linux_audio_output.cc (4925B)


      1 #include <stdio.h>
      2 #include <dlfcn.h>
      3 #include <err.h>
      4 
      5 #include <alsa/asoundlib.h>
      6 
      7 #include "intrinsics.h"
      8 #include "log.h"
      9 #include "platform_audio_output.h"
     10 #include "platform_library.h"
     11 #include "platform_thread.h"
     12 #include "platform_atomic.h"
     13 
     14 // copied from alsa headers
     15 static int ( *_snd_lib_error_set_handler )( snd_lib_error_handler_t handler ) = NULL;
     16 static const char * ( *_snd_strerror )( int errnum ) = NULL;
     17 static int ( *_snd_pcm_open )( snd_pcm_t ** pcm, const char * name, snd_pcm_stream_t stream, int mode ) = NULL;
     18 static int ( *_snd_pcm_set_params )( snd_pcm_t * pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency ) = NULL;
     19 static snd_pcm_sframes_t ( *_snd_pcm_writen )( snd_pcm_t * pcm, void ** bufs, snd_pcm_uframes_t size ) = NULL;
     20 static int ( *_snd_pcm_recover )( snd_pcm_t * pcm, int err, int silent ) = NULL;
     21 static int ( *_snd_pcm_close )( snd_pcm_t * pcm ) = NULL;
     22 
     23 static Library alsa_lib = NULL;
     24 
     25 static void audio_error_handler( const char * file, int line, const char * function, int err, const char * fmt, ... ) {
     26 	printf( "error in %s at %s:%d\n", file, function, line );
     27 
     28 	va_list args;
     29 	va_start( args, fmt );
     30 	vprintf( fmt, args );
     31 	va_end( args );
     32 	printf( "\n" );
     33 }
     34 
     35 void audio_output_init() {
     36 	alsa_lib = library_open( "libasound.so" );
     37 	if( alsa_lib == NULL ) {
     38 		FATAL( "Couldn't open libasound.so: {}", dlerror() );
     39 	}
     40 
     41 	_snd_lib_error_set_handler = ( int ( * )( snd_lib_error_handler_t ) ) library_function( alsa_lib, "snd_lib_error_set_handler" );
     42 	_snd_strerror = ( const char * ( * )( int ) ) library_function( alsa_lib, "snd_strerror" );
     43 	_snd_pcm_open = ( int ( * ) ( snd_pcm_t **, const char *, snd_pcm_stream_t, int ) ) library_function( alsa_lib, "snd_pcm_open" );
     44 	_snd_pcm_set_params = ( int ( * ) ( snd_pcm_t *, snd_pcm_format_t, snd_pcm_access_t, unsigned int, unsigned int, int, unsigned int ) ) library_function( alsa_lib, "snd_pcm_set_params" );
     45 	_snd_pcm_writen = ( snd_pcm_sframes_t ( * ) ( snd_pcm_t *, void **, snd_pcm_uframes_t ) ) library_function( alsa_lib, "snd_pcm_writen" );
     46 	_snd_pcm_recover = ( int ( * )( snd_pcm_t *, int, int ) ) library_function( alsa_lib, "snd_pcm_recover" );
     47 	_snd_pcm_close = ( int ( * ) ( snd_pcm_t * ) ) library_function( alsa_lib, "snd_pcm_close" );
     48 
     49 	if(
     50 		_snd_lib_error_set_handler == NULL
     51 		|| _snd_strerror == NULL
     52 		|| _snd_pcm_open == NULL
     53 		|| _snd_pcm_set_params == NULL
     54 		|| _snd_pcm_writen == NULL
     55 		|| _snd_pcm_recover == NULL
     56 		|| _snd_pcm_close == NULL
     57 	) {
     58 		FATAL( "Couldn't load ALSA functions" );
     59 	}
     60 
     61 	_snd_lib_error_set_handler( audio_error_handler );
     62 }
     63 
     64 void audio_output_term() {
     65 	_snd_lib_error_set_handler = NULL;
     66 	_snd_strerror = NULL;
     67 	_snd_pcm_open = NULL;
     68 	_snd_pcm_set_params = NULL;
     69 	_snd_pcm_writen = NULL;
     70 	_snd_pcm_recover = NULL;
     71 	_snd_pcm_close = NULL;
     72 
     73 	library_close( alsa_lib );
     74 
     75 	alsa_lib = NULL;
     76 }
     77 
     78 static void audio_output_write( snd_pcm_t * pcm, s16 * samples, s32 num_samples ) {
     79 	s32 i = 0;
     80 	while( i < num_samples ) {
     81 		s16 * channels[ 2 ] = { samples + i, samples + i };
     82 
     83 		snd_pcm_sframes_t written = _snd_pcm_writen( pcm, ( void ** ) channels, num_samples - i );
     84 		if( written <= 0 ) {
     85 			_snd_pcm_recover( pcm, written, 1 );
     86 		}
     87 		else {
     88 			i += written;
     89 		}
     90 	}
     91 }
     92 
     93 static THREAD( audio_output_thread ) {
     94 	AudioOutputDevice * device = ( AudioOutputDevice * ) data;
     95 
     96 	while( load_acquire( &device->shutting_down ) == 0 ) {
     97 		constexpr u32 num_samples = 128; // about 3ms, this might be too high still
     98 		STATIC_ASSERT( ARRAY_COUNT( device->buffer.samples ) % num_samples == 0 );
     99 
    100 		u32 cursor = load_acquire( &device->buffer.cursor );
    101 		audio_output_write( device->pcm, &device->buffer.samples[ cursor ], num_samples );
    102 		store_release( &device->buffer.cursor, ( cursor + num_samples ) % ARRAY_COUNT( device->buffer.samples ) );
    103 	}
    104 
    105 	THREAD_END;
    106 }
    107 
    108 void audio_output_open( AudioOutputDevice * device ) {
    109 	const int channels = 2;
    110 	const int sample_rate = 44100;
    111 	const int ms = 1000;
    112 	const int latency = 10 * ms;
    113 
    114 	int err_open = _snd_pcm_open( &device->pcm, "default", SND_PCM_STREAM_PLAYBACK, 0 );
    115 	if( err_open != 0 ) {
    116 		FATAL( "Couldn't open sound output: {}", _snd_strerror( err_open ) );
    117 	}
    118 
    119 	int err_params = _snd_pcm_set_params( device->pcm,
    120 		SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_NONINTERLEAVED,
    121 		channels, sample_rate, 1, latency );
    122 	if( err_params != 0 ) {
    123 		FATAL( "Couldn't configure sound output: {}", _snd_strerror( err_params ) );
    124 	}
    125 
    126 	memset( &device->buffer, 0, sizeof( device->buffer ) );
    127 
    128 	store_release( &device->shutting_down, 0 );
    129 	thread_init( &device->thread, audio_output_thread, device );
    130 }
    131 
    132 void audio_output_close( AudioOutputDevice * device ) {
    133 	store_release( &device->shutting_down, 1 );
    134 	thread_join( &device->thread );
    135 
    136 	int err = _snd_pcm_close( device->pcm );
    137 	if( err != 0 ) {
    138 		FATAL( "Couldn't close sound output: {}", _snd_strerror( err ) );
    139 	}
    140 }