commit 6c61b7f9a4f72ddd1b6d904f5b982f97ef3f3103
parent dd0b302cc1ba8a0b64070efb39caa40a4ad6a526
Author: Michael Savage <mikejsavage@gmail.com>
Date: Wed, 12 Sep 2018 18:17:29 +0300
Audio output overhaul, only tested on Linux
Add a ring buffer to AudioOutputDevice and mix into that from the game
thread. The audio thread now just memcpys from that and goes back to
sleep.
Diffstat:
10 files changed, 80 insertions(+), 99 deletions(-)
diff --git a/audio.cc b/audio.cc
@@ -38,7 +38,7 @@ int main( int argc, char ** argv ) {
mixer_init();
AudioOutputDevice output_device;
- audio_output_open( &output_device, mixer_mix );
+ audio_output_open( &output_device );
size_t wave_len;
u8 * wave = file_get_contents( "02 - Unbreakable.wav", &wave_len );
@@ -94,6 +94,8 @@ int main( int argc, char ** argv ) {
break;
}
#endif
+
+ mixer_mix( &output_device.buffer, &arena, 8192, 0 );
}
audio_output_close( &output_device );
diff --git a/darwin_audio_output.cc b/darwin_audio_output.cc
@@ -14,16 +14,21 @@ static OSStatus audio_output_callback(
u32 inBusNumber, u32 num_samples,
AudioBufferList * buffers
) {
+ ASSERT( ARRAY_COUNT( device->buffer.samples ) % num_samples == 0 );
+
AudioOutputDevice * device = ( AudioOutputDevice * ) data;
s16 * samples = ( s16 * ) buffers->mBuffers[ 0 ].mData;
- device->callback( samples, checked_cast< s32 >( num_samples ) );
+
+ u32 cursor = load_acquire( &device->buffer.cursor );
+ memcpy( samples, &device->buffer.samples[ cursor ], num_samples * sizeof( s16 ) );
+ store_release( &device->buffer.cursor, ( cursor + num_samples ) % ARRAY_COUNT( device->buffer.samples ) );
return 0;
}
void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback ) {
- device->callback = callback;
+ memset( &device->buffer, 0, sizeof( device->buffer ) );
AudioComponentDescription desc = { };
desc.componentType = kAudioUnitType_Output;
diff --git a/linux_audio_output.cc b/linux_audio_output.cc
@@ -94,15 +94,18 @@ static THREAD( audio_output_thread ) {
AudioOutputDevice * device = ( AudioOutputDevice * ) data;
while( load_acquire( &device->shutting_down ) == 0 ) {
- s16 samples[ 1024 ];
- device->callback( samples, ARRAY_COUNT( samples ) );
- audio_output_write( device->pcm, samples, ARRAY_COUNT( samples ) );
+ constexpr u32 num_samples = 1024;
+ STATIC_ASSERT( ARRAY_COUNT( device->buffer.samples ) % num_samples == 0 );
+
+ u32 cursor = load_acquire( &device->buffer.cursor );
+ audio_output_write( device->pcm, &device->buffer.samples[ cursor ], num_samples );
+ store_release( &device->buffer.cursor, ( cursor + num_samples ) % ARRAY_COUNT( device->buffer.samples ) );
}
THREAD_END;
}
-void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback ) {
+void audio_output_open( AudioOutputDevice * device ) {
const int channels = 2;
const int sample_rate = 44100;
const int ms = 1000;
@@ -120,7 +123,8 @@ void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback
FATAL( "Couldn't configure sound output: {}", _snd_strerror( err_params ) );
}
- device->callback = callback;
+ memset( &device->buffer, 0, sizeof( device->buffer ) );
+
store_release( &device->shutting_down, 0 );
thread_init( &device->thread, audio_output_thread, device );
}
diff --git a/linux_audio_output.h b/linux_audio_output.h
@@ -3,11 +3,11 @@
#include <alsa/asoundlib.h>
#include "platform_thread.h"
-#include "platform_atomic.h"
struct AudioOutputDevice {
snd_pcm_t * pcm;
Thread thread;
- AudioOutputCallback * callback;
atomic_u32 shutting_down;
+
+ AudioBuffer buffer;
};
diff --git a/memory_arena.h b/memory_arena.h
@@ -34,8 +34,13 @@ T * alloc( MemoryArena * arena, uptr alignment = alignof( T ) ) {
}
template< typename T >
+T * alloc_many( MemoryArena * arena, size_t n, uptr alignment = alignof( T ) ) {
+ return ( T * ) memarena_push_size( arena, sizeof( T ) * n, alignment );
+}
+
+template< typename T >
array< T > alloc_array( MemoryArena * arena, size_t n, uptr alignment = alignof( T ) ) {
- return array< T >( ( T * ) memarena_push_size( arena, sizeof( T ) * n, alignment ), n );
+ return array< T >( alloc_many< T >( arena, n, alignment ), n );
}
template< typename T >
diff --git a/mixer.cc b/mixer.cc
@@ -1,8 +1,8 @@
#include "intrinsics.h"
+#include "memory_arena.h"
#include "int_conversions.h"
#include "mixer.h"
#include "pool.h"
-#include "spsc.h"
struct PlayingSound {
PlayingSoundID id;
@@ -28,13 +28,10 @@ struct MixerCommand {
bool loop;
};
-#define COMMAND_QUEUE_SIZE 256
-
#define RESET_SOUND_COUNTER 0
STATIC_ASSERT( RESET_SOUND_COUNTER != INVALID_SOUND_ID );
static Pool< PlayingSound, MAX_CONCURRENT_SOUNDS > playing_sounds;
-static FixedSPSC< MixerCommand, COMMAND_QUEUE_SIZE > command_queue;
static PlayingSoundID sound_counter;
// TODO: this should really use a hash index/pool with handles
@@ -52,37 +49,6 @@ static void mixer_stop_all_impl() {
playing_sounds.clear();
}
-static void mixer_process_command( MixerCommand cmd ) {
- // TODO: most of these should make smooth adjustments to avoid popping
- switch( cmd.type ) {
- case MIXER_PLAY: {
- PlayingSound * ps = playing_sounds.acquire();
- if( ps != NULL ) {
- ps->id = cmd.sound_id;
- ps->data = cmd.sound_to_play;
- ps->num_played_samples = cmd.start_pos;
- ps->volume = 1.0f;
- ps->loop = cmd.loop;
- }
- } break;
-
- case MIXER_VOLUME: {
- PlayingSound * ps = find_playing_sound( cmd.sound_id );
- if( ps != NULL ) {
- ps->volume = cmd.volume;
- }
- } break;
-
- case MIXER_STOP: {
- playing_sounds.release( find_playing_sound( cmd.sound_id ) );
- } break;
-
- case MIXER_STOP_ALL: {
- mixer_stop_all_impl();
- } break;
- }
-}
-
static void mixer_mix_sound( PlayingSound * ps, float * samples, size_t num_samples ) {
// TODO: simd
@@ -106,71 +72,58 @@ static void mixer_mix_sound( PlayingSound * ps, float * samples, size_t num_samp
}
void mixer_init() {
- mixer_stop_all_impl();
+ mixer_stop_all();
}
-AUDIO_OUTPUT_CALLBACK( mixer_mix ) {
- for( size_t i = 0; i < COMMAND_QUEUE_SIZE; i++ ) {
- MixerCommand cmd;
- if( !command_queue.dequeue( &cmd ) ) break;
- mixer_process_command( cmd );
- }
+void mixer_mix( AudioBuffer * buffer, MemoryArena * arena, u32 samples_to_mix, u32 mix_ahead ) {
+ MEMARENA_SCOPED_CHECKPOINT( arena );
- // TODO: channels
- const s32 MAX_OUTPUT_SAMPLES = 4096;
- float samples[ MAX_OUTPUT_SAMPLES ];
- ASSERT( num_output_samples <= MAX_OUTPUT_SAMPLES );
- for( s32 i = 0; i < num_output_samples; i++ ) {
- samples[ i ] = 0.0f;
+ float * float_samples = alloc_many< float >( arena, samples_to_mix );
+ for( u32 i = 0; i < samples_to_mix; i++ ) {
+ float_samples[ i ] = 0.0f;
}
for( PlayingSound & ps : playing_sounds ) {
- mixer_mix_sound( &ps, samples, num_output_samples );
+ mixer_mix_sound( &ps, float_samples, samples_to_mix );
}
- for( s32 i = 0; i < num_output_samples; i++ ) {
- output_samples[ i ] = checked_cast< s16 >( quantize11s( clamp11( samples[ i ] ), 16 ) );
+ u32 cursor = load_acquire( &buffer->cursor );
+ for( u32 i = 0; i < samples_to_mix; i++ ) {
+ s16 q = checked_cast< s16 >( quantize11s( clamp11( float_samples[ i ] ), 16 ) );
+ buffer->samples[ ( i + cursor ) % ARRAY_COUNT( buffer->samples ) ] = q;
}
}
PlayingSoundID mixer_play( SoundData sound, MixerLoopBool loop, s32 start_pos ) {
- MixerCommand cmd;
- cmd.type = MIXER_PLAY;
- cmd.sound_to_play = sound;
- cmd.start_pos = start_pos;
- cmd.sound_id = sound_counter;
- cmd.loop = loop == MIXER_LOOP;
- command_queue.enqueue_spin( cmd );
+ PlayingSound * ps = playing_sounds.acquire();
+ if( ps == NULL )
+ return INVALID_SOUND_ID;
+
+ ps->id = sound_counter;
+ ps->data = sound;
+ ps->num_played_samples = start_pos;
+ ps->volume = 1.0f;
+ ps->loop = loop;
sound_counter++;
if( sound_counter == INVALID_SOUND_ID ) {
sound_counter = RESET_SOUND_COUNTER;
}
- return cmd.sound_id;
-}
-
-PlayingSoundID mixer_play( SoundData sound, s32 start_pos ) {
- return mixer_play( sound, MIXER_DONTLOOP, start_pos );
+ return ps->id;
}
void mixer_volume( PlayingSoundID sound_id, float volume ) {
- MixerCommand cmd;
- cmd.type = MIXER_VOLUME;
- cmd.sound_id = sound_id;
- cmd.volume = saturate( volume );
- command_queue.enqueue_spin( cmd );
+ PlayingSound * ps = find_playing_sound( sound_id );
+ if( ps != NULL ) {
+ ps->volume = volume;
+ }
}
void mixer_stop( PlayingSoundID sound_id ) {
- MixerCommand cmd;
- cmd.type = MIXER_STOP;
- cmd.sound_id = sound_id;
- command_queue.enqueue_spin( cmd );
+ playing_sounds.release( find_playing_sound( sound_id ) );
}
void mixer_stop_all() {
- MixerCommand cmd;
- cmd.type = MIXER_STOP_ALL;
- command_queue.enqueue_spin( cmd );
+ playing_sounds.clear();
}
diff --git a/mixer.h b/mixer.h
@@ -3,6 +3,7 @@
#define MAX_CONCURRENT_SOUNDS 1024
#include "intrinsics.h"
+#include "memory_arena.h"
#include "assets.h"
#include "platform_audio_output.h"
@@ -17,13 +18,10 @@ enum MixerLoopBool {
void mixer_init();
-AUDIO_OUTPUT_CALLBACK( mixer_mix );
+void mixer_mix( AudioBuffer * buffer, MemoryArena * arena, u32 samples_to_mix, u32 mix_ahead );
-// TODO: maybe make these take a QueueWriter
PlayingSoundID mixer_play( SoundData sound, MixerLoopBool loop = MIXER_DONTLOOP, s32 start_pos = 0 );
PlayingSoundID mixer_play( SoundData sound, s32 start_pos );
void mixer_volume( PlayingSoundID sound_id, float volume );
void mixer_stop( PlayingSoundID sound_id );
void mixer_stop_all();
-
-// TODO: seek, set position, set playback speed/pitch
diff --git a/platform_audio_output.h b/platform_audio_output.h
@@ -2,16 +2,19 @@
#include "intrinsics.h"
#include "platform.h"
-
-#define AUDIO_OUTPUT_CALLBACK( f ) void f( s16 * output_samples, s32 num_output_samples )
-typedef AUDIO_OUTPUT_CALLBACK( AudioOutputCallback );
+#include "platform_atomic.h"
struct AudioOutputDevice;
+struct AudioBuffer {
+ s16 samples[ 8192 ]; // about 0.2s at 44100Hz
+ atomic_u32 cursor;
+};
+
void audio_output_init();
void audio_output_term();
-void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback );
+void audio_output_open( AudioOutputDevice * device );
void audio_output_close( AudioOutputDevice * device );
#if COMPILER_MINGW
diff --git a/win32_audio_output.cc b/win32_audio_output.cc
@@ -71,8 +71,14 @@ void audio_output_term() {
static THREAD( audio_output_thread ) {
AudioOutputDevice * device = ( AudioOutputDevice * ) data;
+ // TODO: can probably get rid of this, don't need double buffers when
+ // we have a big ring buffer
+
// double buffering
- s16 buffers[ 2 ][ 512 ];
+ constexpr u32 num_samples = 512;
+ STATIC_ASSERT( ARRAY_COUNT( device->buffer.samples ) % num_samples == 0 );
+
+ s16 buffers[ 2 ][ num_samples ];
u32 buffer_idx = 0;
while( load_acquire( &device->shutting_down ) == 0 ) {
@@ -86,7 +92,8 @@ static THREAD( audio_output_thread ) {
WaitForSingleObject( device->event, INFINITE );
}
- device->callback( buffers[ buffer_idx ], ARRAY_COUNT( buffers[ buffer_idx ] ) );
+ u32 cursor = load_acquire( &device->buffer.cursor );
+ memcpy( buffers[ buffer_idx ], &device->buffer[ cursor ], sizeof( buffers[ buffer_idx ] ) );
XAUDIO2_BUFFER buf = { };
buf.AudioBytes = sizeof( buffers[ buffer_idx ] );
@@ -98,12 +105,14 @@ static THREAD( audio_output_thread ) {
}
buffer_idx = ( buffer_idx + 1 ) % ARRAY_COUNT( buffers );
+
+ store_release( &device->buffer.cursor, ( cursor + num_samples ) % ARRAY_COUNT( device->buffer.samples ) );
}
THREAD_END;
}
-void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback ) {
+void audio_output_open( AudioOutputDevice * device ) {
WAVEFORMATEX format = { };
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = 1;
@@ -123,7 +132,8 @@ void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback
device->voice->Start( 0 );
- device->callback = callback;
+ memset( &device->buffer, 0, sizeof( device->buffer ) );
+
store_release( &device->shutting_down, 0 );
thread_init( &device->thread, audio_output_thread, device );
}
diff --git a/win32_audio_output.h b/win32_audio_output.h
@@ -15,6 +15,7 @@ struct AudioOutputDevice {
IXAudio2SourceVoice * voice;
HANDLE event;
Thread thread;
- AudioOutputCallback * callback;
atomic_u32 shutting_down;
+
+ AudioBuffer buffer;
};