Last active
July 28, 2025 20:17
-
-
Save nickav/f91ca22d4d8f10547ff4fbfecec7fcd5 to your computer and use it in GitHub Desktop.
Audio Playback on MacOS with AudioUnit Backend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // Build with: | |
| // > clang coreaudio_backend.m -o main -framework AudioUnit | |
| // | |
| // | |
| // Base Types | |
| // | |
| #include <stdint.h> | |
| typedef int8_t i8; | |
| typedef int16_t i16; | |
| typedef int32_t i32; | |
| typedef int64_t i64; | |
| typedef uint8_t u8; | |
| typedef uint16_t u16; | |
| typedef uint32_t u32; | |
| typedef uint64_t u64; | |
| typedef i8 b8; | |
| typedef i16 b16; | |
| typedef i32 b32; | |
| typedef i64 b64; | |
| typedef float f32; | |
| typedef double f64; | |
| // | |
| // Shared Audio Header | |
| // | |
| typedef struct Audio_Context Audio_Context; | |
| #define AUDIO_CALLBACK(name) void name(Audio_Context *ctx, void *samples, u32 sample_count, void *user) | |
| typedef AUDIO_CALLBACK(Audio_Callback); | |
| typedef struct Audio_Context Audio_Context; | |
| struct Audio_Context | |
| { | |
| // Input | |
| u32 num_channels; | |
| u32 samples_per_second; | |
| Audio_Callback *callback; | |
| void *user_data; | |
| // Output | |
| u32 bits_per_channel; | |
| u32 output_sample_position; | |
| u32 latency_samples; | |
| }; | |
| // | |
| // Audio Backend: CoreAudio | |
| // | |
| // NOTE(nick): requires linking against -framework AudioUnit | |
| #include <AudioUnit/AudioUnit.h> | |
| typedef struct CoreAudio_Context CoreAudio_Context; | |
| struct CoreAudio_Context | |
| { | |
| AudioUnit unit; | |
| Audio_Context ctx; | |
| b32 initted; | |
| b32 running; | |
| }; | |
| static CoreAudio_Context g_audio = {0}; | |
| OSStatus macos_audio_callback( | |
| void *inRefCon, | |
| AudioUnitRenderActionFlags *ioActionFlags, | |
| const AudioTimeStamp *inTimeStamp, | |
| UInt32 inBusNumber, | |
| UInt32 inNumberFrames, | |
| AudioBufferList *ioData) | |
| { | |
| if (!g_audio.running) | |
| { | |
| printf("[CoreAudio] Running...\n"); | |
| g_audio.running = true; | |
| } | |
| SInt16 *output = (SInt16 *)ioData->mBuffers[0].mData; | |
| Audio_Context *ctx = &g_audio.ctx; | |
| assert(ctx->callback); | |
| if (ctx->callback) | |
| { | |
| ctx->callback(ctx, output, inNumberFrames, ctx->user_data); | |
| } | |
| return noErr; | |
| } | |
| bool audio_init(Audio_Context *ctx) | |
| { | |
| // @Robustness: prevent this function from being called twice?? | |
| memcpy(&g_audio.ctx, ctx, sizeof(Audio_Context)); | |
| assert(ctx->samples_per_second == 44100); | |
| assert(ctx->bits_per_channel == 16); | |
| assert(ctx->num_channels == 2); | |
| ctx->output_sample_position = 0; | |
| ctx->latency_samples = 0; | |
| // | |
| // NOTE(nick): kick this off on a thread because it's slow otherwise | |
| // Perhaps specifically when using my Bluetooth headphones... | |
| // | |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
| OSErr err; | |
| AudioComponentDescription acd = { | |
| .componentType = kAudioUnitType_Output, | |
| .componentSubType = kAudioUnitSubType_DefaultOutput, | |
| .componentManufacturer = kAudioUnitManufacturer_Apple, | |
| }; | |
| AudioComponent output = AudioComponentFindNext(NULL, &acd); | |
| if (!output) printf("[CoreAudio] Can't find default output\n"); | |
| err = AudioComponentInstanceNew(output, &g_audio.unit); | |
| if (err) printf("[CoreAudio] Error creating audio unit: %d\n", err); | |
| AURenderCallbackStruct input = { .inputProc = macos_audio_callback }; | |
| err = AudioUnitSetProperty(g_audio.unit, kAudioUnitProperty_SetRenderCallback, | |
| kAudioUnitScope_Input, 0, &input, sizeof(input)); | |
| if (err) printf("[CoreAudio] Error setting callback: %d\n", err); | |
| AudioStreamBasicDescription asbd = { | |
| .mFormatID = kAudioFormatLinearPCM, | |
| .mFormatFlags = 0 | |
| | kAudioFormatFlagIsSignedInteger | |
| | kAudioFormatFlagIsPacked, | |
| .mSampleRate = (double)ctx->samples_per_second, | |
| .mBitsPerChannel = 16, | |
| .mChannelsPerFrame = 2, | |
| .mFramesPerPacket = 1, | |
| .mBytesPerFrame = 4, | |
| .mBytesPerPacket = 4, | |
| }; | |
| err = AudioUnitSetProperty(g_audio.unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &asbd, sizeof(asbd)); | |
| if (err) printf("[CoreAudio] Error setting stream format: %d\n", err); | |
| err = AudioUnitInitialize(g_audio.unit); | |
| if (err) printf("[CoreAudio] Error initializing unit: %d\n", err); | |
| err = AudioOutputUnitStart(g_audio.unit); | |
| if (err) printf("[CoreAudio] Error starting unit: %d\n", err); | |
| g_audio.initted = true; | |
| printf("[CoreAudio] CoreAudio initted\n"); | |
| }); | |
| return true; | |
| } | |
| void audio_free() | |
| { | |
| if (g_audio.unit) | |
| { | |
| AudioOutputUnitStop(g_audio.unit); | |
| AudioUnitUninitialize(g_audio.unit); | |
| AudioComponentInstanceDispose(g_audio.unit); | |
| g_audio.unit = 0; | |
| } | |
| } | |
| // | |
| // NOTE(nick): demo code | |
| // | |
| #include <math.h> | |
| #define TAU 6.28318530717958647692f | |
| void audio_output_sine_wave_i16(u32 samples_per_second, u32 sample_count, f32 tone_hz, i32 tone_volume, i16 *samples) | |
| { | |
| static f32 t_sine = 0; | |
| i32 wave_period = (i32)(samples_per_second / tone_hz); | |
| i16 *sample_out = samples; | |
| for (i32 sample_index = 0; sample_index < (i32)sample_count; sample_index++) | |
| { | |
| f32 sine_value = sin(t_sine); | |
| i16 sample_value = (i16)(sine_value * tone_volume); | |
| *sample_out++ = sample_value; | |
| *sample_out++ = sample_value; | |
| t_sine += TAU / (f32)wave_period; | |
| if (t_sine >= TAU) { | |
| t_sine -= TAU; | |
| } | |
| } | |
| } | |
| void audio_test_jingle(Audio_Context *ctx, void *samples, u32 sample_count, void *user) | |
| { | |
| u32 samples_per_second = ctx->samples_per_second; | |
| f32 tone_hz = 440; | |
| { | |
| static u64 total_samples_written = 0; | |
| i32 buffer_index = total_samples_written % samples_per_second; | |
| i32 buffer_size = samples_per_second; | |
| tone_hz = 440; | |
| if (total_samples_written % (samples_per_second * 8) >= samples_per_second * 4) tone_hz = 466.16; | |
| if (buffer_index > buffer_size * 1 / 4) tone_hz = 523.25; | |
| if (buffer_index > buffer_size * 2 / 4) tone_hz = 659.25; | |
| if (buffer_index > buffer_size * 3 / 4) tone_hz = 783.99; | |
| total_samples_written += sample_count; | |
| } | |
| i32 tone_volume = 3000; | |
| audio_output_sine_wave_i16(samples_per_second, sample_count, tone_hz, tone_volume, (i16 *)samples); | |
| } | |
| int main() | |
| { | |
| Audio_Context ctx = {0}; | |
| ctx.num_channels = 2; | |
| ctx.bits_per_channel = 16; | |
| ctx.samples_per_second = 44100; | |
| ctx.callback = audio_test_jingle; | |
| audio_init(&ctx); | |
| while (!g_audio.running) { | |
| usleep(10000); | |
| } | |
| usleep(8 * 1000000); | |
| audio_free(); | |
| printf("[CoreAudio] Closing...\n"); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment