mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
Merge pull request #23024 from hrydgard/granular-target-like-classic
Audio: Make smooth audio use similar buffersizes to classic audio. Show stats.
This commit is contained in:
7 files changed
+70
-20
No files matched your search
+1
-1
@@ -1130,7 +1130,7 @@ static constexpr ConfigSetting soundSettings[] = {
|
||||
|
||||
ConfigSetting("FillAudioGaps", SETTING(g_Config, bFillAudioGaps), true, CfgFlag::DEFAULT)
|
||||
.Label(I18NCat::AUDIO, "Fill audio gaps"),
|
||||
ConfigSetting("AudioSyncMode", SETTING(g_Config, iAudioPlaybackMode), (int)AudioSyncMode::CLASSIC_PITCH, CfgFlag::DEFAULT)
|
||||
ConfigSetting("AudioSyncMode", SETTING(g_Config, iAudioPlaybackMode), (int)AudioSyncMode::GRANULAR, CfgFlag::DEFAULT)
|
||||
.Label(I18NCat::AUDIO, "Playback mode").Choices(audioPlaybackModes),
|
||||
|
||||
// Legacy volume settings, these get auto upgraded through default handlers on the new settings. NOTE: Must be before the new ones in the order here.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
@@ -14,6 +15,7 @@
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/HW/Display.h"
|
||||
#include "Core/HW/StereoResampler.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/Util/AudioFormat.h" // for clamp_u16
|
||||
@@ -79,6 +81,7 @@ void GranularMixer::Mix(s16 *samples, u32 num_samples, int outSampleRate, float
|
||||
memset(samples, 0, num_samples * 2 * sizeof(s16));
|
||||
// The estimate is 0 until a second has gone by.
|
||||
frameTimeEstimate_ = 1.0f / std::max(fpsEstimate, 1.0f);
|
||||
outSampleRate_ = outSampleRate;
|
||||
|
||||
smoothedReadSize_ = smoothedReadSize_ == 0 ? num_samples : (smoothedReadSize_ * 0.95f + num_samples * 0.05f);
|
||||
|
||||
@@ -102,13 +105,17 @@ void GranularMixer::Mix(s16 *samples, u32 num_samples, int outSampleRate, float
|
||||
const float fade_in_mul = -std::expm1(-1.0 / (out_sample_rate * FADE_IN_RC));
|
||||
const float fade_out_mul = -std::expm1(-1.0 / (out_sample_rate * FADE_OUT_RC));
|
||||
|
||||
// Calculate the ideal length of the granule queue.
|
||||
// NOTE: We must have enough room here for 20fps games, generating all their audio
|
||||
// in a burst each frame (since we can't force real clock sync). That means 16*3 = 48 or rather 50ms.
|
||||
// However, in case of faster framerates, we should apply some pressure to reduce this. And if real clock sync
|
||||
// is on, we should also be able to get away with a shorter buffer here.
|
||||
// const u32 buffer_size_ms = frameTimeEstimate_ * 44100.0f;
|
||||
const u32 buffer_size_samples = smoothedReadSize_ * 4 + std::llround(frameTimeEstimate_ * inSampleRate);
|
||||
// Calculate the length of the granule queue. Dequeue() cuts the queue back to half when it
|
||||
// goes over, so this is a cap rather than a fill level.
|
||||
// We aim for the same latency as StereoResampler, but must also have room for slow (like 20fps)
|
||||
// games, which generate all their audio in a burst each frame (since we can't force real
|
||||
// clock sync), and keep half the queue above what one output callback reads, or each callback
|
||||
// runs dry. Bluetooth and other non-fast outputs read in large chunks.
|
||||
// One more granule on top, for the two half-overlapping granules being played.
|
||||
const int targetSamples = StereoResampler::TargetBufferSize();
|
||||
const int frameSamples = (int)std::llround(frameTimeEstimate_ * inSampleRate);
|
||||
const int readSamples = (int)(smoothedReadSize_ * inSampleRate / out_sample_rate);
|
||||
const u32 buffer_size_samples = std::max({ targetSamples, frameSamples + targetSamples / 2, readSamples * 2 }) + GRANULE_SIZE;
|
||||
queuedSamplesTarget_ = buffer_size_samples;
|
||||
|
||||
// Limit the possible queue sizes to any number between 4 and 64.
|
||||
@@ -304,3 +311,35 @@ void GranularMixer::GetStats(GranularStats *stats) {
|
||||
queuedGranulesMin_ = 10000;
|
||||
queuedGranulesMax_ = 0;
|
||||
}
|
||||
|
||||
void GranularMixer::GetAudioDebugStats(char *buf, size_t bufSize) {
|
||||
GranularStats stats;
|
||||
GetStats(&stats);
|
||||
// Each queued granule holds GRANULE_OVERLAP new samples.
|
||||
const double msPerGranule = GRANULE_OVERLAP * 1000.0 / 44100.0;
|
||||
snprintf(buf, bufSize,
|
||||
"Granular mixer\n"
|
||||
"Queued: %0.1f granules (%0.1fms)\n"
|
||||
"Queued min/max: %d / %d\n"
|
||||
"Cap: %d granules (%0.1fms, %d samples)\n"
|
||||
"Underruns: %d\n"
|
||||
"Overruns: %d\n"
|
||||
"Output sample rate: %d\n"
|
||||
"Read size: %0.1f\n"
|
||||
"Frame time estimate: %0.1fms\n"
|
||||
"Fade volume: %0.2f%s\n",
|
||||
stats.smoothedQueuedGranules, stats.smoothedQueuedGranules * msPerGranule,
|
||||
stats.queuedGranulesMin, stats.queuedGranulesMax,
|
||||
stats.targetQueueSize, stats.targetQueueSize * msPerGranule, stats.queuedSamplesTarget,
|
||||
stats.underruns,
|
||||
stats.overruns,
|
||||
outSampleRate_,
|
||||
stats.smoothedReadSize,
|
||||
stats.frameTimeEstimate * 1000.0f,
|
||||
stats.fadeVolume, stats.looping ? " (looping)" : "");
|
||||
}
|
||||
|
||||
void GranularMixer::ResetStatCounters() {
|
||||
underruns_ = 0;
|
||||
overruns_ = 0;
|
||||
}
|
||||
@@ -46,6 +46,9 @@ public:
|
||||
void PushSamples(const s32 *samples, u32 num_samples, float volume);
|
||||
|
||||
void GetStats(GranularStats *stats);
|
||||
// Text for the audio debug overlay.
|
||||
void GetAudioDebugStats(char *buf, size_t bufSize);
|
||||
void ResetStatCounters();
|
||||
|
||||
static constexpr u32 GRANULE_SIZE = 256;
|
||||
|
||||
@@ -107,6 +110,7 @@ private:
|
||||
int queuedSamplesTarget_ = 0;
|
||||
float smoothedReadSize_ = 0.0f;
|
||||
float frameTimeEstimate_ = 0.0f;
|
||||
int outSampleRate_ = 0;
|
||||
|
||||
void Enqueue();
|
||||
void Dequeue(Granule* granule);
|
||||
|
||||
+13
-10
@@ -74,20 +74,23 @@ StereoResampler::~StereoResampler() {
|
||||
buffer_ = nullptr;
|
||||
}
|
||||
|
||||
void StereoResampler::UpdateBufferSize() {
|
||||
int StereoResampler::TargetBufferSize() {
|
||||
if (g_Config.bExtraAudioBuffering) {
|
||||
return TARGET_BUFSIZE_EXTRA;
|
||||
}
|
||||
int systemBufsize = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER);
|
||||
if (systemBufsize > 0 && TARGET_BUFSIZE_DEFAULT < systemBufsize + TARGET_BUFSIZE_MARGIN) {
|
||||
return std::min(4096, systemBufsize + TARGET_BUFSIZE_MARGIN);
|
||||
}
|
||||
return TARGET_BUFSIZE_DEFAULT;
|
||||
}
|
||||
|
||||
void StereoResampler::UpdateBufferSize() {
|
||||
targetBufsize_ = TargetBufferSize();
|
||||
if (g_Config.bExtraAudioBuffering || targetBufsize_ * 2 > MAX_BUFSIZE_DEFAULT) {
|
||||
maxBufsize_ = MAX_BUFSIZE_EXTRA;
|
||||
targetBufsize_ = TARGET_BUFSIZE_EXTRA;
|
||||
} else {
|
||||
maxBufsize_ = MAX_BUFSIZE_DEFAULT;
|
||||
targetBufsize_ = TARGET_BUFSIZE_DEFAULT;
|
||||
|
||||
int systemBufsize = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER);
|
||||
if (systemBufsize > 0 && targetBufsize_ < systemBufsize + TARGET_BUFSIZE_MARGIN) {
|
||||
targetBufsize_ = std::min(4096, systemBufsize + TARGET_BUFSIZE_MARGIN);
|
||||
if (targetBufsize_ * 2 > MAX_BUFSIZE_DEFAULT)
|
||||
maxBufsize_ = MAX_BUFSIZE_EXTRA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ public:
|
||||
void GetAudioDebugStats(char *buf, size_t bufSize);
|
||||
void ResetStatCounters();
|
||||
|
||||
// The buffer fill to aim for, in 44.1 kHz stereo frames. GranularMixer sizes its queue from this too.
|
||||
static int TargetBufferSize();
|
||||
|
||||
private:
|
||||
void UpdateBufferSize();
|
||||
|
||||
|
||||
+2
-1
@@ -47,12 +47,13 @@ void NativeMix(int16_t *outStereo, int numFrames, int sampleRateHz, void *userda
|
||||
void System_AudioGetDebugStats(char *buf, size_t bufSize) {
|
||||
if (buf) {
|
||||
if (g_Config.iAudioPlaybackMode == (int)AudioSyncMode::GRANULAR) {
|
||||
snprintf(buf, bufSize, "(No stats available for granular yet)");
|
||||
g_granular.GetAudioDebugStats(buf, bufSize);
|
||||
} else {
|
||||
g_resampler.GetAudioDebugStats(buf, bufSize);
|
||||
}
|
||||
} else {
|
||||
g_resampler.ResetStatCounters();
|
||||
g_granular.ResetStatCounters();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1717,7 +1717,7 @@ void DrawAudioOut(ImConfig &cfg, ImControl &control) {
|
||||
ImGui::Text("Queued (smooth): %0.3f", stats.smoothedQueuedGranules);
|
||||
ImGui::Text("Target: %d", stats.targetQueueSize);
|
||||
ImGui::Text("Max: %d", stats.maxQueuedGranules);
|
||||
ImGui::Text("Computed queue latency: %d ms", (int)(stats.smoothedQueuedGranules * (GranularMixer::GRANULE_SIZE * 1000.0 / 44100.0)));
|
||||
ImGui::Text("Computed queue latency: %d ms", (int)(stats.smoothedQueuedGranules * (GranularMixer::GRANULE_SIZE / 2 * 1000.0 / 44100.0)));
|
||||
ImGui::Text("Fade volume: %0.2f", stats.fadeVolume);
|
||||
ImGui::Text("Looping: %s", BoolStr(stats.looping));
|
||||
ImGui::Text("Under/Over: %d / %d", stats.underruns, stats.overruns);
|
||||
|
||||
Reference in new issue
Block a user