mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 00:12:40 +00:00
audio: drain the AudioOut2 queue at an exact 48 kHz grain rate
This commit is contained in:
1 parent
64a554dea7
commit
7ff8aff5e6
2 files changed
+37
-11
No files matched your search
+12
-9
@@ -262,6 +262,11 @@ static AudioOut2Submission& audioout2_submission(const AudioOut2ContextState& co
|
|||||||
return g_audioout2_submissions[&context - g_audioout2_contexts.data()];
|
return g_audioout2_submissions[&context - g_audioout2_contexts.data()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ticks of a sixth of a microsecond: one 48 kHz sample lasts exactly 125 of them.
|
||||||
|
static uint64_t audioout2_clock() {
|
||||||
|
return LibKernel::KernelGetProcessTime() * 6;
|
||||||
|
}
|
||||||
|
|
||||||
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
||||||
const auto sample_count = (grains == 0 ? 512u : grains);
|
const auto sample_count = (grains == 0 ? 512u : grains);
|
||||||
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
||||||
@@ -331,25 +336,23 @@ static void audioout2_update_context_locked(AudioOut2ContextState* state) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto now = LibKernel::KernelGetProcessTime();
|
const auto now = audioout2_clock();
|
||||||
if (state->last_update == 0 || state->queued == 0) {
|
if (state->last_update == 0 || state->queued == 0) {
|
||||||
state->last_update = now;
|
state->last_update = now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (now <= state->last_update) {
|
||||||
const auto grain_micros = static_cast<uint64_t>(audioout2_grain_micros(state->num_grains));
|
|
||||||
if (now <= state->last_update || grain_micros == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto elapsed = now - state->last_update;
|
const auto grain = uint64_t {state->num_grains} * 125;
|
||||||
const auto drained = std::min<uint64_t>(state->queued, elapsed / grain_micros);
|
const auto drained = std::min<uint64_t>(state->queued, (now - state->last_update) / grain);
|
||||||
if (drained == 0) {
|
if (drained == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->queued -= static_cast<uint32_t>(drained);
|
state->queued -= static_cast<uint32_t>(drained);
|
||||||
state->last_update += drained * grain_micros;
|
state->last_update += drained * grain;
|
||||||
if (state->queued == 0) {
|
if (state->queued == 0) {
|
||||||
state->last_update = now;
|
state->last_update = now;
|
||||||
}
|
}
|
||||||
@@ -527,7 +530,7 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
|||||||
state->handle = *ctx;
|
state->handle = *ctx;
|
||||||
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
||||||
state->num_grains = num_grains;
|
state->num_grains = num_grains;
|
||||||
state->last_update = LibKernel::KernelGetProcessTime();
|
state->last_update = audioout2_clock();
|
||||||
state->object_audio_handle = object_audio_handle;
|
state->object_audio_handle = object_audio_handle;
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
@@ -615,7 +618,7 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
|
|||||||
sleep_micros = audioout2_grain_micros(state->num_grains);
|
sleep_micros = audioout2_grain_micros(state->num_grains);
|
||||||
if (state->queued < state->queue_depth || use_device_clock) {
|
if (state->queued < state->queue_depth || use_device_clock) {
|
||||||
if (state->queued == 0) {
|
if (state->queued == 0) {
|
||||||
state->last_update = LibKernel::KernelGetProcessTime();
|
state->last_update = audioout2_clock();
|
||||||
}
|
}
|
||||||
if (state->queued < state->queue_depth) {
|
if (state->queued < state->queue_depth) {
|
||||||
state->queued++;
|
state->queued++;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ int g_open_waiters = 0;
|
|||||||
bool g_block_opens = false;
|
bool g_block_opens = false;
|
||||||
bool g_block_outputs = false;
|
bool g_block_outputs = false;
|
||||||
std::vector<int> g_blocked_output;
|
std::vector<int> g_blocked_output;
|
||||||
|
std::atomic_uint64_t g_process_time {0};
|
||||||
|
std::atomic_bool g_process_time_frozen {false};
|
||||||
|
|
||||||
void Check(bool value, const char* text) {
|
void Check(bool value, const char* text) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -465,6 +467,27 @@ void TestPushConsumesPcm() {
|
|||||||
AudioOut2::AudioOut2ContextDestroy(context);
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestQueueDrainsAtExactGrainRate() {
|
||||||
|
// Three 512-sample grains play in exactly 32000 us.
|
||||||
|
const auto context = CreateContext();
|
||||||
|
g_process_time_frozen = true;
|
||||||
|
const auto start = g_process_time.load();
|
||||||
|
for (int push = 0; push < 3; push++) {
|
||||||
|
Check(AudioOut2::AudioOut2ContextPush(context, 0) == OK, "async push failed");
|
||||||
|
}
|
||||||
|
const auto queued_after = [&](uint64_t micros) {
|
||||||
|
g_process_time = start + micros;
|
||||||
|
uint32_t queued = 0;
|
||||||
|
Check(AudioOut2::AudioOut2ContextGetQueueLevel(context, &queued, nullptr) == OK,
|
||||||
|
"queue-level query failed");
|
||||||
|
return queued;
|
||||||
|
};
|
||||||
|
Check(queued_after(31999) == 1, "the third grain drained before it finished playing");
|
||||||
|
Check(queued_after(32000) == 0, "three grains did not drain in their playing time");
|
||||||
|
g_process_time_frozen = false;
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
|
}
|
||||||
|
|
||||||
void TestPcmCopiedBeforeScratchBufferReuse() {
|
void TestPcmCopiedBeforeScratchBufferReuse() {
|
||||||
const auto context = CreateContext();
|
const auto context = CreateContext();
|
||||||
const auto param = MakeParam();
|
const auto param = MakeParam();
|
||||||
@@ -746,8 +769,7 @@ uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking)
|
|||||||
namespace Libs::LibKernel {
|
namespace Libs::LibKernel {
|
||||||
|
|
||||||
uint64_t KYTY_SYSV_ABI KernelGetProcessTime() {
|
uint64_t KYTY_SYSV_ABI KernelGetProcessTime() {
|
||||||
static std::atomic_uint64_t now {0};
|
return g_process_time_frozen ? g_process_time.load() : g_process_time.fetch_add(1000);
|
||||||
return now.fetch_add(1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Libs::LibKernel
|
} // namespace Libs::LibKernel
|
||||||
@@ -764,6 +786,7 @@ int main() {
|
|||||||
TestAsynchronousDevicePushKeepsQueueBounded();
|
TestAsynchronousDevicePushKeepsQueueBounded();
|
||||||
TestHandleWithoutPcmDoesNotBypassQueue();
|
TestHandleWithoutPcmDoesNotBypassQueue();
|
||||||
TestPushConsumesPcm();
|
TestPushConsumesPcm();
|
||||||
|
TestQueueDrainsAtExactGrainRate();
|
||||||
TestPcmCopiedBeforeScratchBufferReuse();
|
TestPcmCopiedBeforeScratchBufferReuse();
|
||||||
TestPortGainAndValidation();
|
TestPortGainAndValidation();
|
||||||
TestObjectGainAndAmbisonics();
|
TestObjectGainAndAmbisonics();
|
||||||
|
|||||||
Reference in new issue
Block a user