Merge pull request #22492 from hrydgard/android-oboe-mic

Android: Hand microphone input over on the CPU thread, like the other hosts
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-10 19:23:59 +02:00
commit 8e407deb90
18 files changed
+171 -124

No files matched your search

+71
View File
@@ -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 <algorithm>
#include <atomic>
#include <cstdint>
// 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 <uint32_t SIZE>
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 <class F>
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<uint32_t> writePos_{0};
std::atomic<uint32_t> readPos_{0};
};
+1
View File
@@ -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
+1
View File
@@ -381,6 +381,7 @@
<ClInclude Include="ArmCommon.h" />
<ClInclude Include="ArmEmitter.h" />
<ClInclude Include="Audio\AudioBackend.h" />
<ClInclude Include="Audio\SampleRing.h" />
<ClInclude Include="BitScan.h" />
<ClInclude Include="BitSet.h" />
<ClInclude Include="Buffer.h" />
+3
View File
@@ -692,6 +692,9 @@
<ClInclude Include="Audio\AudioBackend.h">
<Filter>Audio</Filter>
</ClInclude>
<ClInclude Include="Audio\SampleRing.h">
<Filter>Audio</Filter>
</ClInclude>
<ClInclude Include="LoongArch64Emitter.h" />
<ClInclude Include="Render\AtlasGen.h">
<Filter>Render</Filter>
-1
View File
@@ -330,7 +330,6 @@ void System_Notify(SystemNotification notification);
std::vector<std::string> 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 = "");
+12 -32
View File
@@ -17,7 +17,6 @@
#include "ppsspp_config.h"
#include <algorithm>
#include <mutex>
#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<MicWaitInfo> 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<u32>({ 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::vector<u32> *>(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<u32> *param = new std::vector<u32>({ sampleRate, 1 });
Microphone::startMic(param);
Microphone::startMic();
}
if (Microphone::availableAudioBufSize() > 0) {
+1 -7
View File
@@ -18,7 +18,6 @@
#pragma once
#include "sceKernel.h"
#include <mutex>
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<std::string> getDeviceList();
void onMicDeviceChange();
// Deprecated.
bool isNeedInput();
}
u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool block = true);
+8 -34
View File
@@ -24,10 +24,9 @@
#include <audioclient.h>
#include <wrl/client.h>
#include <algorithm>
#include <atomic>
#include <thread>
#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<uint32_t> g_micWritePos{0};
static std::atomic<uint32_t> 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<IMMDevice> 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() {
+17 -5
View File
@@ -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) {
+6 -1
View File
@@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <string>
#include <mutex>
@@ -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);
+2 -2
View File
@@ -6,7 +6,6 @@
#include <oboe/Oboe.h>
#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;
}
+14 -4
View File
@@ -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:
@@ -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.
-1
View File
@@ -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.
+8 -30
View File
@@ -20,10 +20,9 @@
#include "iOSCoreAudio.h"
#include <algorithm>
#include <atomic>
#include <cstring>
#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<uint32_t> g_micWritePos{0};
static std::atomic<uint32_t> 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));
});
}
-4
View File
@@ -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;
}
-1
View File
@@ -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<std::string> System_GetCameraDeviceList() { return std::vector<std::string>(); }
+26 -1
View File
@@ -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[]) {