diff --git a/Core/HLE/sceAudio.cpp b/Core/HLE/sceAudio.cpp index 0015d2731a..6f430f558a 100644 --- a/Core/HLE/sceAudio.cpp +++ b/Core/HLE/sceAudio.cpp @@ -570,13 +570,11 @@ static int sceAudioInputInitEx(u32 paramAddr) { } static int sceAudioPollInputEnd() { - ERROR_LOG(Log::HLE, "UNIMPL sceAudioPollInputEnd"); - return 0; + return hleLogDebug(Log::HLE, __MicPollInputEnd()); } static int sceAudioWaitInputEnd() { - ERROR_LOG(Log::HLE, "UNIMPL sceAudioWaitInputEnd"); - return 0; + return hleLogDebug(Log::HLE, 0, "%s", __MicWaitInputEnd() ? "waiting" : "done"); } static int sceAudioGetInputLength() { diff --git a/Core/HLE/sceUsbMic.cpp b/Core/HLE/sceUsbMic.cpp index 3c7d9d2916..2567876078 100644 --- a/Core/HLE/sceUsbMic.cpp +++ b/Core/HLE/sceUsbMic.cpp @@ -42,7 +42,10 @@ static u32 readMicDataLength; static u32 curTargetAddr; static int micState; // 0 means stopped, 1 means started, for save state. static bool micPolling = false; +static u64 inputStartUs; // Not saved, reset on load. static constexpr int MIC_POLL_INTERVAL_US = 5000; +// Hosts deliver audio in chunks. Allow 100 ms of capture jitter before padding. +static constexpr int MIC_JITTER_US = 100000; static void PollMicrophone() { if (micPolling) { @@ -50,6 +53,19 @@ static void PollMicrophone() { } } +// A stalled host microphone must not hang the game (as happened in Go!Edit). +// Fill any samples still missing with silence. +static void PadMicInput(u32 addr, u32 needSize) { + const u32 have = std::min(readMicDataLength, needSize); + if (have < needSize) { + DEBUG_LOG(Log::HLE, "sceUsbMic: host mic only delivered %d of %d bytes, padding with silence", have, needSize); + if (Memory::IsValidRange(addr + have, needSize - have)) { + Memory::Memset(addr + have, 0, needSize - have, "MicSilence"); + } + readMicDataLength = needSize; + } +} + static void __MicBlockingResume(u64 userdata, int cyclesLate) { PollMicrophone(); // Thread ID zero is reserved for polling the host capture stream. @@ -75,24 +91,13 @@ static void __MicBlockingResume(u64 userdata, int cyclesLate) { } if (Microphone::isHaveDevice()) { - // Hosts deliver audio in chunks. Allow 100 ms of capture jitter before padding. const u64 retries = userdata >> 32; - if (micPolling && Microphone::getReadMicDataLength() < (u32)iter->needSize && retries < 20) { + if (micPolling && Microphone::getReadMicDataLength() < (u32)iter->needSize && retries < MIC_JITTER_US / MIC_POLL_INTERVAL_US) { CoreTiming::ScheduleEvent(usToCycles(MIC_POLL_INTERVAL_US), eventMicBlockingResume, (u32)threadID | ((retries + 1) << 32)); iter++; continue; } - // A stalled host microphone must not hang the game (as happened in Go!Edit). - // Fill any samples still missing with silence. - const u32 needSize = (u32)iter->needSize; - const u32 have = std::min((u32)Microphone::getReadMicDataLength(), needSize); - if (have < needSize) { - DEBUG_LOG(Log::HLE, "sceUsbMic: host mic only delivered %d of %d bytes, padding with silence", have, needSize); - if (Memory::IsValidRange(iter->addr + have, needSize - have)) { - Memory::Memset(iter->addr + have, 0, needSize - have, "MicSilence"); - } - readMicDataLength = needSize; - } + PadMicInput(iter->addr, (u32)iter->needSize); u32 ret = __KernelGetWaitValue(threadID, error); DEBUG_LOG(Log::HLE, "sceUsbMic: Waking up thread(%d)", (int)iter->threadID); __KernelResumeThreadFromWait(threadID, ret); @@ -183,6 +188,9 @@ void __UsbMicDoState(PointerWrap &p) { if (!audioBuf && numNeedSamples > 0) { audioBuf = new QueueBuf(numNeedSamples << 1); } + if (p.mode == p.MODE_READ) { + inputStartUs = CoreTiming::GetGlobalTimeUs(); + } if (micState == 0) { if (isMicStartedNow) @@ -276,8 +284,7 @@ int QueueBuf::getStartPos() const { } static int sceUsbMicPollInputEnd() { - ERROR_LOG(Log::HLE, "UNIMPL sceUsbMicPollInputEnd"); - return 0; + return hleLogDebug(Log::HLE, __MicPollInputEnd()); } static int sceUsbMicInputBlocking(u32 maxSamples, u32 sampleRate, u32 bufAddr) { @@ -333,9 +340,11 @@ static int sceUsbMicInputInit(int unknown1, int inputVolume, int unknown2) { } static int sceUsbMicWaitInputEnd() { - WARN_LOG(Log::HLE, "UNIMPL sceUsbMicWaitInputEnd"); - // Hack: Just task switch so other threads get to do work. Helps Beaterator (although recording does not appear to work correctly). - return hleDelayResult(0, "MicWait", 100); + if (__MicWaitInputEnd()) { + return hleLogDebug(Log::HLE, 0, "waiting"); + } + // Nothing pending: still task switch so other threads get to do work. Helps Beaterator. + return hleDelayResult(hleLogDebug(Log::HLE, 0), "MicWait", 100); } int Microphone::startMic() { @@ -439,6 +448,7 @@ u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool b numNeedSamples = maxSamples; readMicDataLength = 0; + inputStartUs = CoreTiming::GetGlobalTimeUs(); if (!Microphone::isMicStarted()) { Microphone::startMic(); } @@ -467,6 +477,39 @@ u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool b return type == CAMERAMIC ? size : maxSamples; } +// A non-blocking input keeps filling its buffer from addAudioData() after it returns. +static bool MicInputPending() { + return Microphone::isHaveDevice() && readMicDataLength < numNeedSamples * 2; +} + +bool __MicWaitInputEnd() { + if (!MicInputPending()) { + return false; + } + const u32 size = numNeedSamples * 2; + u64 waitTimeus = (size - readMicDataLength) * 1000000ULL / 2 / curSampleRate; + CoreTiming::ScheduleEvent(usToCycles(waitTimeus), eventMicBlockingResume, __KernelGetCurThread()); + MicWaitInfo waitInfo = { __KernelGetCurThread(), curTargetAddr, (int)size, curSampleRate }; + waitingThreads.push_back(waitInfo); + DEBUG_LOG(Log::HLE, "MicWaitInputEnd: blocking thread(%d)", (int)__KernelGetCurThread()); + __KernelWaitCurThread(WAITTYPE_MICINPUT, 1, 0, 0, false, "microphone input end"); + return true; +} + +u32 __MicPollInputEnd() { + if (!MicInputPending()) { + return 0; + } + // Same bound as a blocking wait: the request's duration plus the capture jitter. + const u32 size = numNeedSamples * 2; + const u64 durationUs = size * 1000000ULL / 2 / curSampleRate; + if (CoreTiming::GetGlobalTimeUs() < inputStartUs + durationUs + MIC_JITTER_US) { + return 1; + } + PadMicInput(curTargetAddr, size); + return 0; +} + const HLEFunction sceUsbMic[] = { {0x06128E42, &WrapI_V, "sceUsbMicPollInputEnd", 'i', "" }, {0x2E6DCDCD, &WrapI_UUU, "sceUsbMicInputBlocking", 'i', "xxx" }, diff --git a/Core/HLE/sceUsbMic.h b/Core/HLE/sceUsbMic.h index a3e4d69e45..82700af592 100644 --- a/Core/HLE/sceUsbMic.h +++ b/Core/HLE/sceUsbMic.h @@ -87,3 +87,7 @@ namespace Microphone { } u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool block = true); +// Finish a non-blocking __MicInput. Wait blocks the thread until the buffer is full and +// returns whether it had to; Poll returns 1 while the buffer is not full yet. +bool __MicWaitInputEnd(); +u32 __MicPollInputEnd();