diff --git a/Common/Audio/SampleRing.h b/Common/Audio/SampleRing.h new file mode 100644 index 0000000000..a91717bfef --- /dev/null +++ b/Common/Audio/SampleRing.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012- 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/. + +#pragma once + +#include +#include +#include + +// Hands 16-bit samples from one producer thread (a host audio callback) to one consumer thread. +// Lock-free and allocation-free, so it's safe to push from a real-time callback. Input that +// doesn't fit is dropped. +template +class SampleRing { + static_assert((SIZE & (SIZE - 1)) == 0, "SIZE must be a power of two"); + +public: + // Producer. Null samples means silence. + void Push(const int16_t *samples, uint32_t count) { + const uint32_t write = writePos_.load(std::memory_order_relaxed); + const uint32_t read = readPos_.load(std::memory_order_acquire); + count = std::min(count, SIZE - (write - read)); + for (uint32_t i = 0; i < count; i++) { + ring_[(write + i) & (SIZE - 1)] = samples ? samples[i] : 0; + } + writePos_.store(write + count, std::memory_order_release); + } + + // Consumer. Calls func(const int16_t *samples, uint32_t count) for what's buffered, in up to + // two pieces if it wraps around the end of the ring. + template + void Drain(F func) { + const uint32_t read = readPos_.load(std::memory_order_relaxed); + const uint32_t write = writePos_.load(std::memory_order_acquire); + const uint32_t count = write - read; + if (count == 0) { + return; + } + const uint32_t start = read & (SIZE - 1); + const uint32_t first = std::min(count, SIZE - start); + func(&ring_[start], first); + if (count > first) { + func(&ring_[0], count - first); + } + readPos_.store(read + count, std::memory_order_release); + } + + // Consumer. Drops whatever is buffered. + void Clear() { + readPos_.store(writePos_.load(std::memory_order_acquire), std::memory_order_release); + } + +private: + int16_t ring_[SIZE]; + std::atomic writePos_{0}; + std::atomic readPos_{0}; +}; diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index a6fd04639f..0fd334ab09 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -260,6 +260,7 @@ add_library(Common STATIC Render/Text/draw_text_uwp.cpp Render/Text/draw_text_uwp.h Audio/AudioBackend.h + Audio/SampleRing.h System/Display.cpp System/Display.h System/Application.h diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 57d9622932..ff1434abb5 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -381,6 +381,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index dc9852ca52..9a02b190d8 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -692,6 +692,9 @@ Audio + + Audio + Render diff --git a/Common/System/System.h b/Common/System/System.h index 0b7382533f..841549d2f2 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -330,7 +330,6 @@ void System_Notify(SystemNotification notification); std::vector System_GetCameraDeviceList(); bool System_AudioRecordingIsAvailable(); -bool System_AudioRecordingState(); // This will be changed to take an enum. Replacement for the old NativeMessageReceived. void System_PostUIMessage(UIMessage message, std::string_view param = ""); diff --git a/Core/HLE/sceUsbMic.cpp b/Core/HLE/sceUsbMic.cpp index 323e364b36..3c7d9d2916 100644 --- a/Core/HLE/sceUsbMic.cpp +++ b/Core/HLE/sceUsbMic.cpp @@ -17,7 +17,6 @@ #include "ppsspp_config.h" #include -#include #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" @@ -36,9 +35,9 @@ int eventMicBlockingResume = -1; static QueueBuf *audioBuf = nullptr; static u32 numNeedSamples; static std::vector waitingThreads; -static bool isNeedInput; +static bool isNeedInput; // Unused, kept for savestates. static u32 curSampleRate; -static u32 curChannels; +static u32 curChannels; // Always 1, kept for savestates. static u32 readMicDataLength; static u32 curTargetAddr; static int micState; // 0 means stopped, 1 means started, for save state. @@ -76,7 +75,7 @@ static void __MicBlockingResume(u64 userdata, int cyclesLate) { } if (Microphone::isHaveDevice()) { - // SDL delivers host audio in chunks. Allow 100 ms of capture jitter before padding. + // 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) { CoreTiming::ScheduleEvent(usToCycles(MIC_POLL_INTERVAL_US), eventMicBlockingResume, (u32)threadID | ((retries + 1) << 32)); @@ -192,7 +191,7 @@ void __UsbMicDoState(PointerWrap &p) { if (isMicStartedNow) { // Ok, started. } else { - Microphone::startMic(new std::vector({ curSampleRate, curChannels })); + Microphone::startMic(); } } if (p.mode == p.MODE_READ && micPolling) { @@ -213,7 +212,6 @@ QueueBuf::~QueueBuf() { 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) { @@ -226,7 +224,6 @@ int QueueBuf::push(const u8 *buf, int size) { addedSize += size; end = (end + size) % capacity; available = std::min(capacity, available + addedSize); - lock.unlock(); return addedSize; } @@ -235,7 +232,6 @@ int QueueBuf::pop(u8 *buf, int size) { return 0; } int ret = 0; - std::unique_lock lock(mutex); if (getAvailableSize() < size) size = getAvailableSize(); ret = size; @@ -248,7 +244,6 @@ int QueueBuf::pop(u8 *buf, int size) { memcpy(buf + capacity - startPos, buf_, size - (capacity - startPos)); } available -= size; - lock.unlock(); return ret; } @@ -257,21 +252,19 @@ void QueueBuf::resize(int newSize) { return; } int availableSize = getAvailableSize(); - u8 *oldbuf = buf_; - - buf_ = new u8[newSize]; - pop(buf_, std::min(availableSize, newSize)); + u8 *newBuf = new u8[newSize]; + // Unwraps what's buffered to the start of the new buffer. + pop(newBuf, availableSize); + delete[] buf_; + buf_ = newBuf; 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 { @@ -345,9 +338,8 @@ static int sceUsbMicWaitInputEnd() { return hleDelayResult(0, "MicWait", 100); } -int Microphone::startMic(void *param) { - // The rate is curSampleRate, which every caller has set. param can be null (from onMicDeviceChange). - delete static_cast *>(param); +int Microphone::startMic() { + // The rate is curSampleRate, which every caller has set. INFO_LOG(Log::HLE, "microphone_command : sr = %d", curSampleRate); System_MicrophoneCommand("startRecording:" + std::to_string(curSampleRate)); micPolling = System_GetPropertyBool(SYSPROP_MICROPHONE_NEEDS_POLLING); @@ -381,11 +373,6 @@ bool Microphone::isMicStarted() { return micState == 1; } -// Deprecated. -bool Microphone::isNeedInput() { - return ::isNeedInput; -} - int Microphone::numNeedSamples() { return ::numNeedSamples; } @@ -433,12 +420,6 @@ void Microphone::onMicDeviceChange() { if (micPolling) { // The host reopens its capture device. System_MicrophoneCommand("deviceChanged"); - return; - } - if (Microphone::isMicStarted()) { - Microphone::stopMic(); - // Just use the last rate. - Microphone::startMic(nullptr); } } @@ -459,8 +440,7 @@ u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool b numNeedSamples = maxSamples; readMicDataLength = 0; if (!Microphone::isMicStarted()) { - std::vector *param = new std::vector({ sampleRate, 1 }); - Microphone::startMic(param); + Microphone::startMic(); } if (Microphone::availableAudioBufSize() > 0) { diff --git a/Core/HLE/sceUsbMic.h b/Core/HLE/sceUsbMic.h index 240666d0ca..fcc34807db 100644 --- a/Core/HLE/sceUsbMic.h +++ b/Core/HLE/sceUsbMic.h @@ -18,7 +18,6 @@ #pragma once #include "sceKernel.h" -#include void Register_sceUsbMic(); @@ -65,12 +64,10 @@ private: int end = 0; int capacity; // set in constructor u8 *buf_; - // TODO: Turn back to a regular mutex, will take some refactoring. - std::recursive_mutex mutex; }; namespace Microphone { - int startMic(void *param); + int startMic(); int stopMic(); bool isHaveDevice(); bool isMicStarted(); @@ -84,9 +81,6 @@ namespace Microphone { std::vector getDeviceList(); void onMicDeviceChange(); - - // Deprecated. - bool isNeedInput(); } u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool block = true); diff --git a/Windows/WASAPIMicrophone.cpp b/Windows/WASAPIMicrophone.cpp index 8a9873d42f..43f92ec772 100644 --- a/Windows/WASAPIMicrophone.cpp +++ b/Windows/WASAPIMicrophone.cpp @@ -24,10 +24,9 @@ #include #include -#include -#include #include +#include "Common/Audio/SampleRing.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/Log.h" #include "Common/Thread/ThreadUtil.h" @@ -37,12 +36,8 @@ using Microsoft::WRL::ComPtr; -// Single producer (the capture thread), single consumer (the CPU thread, in WASAPIMicrophonePoll). -// Input that doesn't fit is dropped. -static constexpr uint32_t MIC_RING_SIZE = 32768; // Samples, a power of two. 0.7 s at 44.1 kHz. -static int16_t g_micRing[MIC_RING_SIZE]; -static std::atomic g_micWritePos{0}; -static std::atomic g_micReadPos{0}; +// From the capture thread to the CPU thread. 0.7 s at 44.1 kHz. +static SampleRing<32768> g_micRing; // Only touched on the CPU thread. static std::thread g_micThread; @@ -90,16 +85,6 @@ static ComPtr FindDevice(IMMDeviceEnumerator *enumerator) { return device; } -static void PushSamples(const int16_t *samples, uint32_t frames) { - const uint32_t write = g_micWritePos.load(std::memory_order_relaxed); - const uint32_t read = g_micReadPos.load(std::memory_order_acquire); - const uint32_t count = std::min(frames, MIC_RING_SIZE - (write - read)); - for (uint32_t i = 0; i < count; i++) { - g_micRing[(write + i) & (MIC_RING_SIZE - 1)] = samples ? samples[i] : 0; - } - g_micWritePos.store(write + count, std::memory_order_release); -} - enum class CaptureResult { STOPPED, REOPEN, @@ -173,7 +158,7 @@ static CaptureResult RunCapture(IMMDeviceEnumerator *enumerator, int sampleRate, if (FAILED(hr)) { break; } - PushSamples((bufferFlags & AUDCLNT_BUFFERFLAGS_SILENT) ? nullptr : (const int16_t *)data, frames); + g_micRing.Push((bufferFlags & AUDCLNT_BUFFERFLAGS_SILENT) ? nullptr : (const int16_t *)data, frames); capture->ReleaseBuffer(frames); } if (FAILED(hr)) { @@ -236,7 +221,7 @@ void WASAPIMicrophoneStart(int sampleRate) { ResetEvent(g_reopenEvent); // Whatever is left over is from before, maybe at another rate. We're the consumer, so we can drop it. - g_micReadPos.store(g_micWritePos.load(std::memory_order_acquire), std::memory_order_release); + g_micRing.Clear(); g_micThread = std::thread(&CaptureThread, sampleRate); } @@ -251,20 +236,9 @@ void WASAPIMicrophoneStop() { } void WASAPIMicrophonePoll() { - const uint32_t read = g_micReadPos.load(std::memory_order_relaxed); - const uint32_t write = g_micWritePos.load(std::memory_order_acquire); - const uint32_t count = write - read; - if (count == 0) { - return; - } - // In up to two pieces, if it wraps around the end of the ring. - const uint32_t start = read & (MIC_RING_SIZE - 1); - const uint32_t first = std::min(count, MIC_RING_SIZE - start); - Microphone::addAudioData((u8 *)&g_micRing[start], first * sizeof(int16_t)); - if (count > first) { - Microphone::addAudioData((u8 *)g_micRing, (count - first) * sizeof(int16_t)); - } - g_micReadPos.store(read + count, std::memory_order_release); + g_micRing.Drain([](const int16_t *samples, uint32_t count) { + Microphone::addAudioData((u8 *)samples, count * sizeof(int16_t)); + }); } void WASAPIMicrophoneDeviceChanged() { diff --git a/android/jni/AndroidAudio.cpp b/android/jni/AndroidAudio.cpp index 764f3a0ef7..59a9d1d89e 100644 --- a/android/jni/AndroidAudio.cpp +++ b/android/jni/AndroidAudio.cpp @@ -1,4 +1,6 @@ +#include "Common/Audio/SampleRing.h" #include "Common/Log.h" +#include "Core/HLE/sceUsbMic.h" #include "android/jni/AndroidAudio.h" #include "android/jni/OboeContext.h" @@ -6,6 +8,9 @@ std::string g_error; std::mutex g_errorMutex; +// From the input stream's callback to the CPU thread. 0.7 s at 44.1 kHz. +static SampleRing<32768> g_micRing; + AudioContext::AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate) : audioCallback(cb), framesPerBuffer(_FramesPerBuffer), sampleRate(_SampleRate) { if (framesPerBuffer == 0) @@ -84,11 +89,18 @@ bool AndroidAudio_Recording_Stop(AndroidAudioState *state) { return true; } -bool AndroidAudio_Recording_State(AndroidAudioState *state) { - if (!state) { - return false; - } - return state->input_enable; +void AndroidAudio_Recording_Push(const int16_t *samples, int count) { + g_micRing.Push(samples, count); +} + +void AndroidAudio_Recording_Poll() { + g_micRing.Drain([](const int16_t *samples, uint32_t count) { + Microphone::addAudioData((u8 *)samples, count * sizeof(int16_t)); + }); +} + +void AndroidAudio_Recording_Clear() { + g_micRing.Clear(); } bool AndroidAudio_Resume(AndroidAudioState *state) { diff --git a/android/jni/AndroidAudio.h b/android/jni/AndroidAudio.h index 5c16ecd96c..bf53156c54 100644 --- a/android/jni/AndroidAudio.h +++ b/android/jni/AndroidAudio.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -34,7 +35,11 @@ AndroidAudioState *AndroidAudio_Init(AndroidAudioCallback cb, int optimalFramesP bool AndroidAudio_Recording_SetSampleRate(AndroidAudioState *state, int sampleRate); bool AndroidAudio_Recording_Start(AndroidAudioState *state); bool AndroidAudio_Recording_Stop(AndroidAudioState *state); -bool AndroidAudio_Recording_State(AndroidAudioState *state); +// Microphone input is buffered: the input stream pushes it, and AndroidAudio_Recording_Poll hands it +// over to Microphone::addAudioData. Poll and Clear are for the CPU thread. +void AndroidAudio_Recording_Push(const int16_t *samples, int count); +void AndroidAudio_Recording_Poll(); +void AndroidAudio_Recording_Clear(); bool AndroidAudio_Pause(AndroidAudioState *state); bool AndroidAudio_Resume(AndroidAudioState *state); bool AndroidAudio_Shutdown(AndroidAudioState *state); diff --git a/android/jni/OboeContext.cpp b/android/jni/OboeContext.cpp index f4d8056cb5..c897baea0e 100644 --- a/android/jni/OboeContext.cpp +++ b/android/jni/OboeContext.cpp @@ -6,7 +6,6 @@ #include #include "Common/Log.h" -#include "Core/HLE/sceUsbMic.h" #include "android/jni/OboeContext.h" // Owns the streams. Held by shared_ptr since after a disconnect, Oboe calls the error @@ -46,7 +45,8 @@ public: if (stream->getDirection() == oboe::Direction::Output) { audioCallback_((short *)audioData, numFrames, stream->getSampleRate(), nullptr); } else { - Microphone::addAudioData((uint8_t *)audioData, numFrames * sizeof(int16_t)); + // This is the audio thread, so just buffer it. The CPU thread picks it up. + AndroidAudio_Recording_Push((const int16_t *)audioData, numFrames); } return oboe::DataCallbackResult::Continue; } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 6189263774..a082653eed 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -376,6 +376,8 @@ bool System_GetPropertyBool(SystemProperty prop) { // on a per-folder basis. return !System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE); } + case SYSPROP_MICROPHONE_NEEDS_POLLING: + return true; case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE: return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature. case SYSPROP_HAS_TEXT_INPUT_DIALOG: @@ -836,10 +838,6 @@ bool System_AudioRecordingIsAvailable() { return true; } -bool System_AudioRecordingState() { - return AndroidAudio_Recording_State(g_audioState); -} - extern "C" void Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *, jclass) { INFO_LOG(Log::System, "NativeApp.resume() - begin"); AndroidAudio_Resume(g_audioState); @@ -1006,6 +1004,18 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string PushCommand("infrared_command", param1); return true; case SystemRequestType::MICROPHONE_COMMAND: + // From the CPU thread. Starting and stopping go through Java, which asks for permission. + if (param1 == "pollRecording") { + AndroidAudio_Recording_Poll(); + return true; + } else if (param1 == "deviceChanged") { + // There's no device choice on Android. + return true; + } + if (startsWith(param1, "startRecording:")) { + // Whatever is left over is from before, maybe at another rate. + AndroidAudio_Recording_Clear(); + } PushCommand("microphone_command", param1); return true; case SystemRequestType::SHARE_TEXT: diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 303fbbc3ff..831106396e 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -1847,6 +1847,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList } else if (params.equals("stopRecording")) { NativeApp.audioRecording_Stop(); } + return true; } else if (command.equals("set_keep_screen_bright")) { Window window = this.getWindow(); if (params.equals("on")) { @@ -1893,7 +1894,6 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList Log.w(TAG, "Unknown string command " + command); return false; } - return false; } // Offers a file to other apps through the share sheet. The receiver gets read access to this file only. diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 15bb23ec55..76ccdc3c59 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -120,7 +120,6 @@ jobject Android_GetActivity(JNIEnv *env) { } bool System_AudioRecordingIsAvailable() { return false; } -bool System_AudioRecordingState() { return false; } #endif // Temporary hacks around annoying linking errors. diff --git a/ios/iOSCoreAudio.mm b/ios/iOSCoreAudio.mm index 0a053a1462..a118ca167d 100644 --- a/ios/iOSCoreAudio.mm +++ b/ios/iOSCoreAudio.mm @@ -20,10 +20,9 @@ #include "iOSCoreAudio.h" -#include -#include #include +#include "Common/Audio/SampleRing.h" #include "Common/Log.h" #include "Core/Config.h" #include "Core/HLE/sceUsbMic.h" @@ -44,12 +43,8 @@ static int g_micSampleRate = SAMPLE_RATE; static bool g_micInputOn = false; // What the unit is actually configured with. static int g_micInputRate = 0; -// Single producer (the input callback, on the audio thread), single consumer (the CPU thread, in -// iOSCoreAudioPollRecording). Input that doesn't fit is dropped. -static constexpr uint32_t MIC_RING_SIZE = 32768; // Samples, a power of two. 0.7 s at 44.1 kHz. -static int16_t g_micRing[MIC_RING_SIZE]; -static std::atomic g_micWritePos{0}; -static std::atomic g_micReadPos{0}; +// From the input callback, on the audio thread, to the CPU thread. 0.7 s at 44.1 kHz. +static SampleRing<32768> g_micRing; // Where AudioUnitRender puts the input. The unit's slice limit is set to match, so the audio thread // never has to allocate. @@ -137,13 +132,7 @@ static OSStatus iOSCoreAudioInputCallback(void *inRefCon, } const UInt32 frames = bufferList.mBuffers[0].mDataByteSize / sizeof(int16_t); - const uint32_t write = g_micWritePos.load(std::memory_order_relaxed); - const uint32_t read = g_micReadPos.load(std::memory_order_acquire); - const uint32_t count = std::min((uint32_t)frames, MIC_RING_SIZE - (write - read)); - for (uint32_t i = 0; i < count; i++) { - g_micRing[(write + i) & (MIC_RING_SIZE - 1)] = g_micScratch[i]; - } - g_micWritePos.store(write + count, std::memory_order_release); + g_micRing.Push(g_micScratch, frames); return noErr; } @@ -376,7 +365,7 @@ static void WithRecordPermission(void (^then)(bool granted)) { void iOSCoreAudioStartRecording(int sampleRate) { // Whatever is left over is from before, maybe at another rate. We're the consumer, so we can drop it. - g_micReadPos.store(g_micWritePos.load(std::memory_order_acquire), std::memory_order_release); + g_micRing.Clear(); dispatch_async(dispatch_get_main_queue(), ^{ g_micWanted = true; @@ -404,18 +393,7 @@ void iOSCoreAudioStopRecording() { } void iOSCoreAudioPollRecording() { - const uint32_t read = g_micReadPos.load(std::memory_order_relaxed); - const uint32_t write = g_micWritePos.load(std::memory_order_acquire); - const uint32_t count = write - read; - if (count == 0) { - return; - } - // In up to two pieces, if it wraps around the end of the ring. - const uint32_t start = read & (MIC_RING_SIZE - 1); - const uint32_t first = std::min(count, MIC_RING_SIZE - start); - Microphone::addAudioData((u8 *)&g_micRing[start], first * sizeof(int16_t)); - if (count > first) { - Microphone::addAudioData((u8 *)g_micRing, (count - first) * sizeof(int16_t)); - } - g_micReadPos.store(read + count, std::memory_order_release); + g_micRing.Drain([](const int16_t *samples, uint32_t count) { + Microphone::addAudioData((u8 *)samples, count * sizeof(int16_t)); + }); } diff --git a/ios/main.mm b/ios/main.mm index c40676b6fd..1b14d1ac27 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -657,10 +657,6 @@ bool System_AudioRecordingIsAvailable() { return true; } -bool System_AudioRecordingState() { - return false; // Unused outside Android. -} - PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; } diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index e871544165..ce785a3bb0 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -2028,7 +2028,6 @@ void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\ void System_AudioClear() {} #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) bool System_AudioRecordingIsAvailable() { return false; } -bool System_AudioRecordingState() { return false; } #endif // Stub for now. std::vector System_GetCameraDeviceList() { return std::vector(); } diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index aa4254fe3c..5ace7db54b 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -113,6 +113,7 @@ #include "Core/KeyMap.h" #include "Core/ControlMapper.h" #include "Core/HLE/sceCtrl.h" +#include "Core/HLE/sceUsbMic.h" #include "Core/Util/PathUtil.h" #include "Core/MIPS/MIPSVFPUUtils.h" #include "GPU/Common/DepthSwizzle.h" @@ -184,7 +185,6 @@ jobject Android_GetActivity(JNIEnv *env) { } bool System_AudioRecordingIsAvailable() { return false; } -bool System_AudioRecordingState() { return false; } #endif #ifndef M_PI_2 @@ -3192,6 +3192,30 @@ bool TestUITabOrder() { bool TestTextureReplacer(); +static bool TestQueueBuf() { + const u8 data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + u8 out[16]{}; + + QueueBuf q(8); + q.push(data, 6); + EXPECT_EQ_INT(q.pop(out, 4), 4); + // Wraps around the end, holding 5 6 1 2 3 4 5. + q.push(data, 5); + // Growing has to keep what's buffered, in order. + q.resize(16); + EXPECT_EQ_INT(q.getCapacity(), 16); + EXPECT_EQ_INT(q.pop(out, 16), 7); + const u8 expected[7] = { 5, 6, 1, 2, 3, 4, 5 }; + EXPECT_EQ_MEM(out, expected, sizeof(expected)); + + // Pushing more than the capacity grows it. + QueueBuf small(4); + small.push(data, 8); + EXPECT_EQ_INT(small.pop(out, 16), 8); + EXPECT_EQ_MEM(out, data, sizeof(data)); + return true; +} + TestItem availableTests[] = { #if PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86) TEST_ITEM(Arm64Emitter), @@ -3266,6 +3290,7 @@ TestItem availableTests[] = { TEST_ITEM(UITabOrder), TEST_ITEM(FatShortNames), TEST_ITEM(GEDumpGameID), + TEST_ITEM(QueueBuf), }; int main(int argc, const char *argv[]) {