// Copyright (c) 2019- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "ppsspp_config.h" #include #include #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Common/System/System.h" #include "Common/System/Request.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceUsbMic.h" #include "Core/CoreTiming.h" #include "Core/MemMapHelpers.h" #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) #define HAVE_WIN32_MICROPHONE #endif #ifdef HAVE_WIN32_MICROPHONE #include "Common/CommonWindows.h" #include "Windows/CaptureDevice.h" #endif enum { SCE_USBMIC_ERROR_INVALID_MAX_SAMPLES = 0x80243806, SCE_USBMIC_ERROR_INVALID_SAMPLERATE = 0x8024380A, }; int eventMicBlockingResume = -1; static QueueBuf *audioBuf = nullptr; static u32 numNeedSamples; static std::vector waitingThreads; static bool isNeedInput; static u32 curSampleRate; static u32 curChannels; static u32 readMicDataLength; static u32 curTargetAddr; static int micState; // 0 means stopped, 1 means started, for save state. static void __MicBlockingResume(u64 userdata, int cyclesLate) { SceUID threadID = (SceUID)userdata; u32 error; int count = 0; for (auto waitingThread : waitingThreads) { if (waitingThread.threadID == threadID) { SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_MICINPUT, error); if (waitID == 0) continue; if (Microphone::isHaveDevice()) { if (Microphone::getReadMicDataLength() >= waitingThread.needSize) { u32 ret = __KernelGetWaitValue(threadID, error); DEBUG_LOG(Log::HLE, "sceUsbMic: Waking up thread(%d)", (int)waitingThread.threadID); __KernelResumeThreadFromWait(threadID, ret); waitingThreads.erase(waitingThreads.begin() + count); } else { u64 waitTimeus = (waitingThread.needSize - Microphone::getReadMicDataLength()) * 1000000 / 2 / waitingThread.sampleRate; CoreTiming::ScheduleEvent(usToCycles(waitTimeus), eventMicBlockingResume, userdata); } } else { for (int i = 0; i < waitingThread.needSize; i++) { if (Memory::IsValidAddress(waitingThread.addr + i)) { Memory::Write_U8(i & 0xFF, waitingThread.addr + i); } } u32 ret = __KernelGetWaitValue(threadID, error); DEBUG_LOG(Log::HLE, "sceUsbMic: Waking up thread(%d)", (int)waitingThread.threadID); __KernelResumeThreadFromWait(threadID, ret); waitingThreads.erase(waitingThreads.begin() + count); readMicDataLength += waitingThread.needSize; } } ++count; } } void __UsbMicInit() { if (audioBuf) { delete audioBuf; audioBuf = nullptr; } numNeedSamples = 0; waitingThreads.clear(); isNeedInput = true; curSampleRate = 44100; curChannels = 1; curTargetAddr = 0; readMicDataLength = 0; micState = 0; eventMicBlockingResume = CoreTiming::RegisterEvent("MicBlockingResume", &__MicBlockingResume); } void __UsbMicShutdown() { if (audioBuf) { delete audioBuf; audioBuf = nullptr; } Microphone::stopMic(); } void __UsbMicDoState(PointerWrap &p) { auto s = p.Section("sceUsbMic", 0, 3); if (!s) { // Still need to restore the event. eventMicBlockingResume = -1; CoreTiming::RestoreRegisterEvent(eventMicBlockingResume, "MicBlockingResume", &__MicBlockingResume); waitingThreads.clear(); return; } bool isMicStartedNow = Microphone::isMicStarted(); Do(p, numNeedSamples); Do(p, waitingThreads); Do(p, isNeedInput); Do(p, curSampleRate); Do(p, curChannels); Do(p, micState); if (s > 1) { Do(p, eventMicBlockingResume); } else { eventMicBlockingResume = -1; } CoreTiming::RestoreRegisterEvent(eventMicBlockingResume, "MicBlockingResume", &__MicBlockingResume); if (s > 2) { Do(p, curTargetAddr); Do(p, readMicDataLength); } if (!audioBuf && numNeedSamples > 0) { audioBuf = new QueueBuf(numNeedSamples << 1); } if (micState == 0) { if (isMicStartedNow) Microphone::stopMic(); } else if (micState == 1) { if (isMicStartedNow) { // Ok, started. } else { Microphone::startMic(new std::vector({ curSampleRate, curChannels })); } } } QueueBuf::QueueBuf(int size) : available(0), end(0), capacity(size) { buf_ = new u8[size]; } QueueBuf::~QueueBuf() { delete[] buf_; } int QueueBuf::push(const u8 *buf, int size) { int addedSize = 0; // This will overwrite the old data if the size prepare to add more than remaining size. std::unique_lock lock(mutex); if (size > capacity) resize(size); while (end + size > capacity) { memcpy(buf_ + end, buf + addedSize, capacity - end); addedSize += capacity - end; size -= capacity - end; end = 0; } memcpy(buf_ + end, buf + addedSize, size); addedSize += size; end = (end + size) % capacity; available = std::min(capacity, available + addedSize); lock.unlock(); return addedSize; } int QueueBuf::pop(u8 *buf, int size) { if (size == 0) { return 0; } int ret = 0; std::unique_lock lock(mutex); if (getAvailableSize() < size) size = getAvailableSize(); ret = size; int startPos = getStartPos(); if (startPos + size <= capacity) { memcpy(buf, buf_ + startPos, size); } else { memcpy(buf, buf_ + startPos, capacity - startPos); memcpy(buf + capacity - startPos, buf_, size - (capacity - startPos)); } available -= size; lock.unlock(); return ret; } void QueueBuf::resize(int newSize) { if (capacity >= newSize) { return; } int availableSize = getAvailableSize(); u8 *oldbuf = buf_; buf_ = new u8[newSize]; pop(buf_, std::min(availableSize, newSize)); available = availableSize; end = availableSize; capacity = newSize; delete[] oldbuf; } void QueueBuf::flush() { std::unique_lock lock(mutex); available = 0; end = 0; lock.unlock(); } int QueueBuf::getRemainingSize() const { return capacity - getAvailableSize(); } int QueueBuf::getStartPos() const { return end >= available ? end - available : capacity - available + end; } static int sceUsbMicPollInputEnd() { ERROR_LOG(Log::HLE, "UNIMPL sceUsbMicPollInputEnd"); return 0; } static int sceUsbMicInputBlocking(u32 maxSamples, u32 sampleRate, u32 bufAddr) { if (!Memory::IsValidAddress(bufAddr)) { ERROR_LOG(Log::HLE, "sceUsbMicInputBlocking(%d, %d, %08x): invalid addresses", maxSamples, sampleRate, bufAddr); return -1; } INFO_LOG(Log::HLE, "sceUsbMicInputBlocking: maxSamples: %d, samplerate: %d, bufAddr: %08x", maxSamples, sampleRate, bufAddr); if (maxSamples <= 0 || (maxSamples & 0x3F) != 0) { return SCE_USBMIC_ERROR_INVALID_MAX_SAMPLES; } if (sampleRate != 44100 && sampleRate != 22050 && sampleRate != 11025) { return SCE_USBMIC_ERROR_INVALID_SAMPLERATE; } return __MicInput(maxSamples, sampleRate, bufAddr, USBMIC); } static int sceUsbMicInputInitEx(u32 paramAddr) { ERROR_LOG(Log::HLE, "UNIMPL sceUsbMicInputInitEx: %08x", paramAddr); return 0; } static int sceUsbMicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr) { if (!Memory::IsValidAddress(bufAddr)) { ERROR_LOG(Log::HLE, "sceUsbMicInput(%d, %d, %08x): invalid addresses", maxSamples, sampleRate, bufAddr); return -1; } WARN_LOG(Log::HLE, "UNTEST sceUsbMicInput: maxSamples: %d, samplerate: %d, bufAddr: %08x", maxSamples, sampleRate, bufAddr); if (maxSamples <= 0 || (maxSamples & 0x3F) != 0) { return SCE_USBMIC_ERROR_INVALID_MAX_SAMPLES; } if (sampleRate != 44100 && sampleRate != 22050 && sampleRate != 11025) { return SCE_USBMIC_ERROR_INVALID_SAMPLERATE; } return __MicInput(maxSamples, sampleRate, bufAddr, USBMIC, false); } static int sceUsbMicGetInputLength() { int ret = Microphone::getReadMicDataLength() / 2; ERROR_LOG(Log::HLE, "UNTEST sceUsbMicGetInputLength(ret: %d)", ret); return ret; } static int sceUsbMicInputInit(int unknown1, int inputVolume, int unknown2) { ERROR_LOG(Log::HLE, "UNIMPL sceUsbMicInputInit(unknown1: %d, inputVolume: %d, unknown2: %d)", unknown1, inputVolume, unknown2); return 0; } static int sceUsbMicWaitInputEnd() { ERROR_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); } int Microphone::startMic(void *param) { #ifdef HAVE_WIN32_MICROPHONE if (winMic) winMic->sendMessage({ CAPTUREDEVIDE_COMMAND::START, param }); #elif PPSSPP_PLATFORM(ANDROID) std::vector *micParam = static_cast*>(param); int sampleRate = micParam->at(0); int channels = micParam->at(1); INFO_LOG(Log::HLE, "microphone_command : sr = %d", sampleRate); System_MicrophoneCommand("startRecording:" + std::to_string(sampleRate)); #endif micState = 1; return 0; } int Microphone::stopMic() { #ifdef HAVE_WIN32_MICROPHONE if (winMic) winMic->sendMessage({ CAPTUREDEVIDE_COMMAND::STOP, nullptr }); #elif PPSSPP_PLATFORM(ANDROID) System_MicrophoneCommand("stopRecording"); #endif micState = 0; return 0; } bool Microphone::isHaveDevice() { #ifdef HAVE_WIN32_MICROPHONE return winMic->getDeviceCounts() >= 1; #elif PPSSPP_PLATFORM(ANDROID) return System_AudioRecordingIsAvailable(); #endif return false; } bool Microphone::isMicStarted() { return micState == 1; } // Deprecated. bool Microphone::isNeedInput() { return ::isNeedInput; } int Microphone::numNeedSamples() { return ::numNeedSamples; } int Microphone::availableAudioBufSize() { return audioBuf->getAvailableSize(); } int Microphone::getReadMicDataLength() { return ::readMicDataLength; } int Microphone::addAudioData(u8 *buf, int size) { if (!audioBuf) return 0; audioBuf->push(buf, size); int addSize = std::min(audioBuf->getAvailableSize(), numNeedSamples() * 2 - getReadMicDataLength()); if (Memory::IsValidRange(curTargetAddr + readMicDataLength, addSize)) { getAudioData(Memory::GetPointerWriteUnchecked(curTargetAddr + readMicDataLength), addSize); NotifyMemInfo(MemBlockFlags::WRITE, curTargetAddr + readMicDataLength, addSize, "MicAddAudioData"); } readMicDataLength += addSize; return size; } int Microphone::getAudioData(u8 *buf, int size) { if(audioBuf) return audioBuf->pop(buf, size); return 0; } void Microphone::flushAudioData() { audioBuf->flush(); } std::vector Microphone::getDeviceList() { #ifdef HAVE_WIN32_MICROPHONE if (winMic) { return winMic->getDeviceList(); } #endif return std::vector(); } void Microphone::onMicDeviceChange() { if (Microphone::isMicStarted()) { Microphone::stopMic(); // Just use the last param. Microphone::startMic(nullptr); } } u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool block) { curSampleRate = sampleRate; curChannels = 1; curTargetAddr = bufAddr; int size = maxSamples << 1; if (!audioBuf) { audioBuf = new QueueBuf(size); } else { audioBuf->resize(size); } numNeedSamples = maxSamples; readMicDataLength = 0; if (!Microphone::isMicStarted()) { std::vector *param = new std::vector({ sampleRate, 1 }); Microphone::startMic(param); } if (Microphone::availableAudioBufSize() > 0) { u32 addSize = std::min(Microphone::availableAudioBufSize(), size); if (Memory::IsValidRange(curTargetAddr, addSize)) { Microphone::getAudioData(Memory::GetPointerWriteUnchecked(curTargetAddr), addSize); NotifyMemInfo(MemBlockFlags::WRITE, curTargetAddr, addSize, "MicInput"); } readMicDataLength += addSize; } if (!block) { return type == CAMERAMIC ? size : maxSamples; } u64 waitTimeus = (size - Microphone::availableAudioBufSize()) * 1000000 / 2 / sampleRate; CoreTiming::ScheduleEvent(usToCycles(waitTimeus), eventMicBlockingResume, __KernelGetCurThread()); MicWaitInfo waitInfo = { __KernelGetCurThread(), bufAddr, size, sampleRate }; waitingThreads.push_back(waitInfo); DEBUG_LOG(Log::HLE, "MicInputBlocking: blocking thread(%d)", (int)__KernelGetCurThread()); __KernelWaitCurThread(WAITTYPE_MICINPUT, 1, size, 0, false, "blocking microphone"); return type == CAMERAMIC ? size : maxSamples; } const HLEFunction sceUsbMic[] = { {0x06128E42, &WrapI_V, "sceUsbMicPollInputEnd", 'i', "" }, {0x2E6DCDCD, &WrapI_UUU, "sceUsbMicInputBlocking", 'i', "xxx" }, {0x45310F07, &WrapI_U, "sceUsbMicInputInitEx", 'i', "x" }, {0x5F7F368D, &WrapI_UUU, "sceUsbMicInput", 'i', "xxx" }, {0x63400E20, &WrapI_V, "sceUsbMicGetInputLength", 'i', "" }, {0xB8E536EB, &WrapI_III, "sceUsbMicInputInit", 'i', "iii" }, {0xF899001C, &WrapI_V, "sceUsbMicWaitInputEnd", 'i', "" }, }; void Register_sceUsbMic() { RegisterModule("sceUsbMic", ARRAY_SIZE(sceUsbMic), sceUsbMic); }