diff --git a/core/oslib/audiobackend_coreaudio.cpp b/core/oslib/audiobackend_coreaudio.cpp index 28a3bac5f..cb6f850c3 100644 --- a/core/oslib/audiobackend_coreaudio.cpp +++ b/core/oslib/audiobackend_coreaudio.cpp @@ -19,62 +19,55 @@ #include -//#include #include +#include static AudioUnit audioUnit; -// ~ 93 ms -#define BUFSIZE (4 * 1024 * 4) -static u8 samples_temp[BUFSIZE]; +static u32 BUFSIZE; +static u8 *samples_temp; static std::atomic samples_wptr; static std::atomic samples_rptr; static cResetEvent bufferEmpty; +// input buffer and indexes +static u8 samples_input[2400]; +constexpr size_t InputBufSize = sizeof(samples_input); +static std::atomic input_wptr; +static std::atomic input_rptr; +AudioQueueRef recordQueue; + static OSStatus coreaudio_callback(void* ctx, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* ts, UInt32 bus, UInt32 frames, AudioBufferList* abl) { - verify(frames <= 1024); - - for (int i = 0; i < abl->mNumberBuffers; i++) + for (int i = 0; i < abl->mNumberBuffers; i++) { - u32 buf_size = abl->mBuffers[i].mDataByteSize; - u8* out_buffer = reinterpret_cast(abl->mBuffers[i].mData); - if ((samples_wptr - samples_rptr + BUFSIZE) % BUFSIZE < buf_size) - { - //printf("Core Audio: buffer underrun"); - memset(abl->mBuffers[i].mData, '\0', buf_size); - } - else - { - if (samples_rptr + buf_size > BUFSIZE) - { - // The data wraps around the buffer, so we need to do 2 copies - int size1 = BUFSIZE - samples_rptr; - int size2 = buf_size - size1; - memcpy(out_buffer, samples_temp + samples_rptr, size1); - memcpy(out_buffer + size1, samples_temp, size2); - } - else - { - memcpy(out_buffer, samples_temp + samples_rptr, buf_size); - } - - // Increment the read pointer - samples_rptr = (samples_rptr + buf_size) % BUFSIZE; - - // Set the mutex to allow writing - bufferEmpty.Set(); - } + int size = abl->mBuffers[i].mDataByteSize; + u8 *outBuffer = (u8 *)abl->mBuffers[i].mData; + while (size != 0) + { + int avail = (samples_wptr - samples_rptr + BUFSIZE) % BUFSIZE; + if (avail == 0) + { + //printf("Core Audio: buffer underrun %d bytes (%d)", size, abl->mBuffers[i].mDataByteSize); + memset(outBuffer, '\0', size); + return noErr; + } + avail = std::min(avail, size); + avail = std::min(avail, (int)BUFSIZE - samples_rptr); + memcpy(outBuffer, samples_temp + samples_rptr, avail); + samples_rptr = (samples_rptr + avail) % BUFSIZE; + size -= avail; + outBuffer += avail; + // Set the mutex to allow writing + bufferEmpty.Set(); + } } - bufferEmpty.Set(); - return noErr; } -// We're making these functions static - there's no need to pollute the global namespace static void coreaudio_init() { OSStatus err; @@ -122,36 +115,41 @@ static void coreaudio_init() kAudioUnitParameterFlag_Output, 0, 1, 0); verify(err == noErr); - */ err = AudioUnitInitialize(audioUnit); - verify(err == noErr); + BUFSIZE = settings.aica.BufferSize * 4; + samples_temp = new u8[BUFSIZE](); + samples_rptr = 0; + samples_wptr = 0; + err = AudioOutputUnitStart(audioUnit); - verify(err == noErr); - + bufferEmpty.Set(); } static u32 coreaudio_push(const void* frame, u32 samples, bool wait) { - int byte_size = samples * 4; - while (true) + int size = samples * 4; + while (size != 0) { - int space = (samples_rptr - samples_wptr + BUFSIZE) % BUFSIZE; - if (space != 0 && byte_size > space - 1) + int avail = (samples_rptr - samples_wptr - 4 + BUFSIZE) % BUFSIZE; + if (avail == 0) { if (!wait) break; bufferEmpty.Wait(); continue; } - memcpy(&samples_temp[samples_wptr], frame, byte_size); - samples_wptr = (samples_wptr + byte_size) % BUFSIZE; - break; + avail = std::min(avail, size); + avail = std::min(avail, (int)BUFSIZE - samples_wptr); + memcpy(&samples_temp[samples_wptr], frame, avail); + samples_wptr = (samples_wptr + avail) % BUFSIZE; + frame = (u8 *)frame + avail; + size -= avail; } return 1; @@ -159,18 +157,112 @@ static u32 coreaudio_push(const void* frame, u32 samples, bool wait) static void coreaudio_term() { - OSStatus err; + AudioOutputUnitStop(audioUnit); + AudioUnitUninitialize(audioUnit); + AudioComponentInstanceDispose(audioUnit); + bufferEmpty.Set(); + delete [] samples_temp; +} - err = AudioOutputUnitStop(audioUnit); - verify(err == noErr); +static void coreaudio_record_callback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 frameSize, const AudioStreamPacketDescription *dataFormat) +{ + //DEBUG_LOG(AUDIO, "AudioQueue callback: wptr %d rptr %d bytes %d", (int)input_wptr, (int)input_rptr, inBuffer->mAudioDataByteSize); + UInt32 size = inBuffer->mAudioDataByteSize; + UInt32 freeSpace = (input_rptr - input_wptr - 2 + InputBufSize) % InputBufSize; + if (size > freeSpace) + { + DEBUG_LOG(AUDIO, "coreaudio: record overrun %d bytes", size - freeSpace); + size = freeSpace; + } + while (size != 0) + { + UInt32 chunk = std::min(size, (UInt32)(InputBufSize - input_wptr)); + memcpy(samples_input + input_wptr, inBuffer->mAudioData, chunk); + input_wptr = (input_wptr + chunk) % InputBufSize; + size -= chunk; + } + AudioQueueEnqueueBuffer(recordQueue, inBuffer, 0, nullptr); +} - err = AudioUnitUninitialize(audioUnit); - verify(err == noErr); +static void coreaudio_term_record() +{ + if (recordQueue != nullptr) + { + AudioQueueStop(recordQueue, true); + AudioQueueDispose(recordQueue, true); + recordQueue = nullptr; + } +} - err = AudioComponentInstanceDispose(audioUnit); - verify(err == noErr); +static bool coreaudio_init_record(u32 sampling_freq) +{ + AudioStreamBasicDescription desc; + desc.mFormatID = kAudioFormatLinearPCM; + desc.mSampleRate = (double)sampling_freq; + desc.mChannelsPerFrame = 1; + desc.mBitsPerChannel = 16; + desc.mBytesPerPacket = desc.mBytesPerFrame = 2; + desc.mFramesPerPacket = 1; + desc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; + desc.mReserved = 0; - bufferEmpty.Set(); + OSStatus err = AudioQueueNewInput(&desc, + coreaudio_record_callback, + nullptr, + nullptr, + kCFRunLoopCommonModes, + 0, + &recordQueue); + if (err != noErr) + { + INFO_LOG(AUDIO, "AudioQueueNewInput failed: %d", err); + return false; + } + + AudioQueueBufferRef buffers[2]; + for (UInt32 i = 0; i < ARRAY_SIZE(buffers) && err == noErr; i++) + { + err = AudioQueueAllocateBuffer(recordQueue, 480, &buffers[i]); + if (err == noErr) + err = AudioQueueEnqueueBuffer(recordQueue, buffers[i], 0, nullptr); + } + input_wptr = 0; + input_rptr = 0; + if (err == noErr) + err = AudioQueueStart(recordQueue, nullptr); + if (err != noErr) + { + INFO_LOG(AUDIO, "AudioQueue init failed: %d", err); + coreaudio_term_record(); + return false; + } + DEBUG_LOG(AUDIO, "AudioQueue initialized - sample rate %f", desc.mSampleRate); + + return true; +} + +static u32 coreaudio_record(void* frame, u32 samples) +{ +// DEBUG_LOG(AUDIO, "coreaudio_record: wptr %d rptr %d", (int)input_wptr, (int)input_rptr); + u32 size = samples * 2; + while (size != 0) + { + u32 avail = (input_wptr - input_rptr + InputBufSize) % InputBufSize; + if (avail == 0) + { + DEBUG_LOG(AUDIO, "coreaudio: record underrun %d bytes", size); + break; + } + avail = std::min(avail, size); + avail = std::min(avail, (u32)(InputBufSize - input_rptr)); + + memcpy(frame, &samples_input[input_rptr], avail); + frame = (u8 *)frame + avail; + input_rptr = (input_rptr + avail) % InputBufSize; + size -= avail; + } + + return samples - size / 2; } static audiobackend_t audiobackend_coreaudio = { @@ -179,7 +271,10 @@ static audiobackend_t audiobackend_coreaudio = { &coreaudio_init, &coreaudio_push, &coreaudio_term, - NULL + nullptr, + &coreaudio_init_record, + &coreaudio_record, + &coreaudio_term_record }; static bool core = RegisterAudioBackend(&audiobackend_coreaudio); diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index d8fa5c99c..3d9d5624e 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -1277,7 +1277,7 @@ static void gui_display_settings() ImGui::Checkbox("Limit Emulator Speed", &settings.aica.LimitFPS); ImGui::SameLine(); ShowHelpMarker("Whether to limit the emulator speed using the audio output. Recommended"); -#if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(_WIN32) +#if !defined(__ANDROID__) && !defined(_WIN32) int latency = (int)roundf(settings.aica.BufferSize * 1000.f / 44100.f); ImGui::SliderInt("Latency", &latency, 12, 512, "%d ms"); settings.aica.BufferSize = (int)roundf(latency * 44100.f / 1000.f); diff --git a/shell/apple/emulator-osx/emulator-osx/Info.plist b/shell/apple/emulator-osx/emulator-osx/Info.plist index 5984f6c4b..442f31f2a 100644 --- a/shell/apple/emulator-osx/emulator-osx/Info.plist +++ b/shell/apple/emulator-osx/emulator-osx/Info.plist @@ -2,6 +2,8 @@ + NSMicrophoneUsageDescription + Flycast requires microphone access to emulate the Dreamcast microphone CFBundleDevelopmentRegion en CFBundleDisplayName