macos: microphone support. Audio latency setting

This commit is contained in:
Flyinghead 2020-12-24 08:58:46 +01:00
parent 46bb7d3b88
commit ee157db078
3 changed files with 156 additions and 59 deletions

View File

@ -19,62 +19,55 @@
#include <atomic>
//#include <CoreAudio/CoreAudio.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioQueue.h>
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<int> samples_wptr;
static std::atomic<int> samples_rptr;
static cResetEvent bufferEmpty;
// input buffer and indexes
static u8 samples_input[2400];
constexpr size_t InputBufSize = sizeof(samples_input);
static std::atomic<int> input_wptr;
static std::atomic<int> 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<u8*>(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);

View File

@ -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);

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSMicrophoneUsageDescription</key>
<string>Flycast requires microphone access to emulate the Dreamcast microphone</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>