SPU2X: New stretcher: hopefully works better than r4834 (same goals as r4834: better resiliance to very high/low fps, compared to r4820).

I'm not fully happy with it, as it's a bit complicated for my taste, so feedback about how it performs will be appreciated.

git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4840 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
avihal@gmail.com 2011-08-02 18:48:48 +00:00
parent 34569047b4
commit 36fc051238
1 changed files with 87 additions and 68 deletions

View File

@ -81,19 +81,22 @@ float SndBuffer::GetStatusPct()
// These params were tested to show good respond and stability, on all audio systems (dsound, wav, port audio, xaudio2),
// even at extreme small latency of 50ms which can handle 50%-100% variations without audible glitches.
int targetIPS=750;
//running average can be implemented in O(1) time.
//For the sake of simplicity, this average is calculated in O(<buffer-size>). Possibly improve later.
#define STRETCH_AVERAGE_LEN 50
#define MAX_STRETCH_AVERAGE_LEN 100
int STRETCH_AVERAGE_LEN=50.0 *targetIPS/750;
//adds a value to the running average buffer, and return new running average.
float addToAvg(float val){
static float avg_fullness[STRETCH_AVERAGE_LEN]={0};
static float avg_fullness[MAX_STRETCH_AVERAGE_LEN]={0};
static int nextAvgPos=0;
avg_fullness[nextAvgPos]=val;
nextAvgPos=(nextAvgPos+1)%STRETCH_AVERAGE_LEN;
float sum=0;
for(int i=0; i<STRETCH_AVERAGE_LEN; i++)
for(int c=0, i=(nextAvgPos-1)%STRETCH_AVERAGE_LEN; c<STRETCH_AVERAGE_LEN; c++, i=(i+STRETCH_AVERAGE_LEN-1)%STRETCH_AVERAGE_LEN)
sum+=avg_fullness[i];
sum= (float)sum/(float)STRETCH_AVERAGE_LEN;
@ -110,91 +113,107 @@ T clamp(T val, T min, T max){
//actual stretch algorithm implementation
void SndBuffer::UpdateTempoChangeSoundTouch2()
{
//base aim at buffer filled %
float baseTargetFullness=0.05;
//threshold params (hysteresis)
static const float hys_ok_factor=1.03;
static const int hys_min_ok_count=100; //consecutive iterations within hys_ok before going to 1:1 mode
static const float hys_bad_factor=1.2;
long targetSamplesReservoir=48*SndOutLatencyMS;//48000*SndOutLatencyMS/1000
//base aim at buffer filled %
float baseTargetFullness=(double)targetSamplesReservoir;///(double)m_size;//0.05;
//state vars
static bool inside_hysteresis=false;
static int hys_ok_count=0;
static float dynamicTargetFullness=baseTargetFullness;
//some precalculated values
static const float hys_ok_min=1.0/hys_ok_factor;
static const float hys_ok_max=hys_ok_factor;
static const float hys_bad_min=1.0/hys_bad_factor;
static const float hys_bad_max=hys_bad_factor;
float bufferFullness=(float)m_data;///(float)m_size;
float bufferFullness=(float)m_data/(float)m_size;
static float last_bufferFullness=0;
if(last_bufferFullness != bufferFullness){// only recalculate if buffer changes
last_bufferFullness = bufferFullness;
float tempoAdjust=bufferFullness/dynamicTargetFullness;
float avgerage = addToAvg(tempoAdjust);
tempoAdjust = avgerage;
tempoAdjust = clamp( tempoAdjust, 0.05f, 10.0f);
dynamicTargetFullness += (baseTargetFullness/tempoAdjust - dynamicTargetFullness)/50.0;
if(
tempoAdjust == clamp(tempoAdjust, 0.9f, 1.1f)
&& dynamicTargetFullness == clamp( dynamicTargetFullness, baseTargetFullness*0.9f, baseTargetFullness*1.1f)
)
dynamicTargetFullness=baseTargetFullness;
if( !inside_hysteresis )
{
if( tempoAdjust == clamp( tempoAdjust, hys_ok_min, hys_ok_max ) )
hys_ok_count++;
else
hys_ok_count=0;
if( hys_ok_count >= hys_min_ok_count ){
inside_hysteresis=true;
if(MsgOverruns()) printf("======> stretch: None (1:1)\n");
{//test current iterations/sec every 0.5s, and change algo params accordingly if different than previous IPS more than 30%
static long iters=0;
static wxDateTime last=wxDateTime::UNow();
wxDateTime unow=wxDateTime::UNow();
wxTimeSpan delta = unow.Subtract(last);
if( delta.GetMilliseconds()>500 ){
int pot_targetIPS=1000.0/delta.GetMilliseconds().ToDouble()*iters;
if(pot_targetIPS != clamp(pot_targetIPS, int((float)targetIPS/1.3f), int((float)targetIPS*1.3f)) ){
if(MsgOverruns()) printf("Stretcher: setting iters/sec from %d to %d\n", targetIPS, pot_targetIPS);
targetIPS=pot_targetIPS;
STRETCH_AVERAGE_LEN=clamp((int)(50.0f *(float)targetIPS/750.0f), 3, MAX_STRETCH_AVERAGE_LEN);
}
}
else if( tempoAdjust != clamp( tempoAdjust, hys_bad_min, hys_bad_max ) ){
if(MsgOverruns()) printf("~~~~~~> stretch: Dynamic\n");
inside_hysteresis=false;
hys_ok_count=0;
last=unow;
iters=0;
}
iters++;
}
if(inside_hysteresis)
tempoAdjust=1.0;
//Algorithm params: (threshold params (hysteresis), etc)
const float hys_ok_factor=1.04;
int hys_min_ok_count=clamp((int)(50.0 *(float)targetIPS/750.0), 2, 100); //consecutive iterations within hys_ok before going to 1:1 mode
const float hys_bad_factor=1.2;
int compensationDivider=clamp((int)(100.0 *(float)targetIPS/750), 15, 150);
if(MsgOverruns()){
static int iters=0;
static wxDateTime last=wxDateTime::UNow();
wxDateTime unow=wxDateTime::UNow();
wxTimeSpan delta = unow.Subtract(last);
float tempoAdjust=bufferFullness/dynamicTargetFullness;
float avgerage = addToAvg(tempoAdjust);
tempoAdjust = avgerage;
tempoAdjust = clamp( tempoAdjust, 0.05f, 10.0f);
if(delta.GetMilliseconds()>1000){//report buffers state and tempo adjust every second
printf("buffers[->%.2f]: %.3f (%3.0f%%), tempo adjust: %f, compensated target: %.3f, iterations: %d\n",
(double)baseTargetFullness, (double)bufferFullness, (double)(100.0*bufferFullness/baseTargetFullness), (double)tempoAdjust, (double)dynamicTargetFullness, iters);
last=unow;
iters=0;
}
iters++;
}
pSoundTouch->setTempo(tempoAdjust);
dynamicTargetFullness += (baseTargetFullness/tempoAdjust - dynamicTargetFullness)/(double)compensationDivider;
if(
tempoAdjust == clamp(tempoAdjust, 0.9f, 1.1f)
&& dynamicTargetFullness == clamp( dynamicTargetFullness, baseTargetFullness*0.9f, baseTargetFullness*1.1f)
)
dynamicTargetFullness=baseTargetFullness;
//collect some stats...
if(tempoAdjust==1.0)
ts_stats_normalblocks++;
if( !inside_hysteresis )
{
if( tempoAdjust == clamp( tempoAdjust, 1.0f/hys_ok_factor, hys_ok_factor ) )
hys_ok_count++;
else
ts_stats_stretchblocks++;
hys_ok_count=0;
if( hys_ok_count >= hys_min_ok_count ){
inside_hysteresis=true;
if(MsgOverruns()) printf("======> stretch: None (1:1)\n");
}
}
else if( tempoAdjust != clamp( tempoAdjust, 1.0f/hys_bad_factor, hys_bad_factor ) ){
if(MsgOverruns()) printf("~~~~~~> stretch: Dynamic\n");
inside_hysteresis=false;
hys_ok_count=0;
}
if(inside_hysteresis)
tempoAdjust=1.0;
if(MsgOverruns()){
static int iters=0;
static wxDateTime last=wxDateTime::UNow();
wxDateTime unow=wxDateTime::UNow();
wxTimeSpan delta = unow.Subtract(last);
if(delta.GetMilliseconds()>1000){//report buffers state and tempo adjust every second
printf("buffers: %4d ms (%3.0f%%), tempo: %f, comp: %2.3f, iters: %d, (N-IPS:%d -> avg:%d, minokc:%d, div:%d)\n",
(int)(m_data/48), (double)(100.0*bufferFullness/baseTargetFullness), (double)tempoAdjust, (double)(dynamicTargetFullness/baseTargetFullness), iters, (int)targetIPS
, STRETCH_AVERAGE_LEN, hys_min_ok_count, compensationDivider
);
last=unow;
iters=0;
}
iters++;
}
pSoundTouch->setTempo(tempoAdjust);
//collect some unuseful stats...
if(tempoAdjust==1.0)
ts_stats_normalblocks++;
else
ts_stats_stretchblocks++;
return;
}
void SndBuffer::UpdateTempoChangeSoundTouch()
{
float statusPct = GetStatusPct();
@ -436,7 +455,7 @@ void SndBuffer::timeStretchWrite()
if( MsgOverruns() )
{
if( progress )
if( progress && (ts_stats_normalblocks + ts_stats_stretchblocks))
{
if( ++ts_stats_logcounter > 150 )
{