From 706cde3eaa04d24fbf8aa75dbf791c1ae6c68fda Mon Sep 17 00:00:00 2001 From: Eric Warmenhoven Date: Thu, 1 Jan 2026 14:54:41 -0500 Subject: [PATCH] apple: use coreaudio resampling, add coreaudio3 driver to ios/tvos --- audio/audio_driver.c | 56 +++- audio/audio_driver.h | 17 + audio/drivers/alsa.c | 1 + audio/drivers/alsa_qsa.c | 1 + audio/drivers/alsathread.c | 3 +- audio/drivers/audioio.c | 1 + audio/drivers/audioworklet.c | 3 +- audio/drivers/coreaudio.c | 273 ++++++++++++++++ audio/drivers/coreaudio3.m | 307 +++++++++++++++--- audio/drivers/ctr_csnd_audio.c | 3 +- audio/drivers/ctr_dsp_audio.c | 3 +- audio/drivers/ctr_dsp_thread_audio.c | 3 +- audio/drivers/dsound.c | 1 + audio/drivers/gx_audio.c | 1 + audio/drivers/jack.c | 1 + audio/drivers/openal.c | 1 + audio/drivers/opensl.c | 1 + audio/drivers/oss.c | 1 + audio/drivers/pipewire.c | 1 + audio/drivers/ps2_audio.c | 3 +- audio/drivers/ps3_audio.c | 3 +- audio/drivers/psp_audio.c | 3 +- audio/drivers/pulse.c | 1 + audio/drivers/roar.c | 3 +- audio/drivers/rsound.c | 1 + audio/drivers/rwebaudio.c | 1 + audio/drivers/sdl_audio.c | 3 +- audio/drivers/switch_audio.c | 1 + audio/drivers/switch_libnx_audren_audio.c | 1 + .../switch_libnx_audren_thread_audio.c | 1 + audio/drivers/switch_thread_audio.c | 3 +- audio/drivers/tinyalsa.c | 1 + audio/drivers/wasapi.c | 3 +- audio/drivers/wiiu_audio.c | 1 + audio/drivers/xaudio.c | 1 + audio/drivers/xenon360_audio.c | 3 +- pkg/apple/BaseConfig.xcconfig | 2 +- pkg/apple/RetroArch.xcodeproj/project.pbxproj | 6 + 38 files changed, 667 insertions(+), 52 deletions(-) diff --git a/audio/audio_driver.c b/audio/audio_driver.c index 04447e702379..a34ba1e73117 100644 --- a/audio/audio_driver.c +++ b/audio/audio_driver.c @@ -99,7 +99,8 @@ audio_driver_t audio_null = { NULL, NULL, NULL, /* write_avail */ - NULL /* buffer_size */ + NULL, /* buffer_size */ + NULL /* write_raw */ }; audio_driver_t *audio_drivers[] = { @@ -456,6 +457,45 @@ static void audio_driver_flush(audio_driver_state_t *audio_st, ? 0.0f : audio_st->volume_gain; + /* Fast path: if driver handles resampling and no DSP/mixer is active, + * bypass software resampling entirely. */ + if (audio_st->current_audio->write_raw +#ifdef HAVE_DSP_FILTER + && !audio_st->dsp +#endif +#ifdef HAVE_AUDIOMIXER + && audio_st->mixer_streams_playing == 0 +#endif + ) + { + size_t frames = samples >> 1; + double rate_adjust = 1.0; + unsigned input_rate = (unsigned)audio_st->input; + + /* Rate control for A/V sync */ + if (audio_st->flags & AUDIO_FLAG_CONTROL) + { + unsigned write_idx = + audio_st->free_samples_count++ & (AUDIO_BUFFER_FREE_SAMPLES_COUNT - 1); + int avail = (int)audio_st->current_audio->write_avail( + audio_st->context_audio_data); + int half_size = (int)(audio_st->buffer_size / 2); + int delta_mid = avail - half_size; + double direction = (double)delta_mid / half_size; + + audio_st->free_samples_buf[write_idx] = avail; + rate_adjust = 1.0 + audio_st->rate_control_delta * direction; + } + + if (is_slowmotion) + rate_adjust *= slowmotion_ratio; + + /* Note: mute/volume is not applied here - driver must handle or ignore */ + audio_st->current_audio->write_raw(audio_st->context_audio_data, + data, frames, input_rate, rate_adjust); + return; + } + src_data.data_out = NULL; src_data.output_frames = 0; /* We'll assign a proper output to the resampler later in this function */ @@ -1121,6 +1161,8 @@ static void audio_mixer_play_stop_cb( audio_driver_st.mixer_streams[i].stop_cb = NULL; audio_driver_st.mixer_streams[i].handle = NULL; audio_driver_st.mixer_streams[i].voice = NULL; + if (audio_driver_st.mixer_streams_playing > 0) + audio_driver_st.mixer_streams_playing--; } break; case AUDIO_MIXER_SOUND_STOPPED: @@ -1143,6 +1185,8 @@ static void audio_mixer_menu_stop_cb( unsigned i = (unsigned)idx; audio_driver_st.mixer_streams[i].state = AUDIO_STREAM_STATE_STOPPED; audio_driver_st.mixer_streams[i].volume = 0.0f; + if (audio_driver_st.mixer_streams_playing > 0) + audio_driver_st.mixer_streams_playing--; } break; case AUDIO_MIXER_SOUND_STOPPED: @@ -1180,6 +1224,8 @@ static void audio_mixer_play_stop_sequential_cb( audio_driver_st.mixer_streams[i].stop_cb = NULL; audio_driver_st.mixer_streams[i].handle = NULL; audio_driver_st.mixer_streams[i].voice = NULL; + if (audio_driver_st.mixer_streams_playing > 0) + audio_driver_st.mixer_streams_playing--; i++; @@ -1330,6 +1376,11 @@ bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params) audio_driver_st.mixer_streams[free_slot].volume = params->volume; audio_driver_st.mixer_streams[free_slot].stop_cb = stop_cb; + if ( params->state == AUDIO_STREAM_STATE_PLAYING + || params->state == AUDIO_STREAM_STATE_PLAYING_LOOPED + || params->state == AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL) + audio_driver_st.mixer_streams_playing++; + return true; } @@ -1357,6 +1408,7 @@ static void audio_driver_mixer_play_stream_internal( audio_driver_st.resampler_quality, audio_driver_st.mixer_streams[i].stop_cb); audio_driver_st.mixer_streams[i].state = (enum audio_mixer_state)type; + audio_driver_st.mixer_streams_playing++; break; case AUDIO_STREAM_STATE_PLAYING: case AUDIO_STREAM_STATE_PLAYING_LOOPED: @@ -1591,6 +1643,8 @@ void audio_driver_mixer_stop_stream(unsigned i) audio_mixer_stop(voice); audio_driver_st.mixer_streams[i].state = AUDIO_STREAM_STATE_STOPPED; audio_driver_st.mixer_streams[i].volume = 1.0f; + if (audio_driver_st.mixer_streams_playing > 0) + audio_driver_st.mixer_streams_playing--; } break; case AUDIO_STREAM_STATE_STOPPED: diff --git a/audio/audio_driver.h b/audio/audio_driver.h index cd0ca5606d0b..79707b98839b 100644 --- a/audio/audio_driver.h +++ b/audio/audio_driver.h @@ -165,6 +165,22 @@ typedef struct audio_driver size_t (*write_avail)(void *data); size_t (*buffer_size)(void *data); + + /** + * Optional. Write raw int16 samples with resampling handled by driver. + * If non-NULL, audio_driver will call this instead of doing software + * resampling. The driver is responsible for resampling from input_rate + * to its output rate, applying the rate_adjust factor for A/V sync. + * + * @param data Driver context + * @param samples Interleaved int16 stereo samples (LRLRLR...) + * @param frames Number of frames (pairs of samples) + * @param input_rate Source sample rate in Hz + * @param rate_adjust Rate adjustment multiplier for A/V sync (1.0 = normal) + * @return Number of frames written, or -1 on error + */ + ssize_t (*write_raw)(void *data, const int16_t *samples, size_t frames, + unsigned input_rate, double rate_adjust); } audio_driver_t; typedef struct @@ -247,6 +263,7 @@ typedef struct bool mute_enable; #ifdef HAVE_AUDIOMIXER bool mixer_mute_enable; + uint8_t mixer_streams_playing; /* Count of currently playing mixer streams */ #endif /* Sample the flush delta-time when fast forwarding to find the correct ratio. */ diff --git a/audio/drivers/alsa.c b/audio/drivers/alsa.c index a4c066d7ec23..b5768a1ed39e 100644 --- a/audio/drivers/alsa.c +++ b/audio/drivers/alsa.c @@ -539,4 +539,5 @@ audio_driver_t audio_alsa = { alsa_device_list_free, alsa_write_avail, alsa_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/alsa_qsa.c b/audio/drivers/alsa_qsa.c index 581967b366c4..94ce264fd65a 100644 --- a/audio/drivers/alsa_qsa.c +++ b/audio/drivers/alsa_qsa.c @@ -377,4 +377,5 @@ audio_driver_t audio_alsa = { NULL, alsa_qsa_write_avail, alsa_qsa_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/alsathread.c b/audio/drivers/alsathread.c index 87b63061f3dc..62068182a429 100644 --- a/audio/drivers/alsathread.c +++ b/audio/drivers/alsathread.c @@ -673,5 +673,6 @@ audio_driver_t audio_alsathread = { alsa_device_list_new, /* Reusing these functions from alsa.c */ alsa_device_list_free, /* because they don't use the driver context */ alsa_thread_write_avail, - alsa_thread_buffer_size + alsa_thread_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/audioio.c b/audio/drivers/audioio.c index 6111ece86416..5c6956391d30 100644 --- a/audio/drivers/audioio.c +++ b/audio/drivers/audioio.c @@ -205,4 +205,5 @@ audio_driver_t audio_audioio = { NULL, audioio_write_avail, audioio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/audioworklet.c b/audio/drivers/audioworklet.c index 41846a566ca1..5505085dc718 100644 --- a/audio/drivers/audioworklet.c +++ b/audio/drivers/audioworklet.c @@ -533,5 +533,6 @@ audio_driver_t audio_audioworklet = { NULL, NULL, audioworklet_write_avail, - audioworklet_buffer_size + audioworklet_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/coreaudio.c b/audio/drivers/coreaudio.c index 55488f8d103c..ac6f6120d875 100644 --- a/audio/drivers/coreaudio.c +++ b/audio/drivers/coreaudio.c @@ -15,6 +15,7 @@ */ #include #include +#include #include @@ -25,6 +26,7 @@ #endif #include +#include #include #include @@ -37,6 +39,9 @@ #include "../audio_driver.h" #include "../../verbosity.h" +/* Threshold for recreating AudioConverter (0.5% change) */ +#define RATE_CHANGE_THRESHOLD 0.005 + typedef struct coreaudio { dispatch_semaphore_t sema; @@ -53,11 +58,28 @@ typedef struct coreaudio #else AudioComponentInstance dev; #endif + + /* AudioConverter for hardware-accelerated resampling */ + AudioConverterRef converter; + unsigned output_rate; /* Hardware output rate */ + double current_ratio; /* Current resampling ratio (adjusted input rate) */ + + /* Temporary buffer for converter output */ + float *conv_buffer; + size_t conv_buffer_frames; + bool dev_alive; bool is_paused; bool nonblock; } coreaudio_t; +/* Context for AudioConverter input callback */ +typedef struct +{ + const int16_t *data; + size_t frames_left; +} converter_callback_ctx_t; + /* Lock-free ring buffer operations */ static inline size_t rb_write_avail(coreaudio_t *dev) @@ -96,6 +118,95 @@ static void rb_read(coreaudio_t *dev, float *data, size_t count) atomic_fetch_sub_explicit(&dev->filled, count, memory_order_release); } +/* AudioConverter input callback - provides int16 samples */ +static OSStatus converter_input_cb( + AudioConverterRef converter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, + AudioStreamPacketDescription **outDataPacketDescription, + void *inUserData) +{ + converter_callback_ctx_t *ctx = (converter_callback_ctx_t *)inUserData; + + if (ctx->frames_left == 0) + { + *ioNumberDataPackets = 0; + return noErr; + } + + UInt32 frames_to_provide = *ioNumberDataPackets; + if (frames_to_provide > ctx->frames_left) + frames_to_provide = (UInt32)ctx->frames_left; + + ioData->mBuffers[0].mData = (void *)ctx->data; + ioData->mBuffers[0].mDataByteSize = frames_to_provide * 4; /* stereo int16 */ + ioData->mBuffers[0].mNumberChannels = 2; + + ctx->data += frames_to_provide * 2; /* advance by samples */ + ctx->frames_left -= frames_to_provide; + *ioNumberDataPackets = frames_to_provide; + + return noErr; +} + +/* Create or update AudioConverter for the given effective input rate */ +static bool coreaudio_update_converter(coreaudio_t *dev, double effective_input_rate) +{ + AudioStreamBasicDescription input_desc = {0}; + AudioStreamBasicDescription output_desc = {0}; + OSStatus err; + + /* Check if we need to recreate the converter */ + if (dev->converter) + { + double ratio_change = fabs(effective_input_rate - dev->current_ratio) / dev->current_ratio; + if (ratio_change < RATE_CHANGE_THRESHOLD) + return true; /* No significant change, keep existing converter */ + + AudioConverterDispose(dev->converter); + dev->converter = NULL; + } + + /* Input format: int16 stereo at effective input rate */ + input_desc.mSampleRate = effective_input_rate; + input_desc.mFormatID = kAudioFormatLinearPCM; + input_desc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger + | kAudioFormatFlagIsPacked; + input_desc.mBytesPerPacket = 4; + input_desc.mFramesPerPacket = 1; + input_desc.mBytesPerFrame = 4; + input_desc.mChannelsPerFrame = 2; + input_desc.mBitsPerChannel = 16; + + /* Output format: float32 stereo at hardware output rate */ + output_desc.mSampleRate = dev->output_rate; + output_desc.mFormatID = kAudioFormatLinearPCM; + output_desc.mFormatFlags = kAudioFormatFlagIsFloat + | kAudioFormatFlagIsPacked; + output_desc.mBytesPerPacket = 8; + output_desc.mFramesPerPacket = 1; + output_desc.mBytesPerFrame = 8; + output_desc.mChannelsPerFrame = 2; + output_desc.mBitsPerChannel = 32; + + err = AudioConverterNew(&input_desc, &output_desc, &dev->converter); + if (err != noErr) + { + RARCH_ERR("[CoreAudio] Failed to create AudioConverter: %d\n", (int)err); + return false; + } + + dev->current_ratio = effective_input_rate; + + /* Set high quality resampling */ + UInt32 quality = kAudioConverterQuality_High; + AudioConverterSetProperty(dev->converter, + kAudioConverterSampleRateConverterQuality, + sizeof(quality), &quality); + + return true; +} + static void coreaudio_free(void *data) { coreaudio_t *dev = (coreaudio_t*)data; @@ -113,6 +224,12 @@ static void coreaudio_free(void *data) #endif } + if (dev->converter) + AudioConverterDispose(dev->converter); + + if (dev->conv_buffer) + free(dev->conv_buffer); + if (dev->buffer) free(dev->buffer); @@ -214,6 +331,54 @@ static void coreaudio_choose_output_device(coreaudio_t *dev, const char* device) } #endif +/* Query the actual hardware sample rate */ +static unsigned coreaudio_get_hardware_sample_rate( +#if !HAS_MACOSX_10_12 + ComponentInstance dev +#else + AudioComponentInstance dev +#endif + ) +{ + AudioStreamBasicDescription hw_desc; + UInt32 size = sizeof(hw_desc); + +#if TARGET_OS_IPHONE + /* On iOS, query the output scope of RemoteIO to get hardware rate */ + if (AudioUnitGetProperty(dev, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, 0, &hw_desc, &size) == noErr) + { + if (hw_desc.mSampleRate > 0) + return (unsigned)hw_desc.mSampleRate; + } +#else + /* On macOS, query the current output device's nominal sample rate */ + { + AudioDeviceID device_id = 0; + UInt32 device_size = sizeof(device_id); + AudioObjectPropertyAddress prop; + Float64 nominal_rate = 0; + + /* Get the current device from the AudioUnit */ + if (AudioUnitGetProperty(dev, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &device_id, &device_size) == noErr + && device_id != 0) + { + prop.mSelector = kAudioDevicePropertyNominalSampleRate; + prop.mScope = kAudioObjectPropertyScopeGlobal; + prop.mElement = kAudioObjectPropertyElementMaster; + size = sizeof(nominal_rate); + + if (AudioObjectGetPropertyData(device_id, &prop, 0, NULL, + &size, &nominal_rate) == noErr && nominal_rate > 0) + return (unsigned)nominal_rate; + } + } +#endif + + return 0; /* Failed to determine, caller should use fallback */ +} + static void *coreaudio_init(const char *device, unsigned rate, unsigned latency, unsigned block_frames, @@ -276,6 +441,17 @@ static void *coreaudio_init(const char *device, dev->dev_alive = true; + /* Query actual hardware sample rate to avoid double resampling */ + { + unsigned hw_rate = coreaudio_get_hardware_sample_rate(dev->dev); + if (hw_rate > 0 && hw_rate != rate) + { + RARCH_LOG("[CoreAudio] Hardware sample rate is %u Hz (requested %u Hz), using hardware rate.\n", + hw_rate, rate); + rate = hw_rate; + } + } + /* Set audio format */ stream_desc.mSampleRate = rate; stream_desc.mBitsPerChannel = sizeof(float) * CHAR_BIT; @@ -312,6 +488,13 @@ static void *coreaudio_init(const char *device, RARCH_LOG("[CoreAudio] Using output sample rate of %.1f Hz.\n", (float)real_desc.mSampleRate); *new_rate = real_desc.mSampleRate; + dev->output_rate = *new_rate; + + /* Allocate converter output buffer (enough for 2048 output frames) */ + dev->conv_buffer_frames = 2048; + dev->conv_buffer = (float *)calloc(dev->conv_buffer_frames * 2, sizeof(float)); + if (!dev->conv_buffer) + goto error; /* Set channel layout (fails on iOS). */ #ifndef TARGET_OS_IPHONE @@ -406,6 +589,95 @@ static ssize_t coreaudio_write(void *data, const void *buf_, size_t len) return written * sizeof(float); } +/* Write raw int16 samples with hardware-accelerated resampling */ +static ssize_t coreaudio_write_raw(void *data, const int16_t *samples, + size_t frames, unsigned input_rate, double rate_adjust) +{ + coreaudio_t *dev = (coreaudio_t*)data; + double effective_rate; + size_t frames_written = 0; + converter_callback_ctx_t ctx; + AudioBufferList output_buffer; + OSStatus err; + + if (!dev || dev->is_paused || frames == 0) + return 0; + + /* Calculate effective input rate with rate adjustment. + * rate_adjust > 1.0 means we need to speed up (more output for same input), + * so we lower the effective input rate to produce more output frames. */ + effective_rate = (double)input_rate / rate_adjust; + + /* Update converter if needed */ + if (!coreaudio_update_converter(dev, effective_rate)) + return -1; + + /* Set up callback context */ + ctx.data = samples; + ctx.frames_left = frames; + + /* Process in chunks that fit our conv_buffer */ + while (ctx.frames_left > 0) + { + UInt32 output_frames = (UInt32)dev->conv_buffer_frames; + + output_buffer.mNumberBuffers = 1; + output_buffer.mBuffers[0].mNumberChannels = 2; + output_buffer.mBuffers[0].mDataByteSize = output_frames * 8; /* stereo float */ + output_buffer.mBuffers[0].mData = dev->conv_buffer; + + err = AudioConverterFillComplexBuffer(dev->converter, + converter_input_cb, &ctx, + &output_frames, &output_buffer, NULL); + + if (err != noErr && err != 1) /* 1 means end of input, which is ok */ + { + RARCH_ERR("[CoreAudio] AudioConverterFillComplexBuffer failed: %d\n", (int)err); + break; + } + + if (output_frames == 0) + break; + + /* Write converted samples to ring buffer */ + { + float *out_ptr = dev->conv_buffer; + size_t out_samples = output_frames * 2; /* stereo */ + + while (!dev->is_paused && out_samples > 0) + { + size_t avail = rb_write_avail(dev); + size_t to_write = (avail < out_samples) ? avail : out_samples; + + if (to_write > 0) + { + rb_write(dev, out_ptr, to_write); + out_ptr += to_write; + out_samples -= to_write; + frames_written += to_write / 2; /* count frames, not samples */ + } + + if (dev->nonblock) + break; + + if (out_samples > 0) + { + dispatch_time_t timeout = dispatch_time( + DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC); + if (dispatch_semaphore_wait(dev->sema, timeout) != 0) + break; + } + } + } + + /* If we couldn't write all samples in nonblock mode, stop */ + if (dev->nonblock && ctx.frames_left > 0) + break; + } + + return (ssize_t)frames_written; +} + static void coreaudio_set_nonblock_state(void *data, bool state) { coreaudio_t *dev = (coreaudio_t*)data; @@ -477,4 +749,5 @@ audio_driver_t audio_coreaudio = { coreaudio_device_list_free, coreaudio_write_avail, coreaudio_buffer_size, + coreaudio_write_raw }; diff --git a/audio/drivers/coreaudio3.m b/audio/drivers/coreaudio3.m index 21879154e673..1cdf2b1da87b 100644 --- a/audio/drivers/coreaudio3.m +++ b/audio/drivers/coreaudio3.m @@ -21,6 +21,7 @@ #include #include #include +#include #include "../audio_driver.h" #include "../../verbosity.h" @@ -116,41 +117,53 @@ static void rb_write_data(ringbuffer_h r, const float *data, size_t len) rb_len_add(r, (int)n); } -static void rb_read_data(ringbuffer_h r, +/* Read non-interleaved: separate L and R buffers */ +static void rb_read_data_noninterleaved(ringbuffer_h r, float *d0, float *d1, size_t len) { - size_t need = len * 2; + size_t need = len * 2; + size_t have = rb_len(r); + size_t n = MIN(have, need); + size_t i = 0; - do { - size_t have = rb_len(r); - size_t n = MIN(have, need); - int i = 0; - for (; i < n/2; i++) - { - d0[i] = r->buffer[r->read_ptr]; - rb_advance_read(r); - d1[i] = r->buffer[r->read_ptr]; - rb_advance_read(r); - } + for (; i < n / 2; i++) + { + d0[i] = r->buffer[r->read_ptr]; + rb_advance_read(r); + d1[i] = r->buffer[r->read_ptr]; + rb_advance_read(r); + } - need -= n; - rb_len_sub(r, (int)n); + rb_len_sub(r, (int)n); - if (UNLIKELY(need > 0)) - { - const float quiet = 0.0f; - size_t fill; + /* Fill remainder with silence on underflow */ + for (; i < len; i++) + { + d0[i] = 0.0f; + d1[i] = 0.0f; + } +} + +/* Read interleaved: single buffer with LRLRLR pattern */ +static void rb_read_data_interleaved(ringbuffer_h r, + float *out, size_t frames) +{ + size_t need = frames * 2; /* samples needed */ + size_t have = rb_len(r); + size_t n = MIN(have, need); + size_t samples = 0; - /* we got more data */ - if (rb_len(r) > 0) - continue; + for (; samples < n; samples++) + { + out[samples] = r->buffer[r->read_ptr]; + rb_advance_read(r); + } - /* underflow */ - fill = (need/2)*sizeof(float); - memset_pattern4(&d0[i], &quiet, fill); - memset_pattern4(&d1[i], &quiet, fill); - } - } while (0); + rb_len_sub(r, (int)n); + + /* Fill remainder with silence on underflow */ + for (; samples < need; samples++) + out[samples] = 0.0f; } #pragma mark - CoreAudio3 @@ -163,16 +176,26 @@ @interface CoreAudio3 : NSObject { AUAudioUnit *_au; size_t _bufferSize; BOOL _nonBlock; + BOOL _interleaved; + AudioConverterRef _converter; + unsigned _hwRate; + unsigned _lastInputRate; + double _lastRateAdjust; } @property (nonatomic, readwrite) BOOL nonBlock; @property (nonatomic, readonly) BOOL paused; @property (nonatomic, readonly) size_t writeAvailableInBytes; @property (nonatomic, readonly) size_t bufferSizeInBytes; +@property (nonatomic, readonly) unsigned hardwareRate; - (instancetype)initWithRate:(NSUInteger)rate latency:(NSUInteger)latency; - (ssize_t)writeFloat:(const float *)data samples:(size_t)samples; +- (ssize_t)writeRawInt16:(const int16_t *)samples + frames:(size_t)frames + inputRate:(unsigned)inputRate + rateAdjust:(double)rateAdjust; - (void)start; - (void)stop; @@ -190,13 +213,17 @@ - (instancetype)initWithRate:(NSUInteger)rate AVAudioFormat *format, *renderFormat; _sema = dispatch_semaphore_create(0); - - _bufferSize = (latency * rate) / 1000; - _bufferSize *= 2; /* stereo */ - rb_init(&_rb, _bufferSize); + _converter = NULL; + _lastInputRate = 0; + _lastRateAdjust = 1.0; + _interleaved = NO; desc.componentType = kAudioUnitType_Output; +#if TARGET_OS_IPHONE + desc.componentSubType = kAudioUnitSubType_RemoteIO; +#else desc.componentSubType = kAudioUnitSubType_DefaultOutput; +#endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; au = [[AUAudioUnit alloc] initWithComponentDescription:desc error:&err]; @@ -207,16 +234,50 @@ - (instancetype)initWithRate:(NSUInteger)rate if (format.channelCount < 2) return nil; - renderFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:rate channels:2]; + /* Get the actual hardware sample rate */ + _hwRate = (unsigned)format.sampleRate; + if (_hwRate == 0) + _hwRate = (unsigned)rate; + + /* Use hardware rate for buffer size calculation and output format */ + _bufferSize = (latency * _hwRate) / 1000; + _bufferSize *= 2; /* stereo */ + rb_init(&_rb, _bufferSize); + + /* Set up input bus at hardware rate to avoid system resampling. + * Try non-interleaved first (macOS default), fall back to interleaved (iOS). */ + renderFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:_hwRate channels:2]; [au.inputBusses[0] setFormat:renderFormat error:&err]; if (err != nil) - return nil; + { + /* Try interleaved format instead */ + AudioStreamBasicDescription asbd = {0}; + asbd.mSampleRate = _hwRate; + asbd.mFormatID = kAudioFormatLinearPCM; + asbd.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; + asbd.mBytesPerPacket = 8; + asbd.mFramesPerPacket = 1; + asbd.mBytesPerFrame = 8; + asbd.mChannelsPerFrame = 2; + asbd.mBitsPerChannel = 32; + + renderFormat = [[AVAudioFormat alloc] initWithStreamDescription:&asbd]; + [au.inputBusses[0] setFormat:renderFormat error:&err]; + if (err != nil) + return nil; + _interleaved = YES; + } ringbuffer_h rb = &_rb; __block dispatch_semaphore_t sema = _sema; + __block BOOL interleaved = _interleaved; + au.outputProvider = ^AUAudioUnitStatus(AudioUnitRenderActionFlags * actionFlags, const AudioTimeStamp * timestamp, AUAudioFrameCount frameCount, NSInteger inputBusNumber, AudioBufferList * inputData) { - rb_read_data(rb, inputData->mBuffers[0].mData, inputData->mBuffers[1].mData, frameCount); + if (interleaved) + rb_read_data_interleaved(rb, inputData->mBuffers[0].mData, frameCount); + else + rb_read_data_noninterleaved(rb, inputData->mBuffers[0].mData, inputData->mBuffers[1].mData, frameCount); dispatch_semaphore_signal(sema); return 0; }; @@ -227,7 +288,9 @@ - (instancetype)initWithRate:(NSUInteger)rate _au = au; - RARCH_LOG("[CoreAudio3] Using buffer size of %u bytes: (latency = %u ms).\n", (unsigned)self.bufferSizeInBytes, latency); + RARCH_LOG("[CoreAudio3] Using buffer size of %u bytes: (latency = %u ms, hw rate = %u Hz, %s).\n", + (unsigned)self.bufferSizeInBytes, (unsigned)latency, _hwRate, + _interleaved ? "interleaved" : "non-interleaved"); [self start]; } @@ -236,6 +299,15 @@ - (instancetype)initWithRate:(NSUInteger)rate - (void)dealloc { rb_free(&_rb); + if (_converter) + { + AudioConverterDispose(_converter); + _converter = NULL; + } +} + +- (unsigned)hardwareRate { + return _hwRate; } - (BOOL)paused { @@ -282,6 +354,150 @@ - (ssize_t)writeFloat:(const float *)data samples:(size_t)samples { return _len; } +/* Context for AudioConverter callback */ +typedef struct { + const int16_t *data; + size_t frames_left; +} coreaudio3_converter_ctx_t; + +/* AudioConverter input callback */ +static OSStatus coreaudio3_converter_cb( + AudioConverterRef converter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, + AudioStreamPacketDescription **outDataPacketDescription, + void *inUserData) +{ + coreaudio3_converter_ctx_t *ctx = (coreaudio3_converter_ctx_t *)inUserData; + UInt32 frames_to_provide; + + if (ctx->frames_left == 0) + { + *ioNumberDataPackets = 0; + return noErr; + } + + frames_to_provide = *ioNumberDataPackets; + if (frames_to_provide > ctx->frames_left) + frames_to_provide = (UInt32)ctx->frames_left; + + ioData->mBuffers[0].mData = (void *)ctx->data; + ioData->mBuffers[0].mDataByteSize = frames_to_provide * 4; /* stereo int16 */ + ioData->mBuffers[0].mNumberChannels = 2; + + ctx->data += frames_to_provide * 2; /* advance by samples */ + ctx->frames_left -= frames_to_provide; + *ioNumberDataPackets = frames_to_provide; + + return noErr; +} + +- (ssize_t)writeRawInt16:(const int16_t *)samples + frames:(size_t)frames + inputRate:(unsigned)inputRate + rateAdjust:(double)rateAdjust { + OSStatus err; + double effectiveRate; + UInt32 outputFrames; + float *outputBuffer; + size_t outputSamples; + ssize_t written; + AudioBufferList outputBufferList; + coreaudio3_converter_ctx_t ctx; + + if (frames == 0) + return 0; + + /* Check if we need to recreate the converter */ + effectiveRate = inputRate / rateAdjust; + if (_converter == NULL + || _lastInputRate != inputRate + || fabs(_lastRateAdjust - rateAdjust) > 0.0001) + { + AudioStreamBasicDescription inputDesc, outputDesc; + + if (_converter) + { + AudioConverterDispose(_converter); + _converter = NULL; + } + + /* Input: int16 stereo interleaved at input rate */ + memset(&inputDesc, 0, sizeof(inputDesc)); + inputDesc.mSampleRate = effectiveRate; + inputDesc.mFormatID = kAudioFormatLinearPCM; + inputDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + inputDesc.mBytesPerPacket = 4; /* 2 channels * 2 bytes */ + inputDesc.mFramesPerPacket = 1; + inputDesc.mBytesPerFrame = 4; + inputDesc.mChannelsPerFrame = 2; + inputDesc.mBitsPerChannel = 16; + + /* Output: float32 stereo interleaved at hardware rate */ + memset(&outputDesc, 0, sizeof(outputDesc)); + outputDesc.mSampleRate = _hwRate; + outputDesc.mFormatID = kAudioFormatLinearPCM; + outputDesc.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; + outputDesc.mBytesPerPacket = 8; /* 2 channels * 4 bytes */ + outputDesc.mFramesPerPacket = 1; + outputDesc.mBytesPerFrame = 8; + outputDesc.mChannelsPerFrame = 2; + outputDesc.mBitsPerChannel = 32; + + err = AudioConverterNew(&inputDesc, &outputDesc, &_converter); + if (err != noErr) + { + RARCH_ERR("[CoreAudio3]: AudioConverterNew failed: %d\n", (int)err); + return -1; + } + + /* Set high quality resampling */ + UInt32 quality = kAudioConverterQuality_High; + AudioConverterSetProperty(_converter, + kAudioConverterSampleRateConverterQuality, + sizeof(quality), &quality); + + _lastInputRate = inputRate; + _lastRateAdjust = rateAdjust; + } + + /* Calculate output size and allocate buffer */ + outputFrames = (UInt32)ceil((double)frames * _hwRate / effectiveRate) + 16; + outputBuffer = (float *)malloc(outputFrames * 2 * sizeof(float)); + if (!outputBuffer) + return -1; + + /* Set up output buffer list for interleaved stereo */ + outputBufferList.mNumberBuffers = 1; + outputBufferList.mBuffers[0].mNumberChannels = 2; + outputBufferList.mBuffers[0].mDataByteSize = outputFrames * 2 * sizeof(float); + outputBufferList.mBuffers[0].mData = outputBuffer; + + /* Set up callback context */ + ctx.data = samples; + ctx.frames_left = frames; + + err = AudioConverterFillComplexBuffer(_converter, + coreaudio3_converter_cb, &ctx, + &outputFrames, &outputBufferList, NULL); + + if (err != noErr && err != 1) /* 1 means end of input */ + { + RARCH_ERR("[CoreAudio3]: AudioConverterFillComplexBuffer failed: %d\n", (int)err); + free(outputBuffer); + return -1; + } + + /* Write resampled float data to ring buffer */ + outputSamples = outputFrames * 2; + written = [self writeFloat:outputBuffer samples:outputSamples]; + + free(outputBuffer); + + /* Return input frames consumed */ + return (written > 0) ? (ssize_t)frames : written; +} + @end static void coreaudio3_free(void *data) @@ -301,8 +517,11 @@ static void coreaudio3_free(void *data) { CoreAudio3 *dev = [[CoreAudio3 alloc] initWithRate:rate latency:latency]; + if (dev == nil) + return NULL; - *new_rate = rate; + /* Return hardware rate so RetroArch knows what we're outputting */ + *new_rate = dev.hardwareRate; return (__bridge_retained void *)dev; } @@ -370,6 +589,19 @@ static size_t coreaudio3_buffer_size(void *data) return dev.bufferSizeInBytes; } +static ssize_t coreaudio3_write_raw(void *data, const int16_t *samples, + size_t frames, unsigned input_rate, double rate_adjust) +{ + CoreAudio3 *dev = (__bridge CoreAudio3 *)data; + if (dev == nil) + return -1; + + return [dev writeRawInt16:samples + frames:frames + inputRate:input_rate + rateAdjust:rate_adjust]; +} + audio_driver_t audio_coreaudio3 = { coreaudio3_init, coreaudio3_write, @@ -384,4 +616,5 @@ static size_t coreaudio3_buffer_size(void *data) NULL, /* device_list_free */ coreaudio3_write_avail, coreaudio3_buffer_size, + coreaudio3_write_raw, }; diff --git a/audio/drivers/ctr_csnd_audio.c b/audio/drivers/ctr_csnd_audio.c index 3ae643f54ebc..9e8664b4cb05 100644 --- a/audio/drivers/ctr_csnd_audio.c +++ b/audio/drivers/ctr_csnd_audio.c @@ -292,5 +292,6 @@ audio_driver_t audio_ctr_csnd = { NULL, NULL, ctr_csnd_audio_write_avail, - ctr_csnd_audio_buffer_size + ctr_csnd_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/ctr_dsp_audio.c b/audio/drivers/ctr_dsp_audio.c index 26381279feb5..b8235f3beeff 100644 --- a/audio/drivers/ctr_dsp_audio.c +++ b/audio/drivers/ctr_dsp_audio.c @@ -214,5 +214,6 @@ audio_driver_t audio_ctr_dsp = { NULL, NULL, ctr_dsp_audio_write_avail, - ctr_dsp_audio_buffer_size + ctr_dsp_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/ctr_dsp_thread_audio.c b/audio/drivers/ctr_dsp_thread_audio.c index 3fc47f1b3563..9b3418400f6b 100644 --- a/audio/drivers/ctr_dsp_thread_audio.c +++ b/audio/drivers/ctr_dsp_thread_audio.c @@ -335,5 +335,6 @@ audio_driver_t audio_ctr_dsp_thread = { NULL, NULL, ctr_dsp_thread_audio_write_avail, - ctr_dsp_thread_audio_buffer_size + ctr_dsp_thread_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/dsound.c b/audio/drivers/dsound.c index 1a84782631d6..97441c3b2dc1 100644 --- a/audio/drivers/dsound.c +++ b/audio/drivers/dsound.c @@ -632,4 +632,5 @@ audio_driver_t audio_dsound = { dsound_device_list_free, dsound_write_avail, dsound_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/gx_audio.c b/audio/drivers/gx_audio.c index bd864c54eddb..fcacb26c4553 100644 --- a/audio/drivers/gx_audio.c +++ b/audio/drivers/gx_audio.c @@ -233,4 +233,5 @@ audio_driver_t audio_gx = { NULL, gx_audio_write_avail, gx_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/jack.c b/audio/drivers/jack.c index 96ac9bfc24c9..d07ac491c581 100644 --- a/audio/drivers/jack.c +++ b/audio/drivers/jack.c @@ -372,4 +372,5 @@ audio_driver_t audio_jack = { NULL, ja_write_avail, ja_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/openal.c b/audio/drivers/openal.c index be6c1f6572bb..69f8993c7633 100644 --- a/audio/drivers/openal.c +++ b/audio/drivers/openal.c @@ -349,4 +349,5 @@ audio_driver_t audio_openal = { al_device_list_free, al_write_avail, al_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/opensl.c b/audio/drivers/opensl.c index ec7b5c45ea4f..dd904fa5a547 100644 --- a/audio/drivers/opensl.c +++ b/audio/drivers/opensl.c @@ -313,4 +313,5 @@ audio_driver_t audio_opensl = { NULL, sl_write_avail, sl_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/oss.c b/audio/drivers/oss.c index eb5d75eafb81..a8de978581a9 100644 --- a/audio/drivers/oss.c +++ b/audio/drivers/oss.c @@ -222,4 +222,5 @@ audio_driver_t audio_oss = { NULL, oss_write_avail, oss_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/pipewire.c b/audio/drivers/pipewire.c index 68dcfeb391de..46f9bff4d6e2 100644 --- a/audio/drivers/pipewire.c +++ b/audio/drivers/pipewire.c @@ -887,4 +887,5 @@ audio_driver_t audio_pipewire = { pwire_device_list_free, pwire_write_avail, pwire_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/ps2_audio.c b/audio/drivers/ps2_audio.c index 4d7a36edc58b..afa3e6a16920 100644 --- a/audio/drivers/ps2_audio.c +++ b/audio/drivers/ps2_audio.c @@ -134,5 +134,6 @@ audio_driver_t audio_ps2 = { NULL, NULL, ps2_audio_write_avail, - ps2_audio_buffer_size + ps2_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/ps3_audio.c b/audio/drivers/ps3_audio.c index 6d7d95bcb271..69bef0cec3f0 100644 --- a/audio/drivers/ps3_audio.c +++ b/audio/drivers/ps3_audio.c @@ -240,5 +240,6 @@ audio_driver_t audio_ps3 = { NULL, NULL, ps3_audio_write_avail, - NULL + NULL, /* buffer_size */ + NULL /* write_raw */ }; diff --git a/audio/drivers/psp_audio.c b/audio/drivers/psp_audio.c index 17c077a2a97a..1e782c3138e0 100644 --- a/audio/drivers/psp_audio.c +++ b/audio/drivers/psp_audio.c @@ -338,5 +338,6 @@ audio_driver_t audio_psp = { NULL, NULL, psp_write_avail, - psp_buffer_size + psp_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/pulse.c b/audio/drivers/pulse.c index f8c916a0bb7e..42cc976f7871 100644 --- a/audio/drivers/pulse.c +++ b/audio/drivers/pulse.c @@ -427,4 +427,5 @@ audio_driver_t audio_pulse = { pulse_device_list_free, pulse_write_avail, pulse_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/roar.c b/audio/drivers/roar.c index 09cda38645b8..90975e4fbe1a 100644 --- a/audio/drivers/roar.c +++ b/audio/drivers/roar.c @@ -143,5 +143,6 @@ audio_driver_t audio_roar = { NULL, NULL, ra_write_avail, - NULL + NULL, /* buffer_size */ + NULL /* write_raw */ }; diff --git a/audio/drivers/rsound.c b/audio/drivers/rsound.c index aec3ad1d4428..9a05bc12f6dc 100644 --- a/audio/drivers/rsound.c +++ b/audio/drivers/rsound.c @@ -231,4 +231,5 @@ audio_driver_t audio_rsound = { NULL, rs_write_avail, rs_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/rwebaudio.c b/audio/drivers/rwebaudio.c index c4033b7f18b2..411e264f1f0a 100644 --- a/audio/drivers/rwebaudio.c +++ b/audio/drivers/rwebaudio.c @@ -260,4 +260,5 @@ audio_driver_t audio_rwebaudio = { NULL, rwebaudio_write_avail, rwebaudio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/sdl_audio.c b/audio/drivers/sdl_audio.c index 4986f7b0ba24..d2558b1781c1 100644 --- a/audio/drivers/sdl_audio.c +++ b/audio/drivers/sdl_audio.c @@ -746,5 +746,6 @@ audio_driver_t audio_sdl = { sdl_audio_list_new, sdl_audio_list_free, sdl_audio_write_avail, - NULL + NULL, /* buffer_size */ + NULL /* write_raw */ }; diff --git a/audio/drivers/switch_audio.c b/audio/drivers/switch_audio.c index 2db183aa82a6..ff0651f71ee4 100644 --- a/audio/drivers/switch_audio.c +++ b/audio/drivers/switch_audio.c @@ -362,4 +362,5 @@ audio_driver_t audio_switch = { NULL, /* device_list_free */ switch_audio_write_avail, switch_audio_buffer_size, /* buffer_size */ + NULL /* write_raw */ }; diff --git a/audio/drivers/switch_libnx_audren_audio.c b/audio/drivers/switch_libnx_audren_audio.c index 7d9e9a4b34b8..88a66f1baa69 100644 --- a/audio/drivers/switch_libnx_audren_audio.c +++ b/audio/drivers/switch_libnx_audren_audio.c @@ -365,4 +365,5 @@ audio_driver_t audio_switch_libnx_audren = { NULL, /* device_list_free */ libnx_audren_audio_write_avail, libnx_audren_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/switch_libnx_audren_thread_audio.c b/audio/drivers/switch_libnx_audren_thread_audio.c index f92b32200bc0..64ad35172a30 100644 --- a/audio/drivers/switch_libnx_audren_thread_audio.c +++ b/audio/drivers/switch_libnx_audren_thread_audio.c @@ -438,4 +438,5 @@ audio_driver_t audio_switch_libnx_audren_thread = { NULL, /* device_list_free */ libnx_audren_thread_audio_write_avail, libnx_audren_thread_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/switch_thread_audio.c b/audio/drivers/switch_thread_audio.c index 927a63b63d6a..c04042513b40 100644 --- a/audio/drivers/switch_thread_audio.c +++ b/audio/drivers/switch_thread_audio.c @@ -441,7 +441,8 @@ audio_driver_t audio_switch_thread = { NULL, /* device_list_new */ NULL, /* device_list_free */ switch_thread_audio_write_avail, - switch_thread_audio_buffer_size + switch_thread_audio_buffer_size, + NULL /* write_raw */ }; /* vim: set ts=3 sw=3 */ diff --git a/audio/drivers/tinyalsa.c b/audio/drivers/tinyalsa.c index 3373cf2e5b7d..35cb4b3118a4 100644 --- a/audio/drivers/tinyalsa.c +++ b/audio/drivers/tinyalsa.c @@ -2413,4 +2413,5 @@ audio_driver_t audio_tinyalsa = { NULL, /* AUDIO_device_list_free */ /*TODO*/ tinyalsa_write_avail, /* AUDIO_write_avail */ /*TODO*/ tinyalsa_buffer_size, /* AUDIO_buffer_size */ /*TODO*/ + NULL /* write_raw */ }; diff --git a/audio/drivers/wasapi.c b/audio/drivers/wasapi.c index 1e52afe31643..b930c1aa1016 100644 --- a/audio/drivers/wasapi.c +++ b/audio/drivers/wasapi.c @@ -1619,5 +1619,6 @@ audio_driver_t audio_wasapi = { wasapi_device_list_new, wasapi_device_list_free, wasapi_write_avail, - wasapi_buffer_size + wasapi_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/wiiu_audio.c b/audio/drivers/wiiu_audio.c index 8350a0130529..25a2fd0efbfb 100644 --- a/audio/drivers/wiiu_audio.c +++ b/audio/drivers/wiiu_audio.c @@ -332,4 +332,5 @@ audio_driver_t audio_ax = NULL, /* device_list_free */ ax_audio_write_avail, ax_audio_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/xaudio.c b/audio/drivers/xaudio.c index ff41bd87a7db..4fccb7a99a95 100644 --- a/audio/drivers/xaudio.c +++ b/audio/drivers/xaudio.c @@ -607,4 +607,5 @@ audio_driver_t audio_xa = { xa_device_list_free, xa_write_avail, xa_buffer_size, + NULL /* write_raw */ }; diff --git a/audio/drivers/xenon360_audio.c b/audio/drivers/xenon360_audio.c index 96bb78ec1766..0c687447e0a1 100644 --- a/audio/drivers/xenon360_audio.c +++ b/audio/drivers/xenon360_audio.c @@ -141,5 +141,6 @@ audio_driver_t audio_xenon360 = { NULL, NULL, xenon360_write_avail, - NULL + NULL, /* buffer_size */ + NULL /* write_raw */ }; diff --git a/pkg/apple/BaseConfig.xcconfig b/pkg/apple/BaseConfig.xcconfig index 245a13753a02..ae1cc0a7d0a0 100644 --- a/pkg/apple/BaseConfig.xcconfig +++ b/pkg/apple/BaseConfig.xcconfig @@ -25,6 +25,7 @@ OTHER_CFLAGS = $(inherited) -DHAVE_COCOA_METAL OTHER_CFLAGS = $(inherited) -DHAVE_COMMAND OTHER_CFLAGS = $(inherited) -DHAVE_CONFIGFILE OTHER_CFLAGS = $(inherited) -DHAVE_COREAUDIO +OTHER_CFLAGS = $(inherited) -DHAVE_COREAUDIO3 OTHER_CFLAGS = $(inherited) -DHAVE_CORELOCATION OTHER_CFLAGS = $(inherited) -DHAVE_CORETEXT OTHER_CFLAGS = $(inherited) -DHAVE_DR_FLAC @@ -102,7 +103,6 @@ OTHER_CFLAGS[arch=arm64*] = $(inherited) -D__ARM_NEON__ -DHAVE_NEON OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DGL_SILENCE_DEPRECATION OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_AVF -OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_COREAUDIO3 OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_COREMIDI OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_DISCORD OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_DYLIB diff --git a/pkg/apple/RetroArch.xcodeproj/project.pbxproj b/pkg/apple/RetroArch.xcodeproj/project.pbxproj index 292b207f37d5..2f3895c906be 100644 --- a/pkg/apple/RetroArch.xcodeproj/project.pbxproj +++ b/pkg/apple/RetroArch.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 079650002CA9C99300CB9D84 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07964FFF2CA9C99300CB9D84 /* QuartzCore.framework */; }; 07A960052BFEE67400CB7332 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07A960042BFEE67400CB7332 /* AVFoundation.framework */; }; 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; + 07C0DEB02026010100CB9D84 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07C0DEAF2026010100CB9D84 /* AudioToolbox.framework */; }; + 07C0DEB12026010100CB9D84 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07C0DEAF2026010100CB9D84 /* AudioToolbox.framework */; }; 500845251B89300700CE6073 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 500845261B89300700CE6073 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 500845271B89300700CE6073 /* retroarch.icns in Resources */ = {isa = PBXBuildFile; fileRef = 84DD5EB71A89F1C7007336C1 /* retroarch.icns */; }; @@ -78,6 +80,7 @@ /* Begin PBXFileReference section */ 07964FFF2CA9C99300CB9D84 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 07A960042BFEE67400CB7332 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 07C0DEAF2026010100CB9D84 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 089C165DFE840E0CC02AAC07 /* InfoPlist.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = OSX/en.lproj/InfoPlist.strings; sourceTree = ""; }; 1DDD58150DA1D0A300B32029 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = OSX/en.lproj/MainMenu.xib; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; @@ -110,6 +113,7 @@ 5008452C1B89300700CE6073 /* libz.dylib in Frameworks */, 5008452D1B89300700CE6073 /* CoreAudio.framework in Frameworks */, 5008452E1B89300700CE6073 /* AudioUnit.framework in Frameworks */, + 07C0DEB12026010100CB9D84 /* AudioToolbox.framework in Frameworks */, 5008452F1B89300700CE6073 /* Cg.framework in Frameworks */, 500845301B89300700CE6073 /* OpenGL.framework in Frameworks */, 500845321B89300700CE6073 /* AppKit.framework in Frameworks */, @@ -125,6 +129,7 @@ 5061C8A41AE47E510080AE14 /* libz.dylib in Frameworks */, 84DD5EA91A89E4BE007336C1 /* CoreAudio.framework in Frameworks */, 84DD5EB31A89E6C0007336C1 /* AudioUnit.framework in Frameworks */, + 07C0DEB02026010100CB9D84 /* AudioToolbox.framework in Frameworks */, 84DD5EAD1A89E5B4007336C1 /* OpenGL.framework in Frameworks */, 84DD5EA31A89E2AA007336C1 /* AppKit.framework in Frameworks */, 07A960052BFEE67400CB7332 /* AVFoundation.framework in Frameworks */, @@ -147,6 +152,7 @@ children = ( 84DD5EB41A89E737007336C1 /* IOKit.framework */, 84DD5EB21A89E6C0007336C1 /* AudioUnit.framework */, + 07C0DEAF2026010100CB9D84 /* AudioToolbox.framework */, 84DD5EB01A89E664007336C1 /* Cg.framework */, 84DD5EAC1A89E5B4007336C1 /* OpenGL.framework */, 84DD5EA81A89E4BE007336C1 /* CoreAudio.framework */,