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 }