mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 03:46:53 +02:00
Capture PCM at set time when multiple AudioOut2 ports share a buffer (#826)
* capture PCM at set time when ports share a buffer * Snapshot size limit and open device check * Fall back to PCM pointer on stale snapshot * audio: own AudioOut2 PCM at attribute set time --------- Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
+22
-12
@@ -188,7 +188,7 @@ struct AudioOut2PortStateEntry {
|
|||||||
uint32_t samples_num = 512;
|
uint32_t samples_num = 512;
|
||||||
AudioInternal::Format audio_format = AudioInternal::Format::Unknown;
|
AudioInternal::Format audio_format = AudioInternal::Format::Unknown;
|
||||||
int audio_handle = 0;
|
int audio_handle = 0;
|
||||||
const void* pcm_data = nullptr;
|
std::vector<uint8_t> pcm_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AudioOut2SpeakerArrayState {
|
struct AudioOut2SpeakerArrayState {
|
||||||
@@ -341,11 +341,17 @@ static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle p
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
||||||
|
const auto bytes_per_sample = (state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
||||||
|
return static_cast<size_t>(state.samples_num) *
|
||||||
|
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
||||||
|
}
|
||||||
|
|
||||||
static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
|
static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
|
||||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||||
for (const auto& state: g_audioout2_ports) {
|
for (const auto& state: g_audioout2_ports) {
|
||||||
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
||||||
state.pcm_data != nullptr && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
|
!state.pcm_data.empty() && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,24 +359,23 @@ static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
|
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
|
||||||
|
// The backend consumes the buffers synchronously, but can block while pacing SDL's queue.
|
||||||
|
// Keep both PCM storage and port handles alive until it returns.
|
||||||
std::vector<AudioInternal::OutputParam> params;
|
std::vector<AudioInternal::OutputParam> params;
|
||||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||||
|
|
||||||
g_audioout2_port_mutex.Lock();
|
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||||
for (const auto& state: g_audioout2_ports) {
|
for (const auto& state: g_audioout2_ports) {
|
||||||
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
||||||
state.pcm_data != nullptr && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
!state.pcm_data.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||||
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data});
|
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data.data()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_audioout2_port_mutex.Unlock();
|
|
||||||
|
|
||||||
if (params.empty()) {
|
if (!params.empty()) {
|
||||||
return;
|
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
|
||||||
|
blocking);
|
||||||
}
|
}
|
||||||
|
|
||||||
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
|
|
||||||
blocking);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void audioout2_close_audio_handle(int audio_handle) {
|
static void audioout2_close_audio_handle(int audio_handle) {
|
||||||
@@ -679,7 +684,12 @@ int KYTY_SYSV_ABI AudioOut2PortSetAttributes(AudioOut2PortHandle port,
|
|||||||
if (has_pcm) {
|
if (has_pcm) {
|
||||||
g_audioout2_port_mutex.Lock();
|
g_audioout2_port_mutex.Lock();
|
||||||
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
|
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
|
||||||
state->pcm_data = pcm_data;
|
if (pcm_data != nullptr && state->audio_format != AudioInternal::Format::Unknown) {
|
||||||
|
const auto* bytes = static_cast<const uint8_t*>(pcm_data);
|
||||||
|
state->pcm_data.assign(bytes, bytes + audioout2_pcm_size(*state));
|
||||||
|
} else {
|
||||||
|
state->pcm_data.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g_audioout2_port_mutex.Unlock();
|
g_audioout2_port_mutex.Unlock();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -16,14 +17,16 @@ namespace {
|
|||||||
|
|
||||||
namespace AudioOut2 = Libs::Audio::AudioOut2;
|
namespace AudioOut2 = Libs::Audio::AudioOut2;
|
||||||
|
|
||||||
std::mutex g_device_mutex;
|
std::mutex g_device_mutex;
|
||||||
std::condition_variable g_device_cv;
|
std::condition_variable g_device_cv;
|
||||||
std::vector<int> g_live_devices;
|
std::vector<int> g_live_devices;
|
||||||
std::vector<int> g_device_backed_handles;
|
std::vector<int> g_device_backed_handles;
|
||||||
std::vector<bool> g_output_blocking;
|
std::vector<bool> g_output_blocking;
|
||||||
int g_next_device = 1;
|
std::vector<std::vector<uint8_t>> g_output_pcm;
|
||||||
int g_open_waiters = 0;
|
size_t g_capture_bytes = 0;
|
||||||
bool g_block_opens = false;
|
int g_next_device = 1;
|
||||||
|
int g_open_waiters = 0;
|
||||||
|
bool g_block_opens = false;
|
||||||
|
|
||||||
void Check(bool value, const char* text) {
|
void Check(bool value, const char* text) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -146,6 +149,17 @@ std::vector<bool> OutputCalls() {
|
|||||||
return g_output_blocking;
|
return g_output_blocking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CaptureOutputPcm(size_t bytes) {
|
||||||
|
std::lock_guard lock(g_device_mutex);
|
||||||
|
g_capture_bytes = bytes;
|
||||||
|
g_output_pcm.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<uint8_t>> OutputPcm() {
|
||||||
|
std::lock_guard lock(g_device_mutex);
|
||||||
|
return g_output_pcm;
|
||||||
|
}
|
||||||
|
|
||||||
void TestSlotReuse() {
|
void TestSlotReuse() {
|
||||||
const auto context = CreateContext();
|
const auto context = CreateContext();
|
||||||
const auto param = MakeParam();
|
const auto param = MakeParam();
|
||||||
@@ -327,6 +341,41 @@ void TestHandleWithoutPcmDoesNotBypassQueue() {
|
|||||||
AudioOut2::AudioOut2ContextDestroy(context);
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestPcmCopiedBeforeScratchBufferReuse() {
|
||||||
|
const auto context = CreateContext();
|
||||||
|
const auto param = MakeParam();
|
||||||
|
AudioOut2::AudioOut2PortHandle first = 0;
|
||||||
|
AudioOut2::AudioOut2PortHandle second = 0;
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &first) == OK,
|
||||||
|
"first port create failed");
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &second) == OK,
|
||||||
|
"second port create failed");
|
||||||
|
|
||||||
|
std::vector<float> scratch(512 * 2);
|
||||||
|
std::vector<float> first_pcm(scratch.size(), 0.25f);
|
||||||
|
std::vector<float> second_pcm(scratch.size(), -0.5f);
|
||||||
|
std::copy(first_pcm.begin(), first_pcm.end(), scratch.begin());
|
||||||
|
SetPcm(first, scratch.data());
|
||||||
|
std::copy(second_pcm.begin(), second_pcm.end(), scratch.begin());
|
||||||
|
SetPcm(second, scratch.data());
|
||||||
|
std::fill(scratch.begin(), scratch.end(), 0.0f);
|
||||||
|
|
||||||
|
const auto pcm_bytes = scratch.size() * sizeof(float);
|
||||||
|
CaptureOutputPcm(pcm_bytes);
|
||||||
|
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "shared-buffer push failed");
|
||||||
|
const auto output = OutputPcm();
|
||||||
|
Check(output.size() == 2, "shared-buffer push did not output both ports");
|
||||||
|
Check(std::memcmp(output[0].data(), first_pcm.data(), pcm_bytes) == 0,
|
||||||
|
"first port lost PCM when scratch buffer was reused");
|
||||||
|
Check(std::memcmp(output[1].data(), second_pcm.data(), pcm_bytes) == 0,
|
||||||
|
"second port lost PCM when scratch buffer was reused");
|
||||||
|
|
||||||
|
CaptureOutputPcm(0);
|
||||||
|
AudioOut2::AudioOut2PortDestroy(first);
|
||||||
|
AudioOut2::AudioOut2PortDestroy(second);
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace Libs::Audio::AudioInternal {
|
namespace Libs::Audio::AudioInternal {
|
||||||
@@ -363,9 +412,15 @@ bool AudioOutHasDevice(int handle) {
|
|||||||
g_device_backed_handles.end();
|
g_device_backed_handles.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool blocking) {
|
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) {
|
||||||
std::lock_guard lock(g_device_mutex);
|
std::lock_guard lock(g_device_mutex);
|
||||||
g_output_blocking.push_back(blocking);
|
g_output_blocking.push_back(blocking);
|
||||||
|
if (g_capture_bytes != 0) {
|
||||||
|
for (uint32_t i = 0; i < num; i++) {
|
||||||
|
const auto* bytes = static_cast<const uint8_t*>(params[i].data);
|
||||||
|
g_output_pcm.emplace_back(bytes, bytes + g_capture_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +444,7 @@ int main() {
|
|||||||
TestFloat12ChannelPortOutputsPcm();
|
TestFloat12ChannelPortOutputsPcm();
|
||||||
TestAsynchronousDevicePushKeepsQueueBounded();
|
TestAsynchronousDevicePushKeepsQueueBounded();
|
||||||
TestHandleWithoutPcmDoesNotBypassQueue();
|
TestHandleWithoutPcmDoesNotBypassQueue();
|
||||||
|
TestPcmCopiedBeforeScratchBufferReuse();
|
||||||
std::printf("AudioOut2PortTests: all cases passed\n");
|
std::printf("AudioOut2PortTests: all cases passed\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user