mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-15 15:30:29 +02:00
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:
18 files changed
+171
-124
No files matched your search
@@ -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};
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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));
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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[]) {
|
||||
|
||||
Reference in new issue
Block a user