macos: microphone support. Audio latency setting
This commit is contained in:
parent
46bb7d3b88
commit
ee157db078
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue