#define MAX_BUFSIZE_DEFAULT (4096)
#define MAX_BUFSIZE_EXTRA (8192)
#define TARGET_BUFSIZE_MARGIN 512
#define TARGET_BUFSIZE_DEFAULT 1680
#define TARGET_BUFSIZE_EXTRA 3360
#define MAX_FREQ_SHIFT 600.0f
#define CONTROL_FACTOR 0.2f
#define CONTROL_AVG 32.0f
#include "ppsspp_config.h"
#include <algorithm>
#include <cstring>
#include <atomic>
#include "Common/Common.h"
#include "Common/System/System.h"
#include "Common/Log.h"
#include "Common/Math/SIMDHeaders.h"
#include "Common/Math/CrossSIMD.h"
#include "Common/TimeUtil.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/HW/StereoResampler.h"
#include "Core/Util/AudioFormat.h"
#include "Core/System.h"
StereoResampler::StereoResampler() noexcept
: maxBufsize_(MAX_BUFSIZE_DEFAULT)
, targetBufsize_(TARGET_BUFSIZE_DEFAULT) {
buffer_ = new int16_t[MAX_BUFSIZE_EXTRA * 2]();
float refresh = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
if (refresh != 60.0f && refresh > 50.0f && refresh < 70.0f) {
int input_sample_rate = (int)(44100 * (refresh / 60.0f));
INFO_LOG(Log::Audio, "StereoResampler: Adjusting target sample rate to %dHz", input_sample_rate);
inputSampleRateHz_ = input_sample_rate;
}
UpdateBufferSize();
}
StereoResampler::~StereoResampler() {
delete[] buffer_;
buffer_ = nullptr;
}
void StereoResampler::UpdateBufferSize() {
if (g_Config.bExtraAudioBuffering) {
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;
}
}
}
template<bool multiply>
inline void ClampBufferToS16(s16 *out, const s32 *in, size_t size, int factor) {
if (multiply) {
for (size_t i = 0; i < size; i++) {
out[i] = clamp_s16((in[i] * factor) >> 12);
}
} else {
#ifdef _M_SSE
while (size >= 8) {
__m128i in1 = _mm_loadu_si128((__m128i *)in);
__m128i in2 = _mm_loadu_si128((__m128i *)(in + 4));
__m128i packed = _mm_packs_epi32(in1, in2);
_mm_storeu_si128((__m128i *)out, packed);
out += 8;
in += 8;
size -= 8;
}
#elif PPSSPP_ARCH(ARM_NEON)
while (size >= 8) {
int32x4_t in1 = vld1q_s32(in);
int32x4_t in2 = vld1q_s32(in + 4);
int16x4_t packed1 = vqmovn_s32(in1);
int16x4_t packed2 = vqmovn_s32(in2);
vst1_s16(out, packed1);
vst1_s16(out + 4, packed2);
out += 8;
in += 8;
size -= 8;
}
#endif
for (size_t i = 0; i < size; i++) {
out[i] = clamp_s16(in[i]);
}
}
}
inline void ClampBufferToS16WithVolume(s16 *out, const s32 *in, size_t size, int volume) {
if (volume >= 4096) {
ClampBufferToS16<false>(out, in, size, 0);
} else if (volume <= 0) {
memset(out, 0, size * sizeof(s16));
} else {
ClampBufferToS16<true>(out, in, size, volume);
}
}
void StereoResampler::Clear() {
memset(buffer_, 0, maxBufsize_ * 2 * sizeof(int16_t));
}
inline int16_t MixSingleSample(int16_t s1, int16_t s2, uint16_t frac) {
int32_t value = s1 + (((s2 - s1) * frac) >> 16);
if (value < -32767)
return -32767;
else if (value > 32767)
return 32767;
else
return (int16_t)value;
}
void StereoResampler::Mix(s16 *samples, unsigned int numSamples, bool consider_framelimit, int sample_rate) {
if (!samples)
return;
unsigned int currentSample;
u32 indexR = indexR_.load();
u32 indexW = indexW_.load();
const int INDEX_MASK = (maxBufsize_ * 2 - 1);
lastBufSize_ = ((indexW - indexR) & INDEX_MASK) / 2;
float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2);
numLeft -= droppedSamples_;
droppedSamples_ = 0;
numLeftI_ = (numLeft + numLeftI_ * (CONTROL_AVG - 1.0f)) / CONTROL_AVG;
float offset = (numLeftI_ - (float)targetBufsize_) * CONTROL_FACTOR;
if (offset > MAX_FREQ_SHIFT) offset = MAX_FREQ_SHIFT;
if (offset < -MAX_FREQ_SHIFT) offset = -MAX_FREQ_SHIFT;
outputSampleRateHz_ = (float)(inputSampleRateHz_ + offset);
const u32 ratio = (u32)(65536.0 * outputSampleRateHz_ / (double)sample_rate);
ratio_ = ratio;
u32 frac = frac_;
for (currentSample = 0; currentSample < numSamples * 2; currentSample += 2) {
if (((indexW - indexR) & INDEX_MASK) <= 2) {
underrunCount_++;
break;
}
u32 indexR2 = indexR + 2;
s16 l1 = buffer_[indexR & INDEX_MASK];
s16 r1 = buffer_[(indexR + 1) & INDEX_MASK];
s16 l2 = buffer_[indexR2 & INDEX_MASK];
s16 r2 = buffer_[(indexR2 + 1) & INDEX_MASK];
samples[currentSample] = MixSingleSample(l1, l2, (u16)frac);
samples[currentSample + 1] = MixSingleSample(r1, r2, (u16)frac);
frac += ratio;
indexR += 2 * (frac >> 16);
frac &= 0xffff;
}
frac_ = frac;
outputSampleCount_ += currentSample / 2;
short s[2];
s[0] = clamp_s16(buffer_[(indexR - 1) & INDEX_MASK]);
s[1] = clamp_s16(buffer_[(indexR - 2) & INDEX_MASK]);
for (; currentSample < numSamples * 2; currentSample += 2) {
samples[currentSample] = s[0];
samples[currentSample + 1] = s[1];
}
indexR_.store(indexR);
}
void StereoResampler::PushSamples(const s32 *samples, unsigned int numSamples, float multiplier) {
inputSampleCount_ += numSamples;
UpdateBufferSize();
const int INDEX_MASK = (maxBufsize_ * 2 - 1);
u32 indexW = indexW_.load();
u32 cap = maxBufsize_ * 2;
if (PSP_CoreParameter().fastForward) {
cap = targetBufsize_ * 2;
}
if (numSamples * 2 + ((indexW - indexR_.load()) & INDEX_MASK) >= cap) {
if (!PSP_CoreParameter().fastForward) {
overrunCount_++;
}
return;
}
int volume = (int)(multiplier * 4096.0f);
unsigned int indexW_left_samples = maxBufsize_ * 2 - (indexW & INDEX_MASK);
if (numSamples * 2 > indexW_left_samples) {
ClampBufferToS16WithVolume(&buffer_[indexW & INDEX_MASK], samples, indexW_left_samples, volume);
ClampBufferToS16WithVolume(&buffer_[0], samples + indexW_left_samples, numSamples * 2 - indexW_left_samples, volume);
} else {
ClampBufferToS16WithVolume(&buffer_[indexW & INDEX_MASK], samples, numSamples * 2, volume);
}
indexW_ += numSamples * 2;
lastPushSize_ = numSamples;
}
void StereoResampler::GetAudioDebugStats(char *buf, size_t bufSize) {
double elapsed = time_now_d() - startTime_;
double effective_input_sample_rate = (double)inputSampleCount_ / elapsed;
double effective_output_sample_rate = (double)outputSampleCount_ / elapsed;
double bufferLatencyMs = 1000.0 * (double)lastBufSize_ / (double)inputSampleRateHz_;
snprintf(buf, bufSize,
"Audio buffer: %d/%d (%0.1fms, target: %d)\n"
"Filtered: %0.2f\n"
"Underruns: %d\n"
"Overruns: %d\n"
"Sample rate: %d (input: %d)\n"
"Effective input sample rate: %0.2f\n"
"Effective output sample rate: %0.2f\n"
"Push size: %d\n"
"Ratio: %0.6f\n",
lastBufSize_,
maxBufsize_,
bufferLatencyMs,
targetBufsize_,
numLeftI_,
underrunCountTotal_,
overrunCountTotal_,
(int)outputSampleRateHz_,
inputSampleRateHz_,
effective_input_sample_rate,
effective_output_sample_rate,
lastPushSize_,
(float)ratio_ / 65536.0f);
underrunCountTotal_ += underrunCount_;
overrunCountTotal_ += overrunCount_;
underrunCount_ = 0;
overrunCount_ = 0;
}
void StereoResampler::ResetStatCounters() {
underrunCount_ = 0;
overrunCount_ = 0;
underrunCountTotal_ = 0;
overrunCountTotal_ = 0;
inputSampleCount_ = 0;
outputSampleCount_ = 0;
startTime_ = time_now_d();
}