diff --git a/Common/System/NativeApp.h b/Common/System/NativeApp.h index c8f11597b0..02841f87e5 100644 --- a/Common/System/NativeApp.h +++ b/Common/System/NativeApp.h @@ -59,13 +59,11 @@ void NativeMouseDelta(float dx, float dy); void NativeFrame(GraphicsContext *graphicsContext); // This should render num_samples 44khz stereo samples. -// Try not to make too many assumptions on the granularity -// of num_samples. -// This function may be called from a totally separate thread from -// the rest of the game, so be careful with synchronization. -// Returns the number of samples actually output. The app should do everything it can -// to fill the buffer completely. -int NativeMix(short *audio, int num_samples, int sampleRateHz, void *userdata); +// Try not to make too many assumptions on the granularity of num_samples. +// This function will likely be called from a totally separate thread from +// the rest of the app, so be careful with synchronization. +// The app must fill the buffer completely, doing its own internal buffering if needed. +void NativeMix(short *audio, int num_samples, int sampleRateHz, void *userdata); // Called when it's time to shutdown. After this has been called, // no more calls to any other function will be made from the framework diff --git a/Core/HW/StereoResampler.cpp b/Core/HW/StereoResampler.cpp index 5b757ad055..f5e4be4054 100644 --- a/Core/HW/StereoResampler.cpp +++ b/Core/HW/StereoResampler.cpp @@ -160,9 +160,9 @@ inline int16_t MixSingleSample(int16_t s1, int16_t s2, uint16_t frac) { } // Executed from sound stream thread, pulling sound out of the buffer. -unsigned int StereoResampler::Mix(short* samples, unsigned int numSamples, bool consider_framelimit, int sample_rate) { +void StereoResampler::Mix(s16 *samples, unsigned int numSamples, bool consider_framelimit, int sample_rate) { if (!samples) - return 0; + return; unsigned int currentSample; @@ -242,9 +242,6 @@ unsigned int StereoResampler::Mix(short* samples, unsigned int numSamples, bool // Flush cached variable indexR_.store(indexR); - - // TODO: What should we actually return here? - return currentSample / 2; } // Executes on the emulator thread, pushing sound into the buffer. diff --git a/Core/HW/StereoResampler.h b/Core/HW/StereoResampler.h index 8de8961ef3..137f7f1007 100644 --- a/Core/HW/StereoResampler.h +++ b/Core/HW/StereoResampler.h @@ -32,7 +32,7 @@ public: ~StereoResampler(); // Called from audio threads - unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit, int sampleRate); + void Mix(s16 *samples, unsigned int numSamples, bool consider_framelimit, int sampleRate); // Called from main thread // This clamps the samples to 16-bit before starting to work on them. diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index d1bbbe1fdf..c86161a014 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -792,9 +792,8 @@ void MainAudio::run() { void MainAudio::timerEvent(QTimerEvent *) { memset(mixbuf, 0, mixlen); - size_t frames = NativeMix((short *)mixbuf, AUDIO_BUFFERS*AUDIO_SAMPLES, AUDIO_FREQ); - if (frames > 0) - feed->write(mixbuf, sizeof(short) * AUDIO_CHANNELS * frames); + NativeMix((short *)mixbuf, AUDIO_BUFFERS * AUDIO_SAMPLES, AUDIO_FREQ); + feed->write(mixbuf, sizeof(short) * AUDIO_CHANNELS * frames); } #endif diff --git a/UI/AudioCommon.cpp b/UI/AudioCommon.cpp index 15b345f8b4..30fb1cf050 100644 --- a/UI/AudioCommon.cpp +++ b/UI/AudioCommon.cpp @@ -7,13 +7,11 @@ StereoResampler g_resampler; // numFrames is number of stereo frames. // This is called from *outside* the emulator thread. -int NativeMix(int16_t *outStereo, int numFrames, int sampleRateHz, void *userdata) { - int validFrames = g_resampler.Mix(outStereo, numFrames, false, sampleRateHz); +void NativeMix(int16_t *outStereo, int numFrames, int sampleRateHz, void *userdata) { + g_resampler.Mix(outStereo, numFrames, false, sampleRateHz); // Mix sound effects on top. - g_BackgroundAudio.SFX().Mix(outStereo, validFrames, sampleRateHz); - - return validFrames; + g_BackgroundAudio.SFX().Mix(outStereo, numFrames, sampleRateHz); } void System_AudioGetDebugStats(char *buf, size_t bufSize) { diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 0559281f4e..bfdf0cfeb1 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -803,19 +803,27 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch void CallbackPostRender(UIContext *dc, void *userdata); bool CreateGlobalPipelines(); +// TODO: Add faster special case for channels == 2. void NativeMixWrapper(float *dest, int framesToWrite, int channels, int sampleRateHz, void *userdata) { static int16_t *buffer; static int bufSize; - if (bufSize < framesToWrite * channels) { - buffer = new int16_t[framesToWrite * channels]; - bufSize = framesToWrite * channels; + if (bufSize < framesToWrite * 2) { + buffer = new int16_t[framesToWrite * 2]; + bufSize = framesToWrite * 2; } NativeMix(buffer, framesToWrite, sampleRateHz, userdata); - for (int i = 0; i < framesToWrite * channels; i++) { - int16_t value = buffer[i]; - dest[i] = (float)value * (float)(1.0f / 32767.0f); + for (int i = 0; i < framesToWrite; i++) { + int16_t lvalue = buffer[i * 2]; + int16_t rvalue = buffer[i * 2 + 1]; + dest[i * channels] = (float)lvalue * (float)(1.0f / 32767.0f); + if (channels > 1) { + dest[i * channels + 1] = (float)lvalue * (float)(1.0f / 32767.0f); + } + for (int j = 2; j < channels; j++) { + dest[i * channels + j] = 0.0f; + } } } diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index 8e3becd287..0993e605b5 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -10,11 +10,12 @@ #include #include +#include "Common/Log.h" #include "WASAPIContext.h" static std::string ConvertWStringToUTF8(const std::wstring &wstr) { - int len = (int)wstr.size(); - int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, 0, 0, NULL, NULL); + const int len = (int)wstr.size(); + const int size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, 0, 0, NULL, NULL); std::string s; s.resize(size); if (size > 0) { @@ -24,8 +25,8 @@ static std::string ConvertWStringToUTF8(const std::wstring &wstr) { } static std::wstring ConvertUTF8ToWString(const std::string_view source) { - int len = (int)source.size(); - int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.data(), len, NULL, 0); + const int len = (int)source.size(); + const int size = MultiByteToWideChar(CP_UTF8, 0, source.data(), len, NULL, 0); std::wstring str; str.resize(size); if (size > 0) { @@ -58,6 +59,11 @@ void WASAPIContext::EnumerateDevices(std::vector *output, bool IMMDeviceCollection *collection = nullptr; enumerator_->EnumAudioEndpoints(captureDevices ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection); + if (!collection) { + ERROR_LOG(Log::Audio, "Failed to enumerate devices"); + return; + } + UINT count = 0; collection->GetCount(&count); @@ -124,7 +130,7 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late audioClient3_->GetMixFormat(&format_); audioClient3_->GetSharedModeEnginePeriod(format_, &defaultPeriodFrames, &fundamentalPeriodFrames, &minPeriodFrames, &maxPeriodFrames); - printf("default: %d fundamental: %d min: %d max: %d\n", defaultPeriodFrames, fundamentalPeriodFrames, minPeriodFrames, maxPeriodFrames); + printf("default: %d fundamental: %d min: %d max: %d\n", (int)defaultPeriodFrames, (int)fundamentalPeriodFrames, (int)minPeriodFrames, (int)maxPeriodFrames); printf("initializing with %d frame period at %d Hz, meaning %0.1fms\n", (int)minPeriodFrames, (int)format_->nSamplesPerSec, FramesToMs(minPeriodFrames, format_->nSamplesPerSec)); audioEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); @@ -218,7 +224,7 @@ void WASAPIContext::FrameUpdate(bool allowAutoChange) { void WASAPIContext::AudioLoop() { DWORD taskID = 0; - HANDLE mmcssHandle = NULL; + HANDLE mmcssHandle = nullptr; if (latencyMode_ == LatencyMode::Aggressive) { mmcssHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskID); } @@ -244,7 +250,7 @@ void WASAPIContext::AudioLoop() { else audioClient_->GetCurrentPadding(&padding), audioClient_->GetBufferSize(&available); - UINT32 framesToWrite = available - padding; + const UINT32 framesToWrite = available - padding; BYTE* buffer = nullptr; if (framesToWrite > 0 && SUCCEEDED(renderClient_->GetBuffer(framesToWrite, &buffer))) { callback_((float *)buffer, framesToWrite, 2, format_->nSamplesPerSec, userdata_); diff --git a/ios/AudioEngine.mm b/ios/AudioEngine.mm index 0c638608ac..5efd7da3e0 100644 --- a/ios/AudioEngine.mm +++ b/ios/AudioEngine.mm @@ -19,7 +19,7 @@ static volatile BOOL done = 0; #define SAMPLE_SIZE 44100 static short stream[SAMPLE_SIZE]; -int NativeMix(short *audio, int numSamples, int sampleRateHz, void *userdata); +void NativeMix(short *audio, int numSamples, int sampleRateHz, void *userdata); @interface AudioEngine () @@ -121,16 +121,15 @@ int NativeMix(short *audio, int numSamples, int sampleRateHz, void *userdata); { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ const int sampleRateHz = 44100; - while (!done) - { - size_t frames_ready; - if (![self playing]) - frames_ready = NativeMix(stream, SAMPLE_SIZE / 2, sampleRateHz, 0); - else + while (!done) { + if (![self playing]) { + NativeMix(stream, SAMPLE_SIZE / 2, sampleRateHz, 0); + frames_ready = SAMPLE_SIZE / 2; + } else { frames_ready = 0; + } - if (frames_ready > 0) - { + if (frames_ready > 0) { const size_t bytes_ready = frames_ready * sizeof(short) * 2; alSourcei(source, AL_BUFFER, 0); alBufferData(buffer, AL_FORMAT_STEREO16, stream, bytes_ready, sampleRateHz); @@ -139,9 +138,9 @@ int NativeMix(short *audio, int numSamples, int sampleRateHz, void *userdata); // TODO: Maybe this could get behind? usleep((1000000 * frames_ready) / sampleRateHz); - } - else + } else { usleep(100); + } pthread_yield_np(); } }); diff --git a/ios/iOSCoreAudio.mm b/ios/iOSCoreAudio.mm index 3152cc0005..3216ec0635 100644 --- a/ios/iOSCoreAudio.mm +++ b/ios/iOSCoreAudio.mm @@ -74,41 +74,21 @@ void iOSCoreAudioSetDisplayConnected(bool connected) { iOSCoreAudioUpdateSession(); } -int NativeMix(short *audio, int numSamples, int sampleRate, void *userdata); +void NativeMix(short *audio, int numSamples, int sampleRate, void *userdata); OSStatus iOSCoreAudioCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) -{ - // see if we have any sound to play + AudioBufferList *ioData) { short *output = (short *)ioData->mBuffers[0].mData; - UInt32 framesReady = NativeMix(output, inNumberFrames, SAMPLE_RATE, nullptr); - - if (framesReady == 0) { - // oops, we don't currently have any sound, so return silence - *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence; - } - - /* - * You'd think iOS would want to know how many frames were - * actually generated in case it was less than asked for, but - * apparently that causes micro-stuttering and everything just - * works better if we lie and say we successfully generated as - * many frames as it wanted... weird. We still get micro-stuttering - * but it's less noticeable this way. - */ - //UInt32 bytesReady = framesReady * sizeof(short) * 2; - UInt32 bytesReady = inNumberFrames * sizeof(short) * 2; - ioData->mBuffers[0].mDataByteSize = bytesReady; - + NativeMix(output, inNumberFrames, SAMPLE_RATE, nullptr); + ioData->mBuffers[0].mDataByteSize = inNumberFrames * sizeof(short) * 2; return noErr; } -void iOSCoreAudioInit() -{ +void iOSCoreAudioInit() { iOSCoreAudioUpdateSession(); NSError *error = nil;