@@ -178,8 +178,10 @@ class Regression {
178178 constructor ( samples , startIndex , endIndex , options )
179179 {
180180 const desiredFrameLength = options . desiredFrameLength ;
181- var profile ;
181+ const kWindowSizeMultiple = 0.1 ;
182182
183+ var profile ;
184+
183185 if ( ! options . preferredProfile || options . preferredProfile == Strings . json . profiles . slope ) {
184186 profile = this . _calculateRegression ( samples , {
185187 shouldClip : true ,
@@ -195,6 +197,11 @@ class Regression {
195197 t2 : 0
196198 } ) ;
197199 this . profile = Strings . json . profiles . flat ;
200+ } else if ( options . preferredProfile == Strings . json . profiles . window || options . preferredProfile == Strings . json . profiles . windowStrict ) {
201+ const window_size = Math . max ( 1 , Math . floor ( samples . length * kWindowSizeMultiple ) ) ;
202+ const strict = options . preferredProfile == Strings . json . profiles . windowStrict ;
203+ profile = this . _windowedFit ( samples , desiredFrameLength , window_size , strict ) ;
204+ this . profile = options . preferredProfile ;
198205 }
199206
200207 this . startIndex = Math . min ( startIndex , endIndex ) ;
@@ -219,6 +226,111 @@ class Regression {
219226 return this . s1 + this . t1 * complexity ;
220227 }
221228
229+ _windowedFit ( samples , desiredFrameLength , windowSize , strict )
230+ {
231+ const kAllowedErrorFactor = 0.9 ;
232+
233+ const complexityIndex = 0 ;
234+ const frameLengthIndex = 1 ;
235+ const frameTimeIndex = 2 ;
236+
237+ const average = array => array . reduce ( ( a , b ) => a + b ) / array . length ;
238+
239+ var sortedSamples = samples . slice ( ) . sort ( ( a , b ) => {
240+ if ( a [ complexityIndex ] == b [ complexityIndex ] )
241+ return b [ frameTimeIndex ] - a [ frameTimeIndex ] ;
242+ return a [ complexityIndex ] - b [ complexityIndex ] ;
243+ } ) ;
244+
245+ var cumFrameLength = 0.0 ;
246+ var bestIndex = 0 ;
247+ var bestComplexity = 0 ;
248+ var runningFrameLengths = [ ] ;
249+ var runningComplexities = [ ] ;
250+
251+ for ( var i = 0 ; i < sortedSamples . length ; ++ i ) {
252+ runningFrameLengths . push ( sortedSamples [ i ] [ frameLengthIndex ] ) ;
253+ runningComplexities . push ( sortedSamples [ i ] [ complexityIndex ] ) ;
254+
255+ if ( runningFrameLengths . length < windowSize ) {
256+ continue
257+ } else if ( runningFrameLengths . length > windowSize ) {
258+ runningFrameLengths . shift ( ) ;
259+ runningComplexities . shift ( ) ;
260+ }
261+
262+ let averageFrameLength = average ( runningFrameLengths ) ;
263+ let averageComplexity = average ( runningComplexities ) ;
264+ let error = desiredFrameLength / averageFrameLength ;
265+ let adjustedComplexity = averageComplexity * Math . min ( 1.0 , error ) ;
266+
267+ if ( error >= kAllowedErrorFactor ) {
268+ if ( adjustedComplexity > bestComplexity ) {
269+ bestComplexity = adjustedComplexity ;
270+ }
271+ } else if ( strict ) {
272+ break ;
273+ }
274+ }
275+
276+ for ( var i = 0 ; i < sortedSamples . length ; ++ i ) {
277+ if ( sortedSamples [ i ] [ complexityIndex ] <= bestComplexity )
278+ bestIndex = i ;
279+ }
280+
281+ let complexity = bestComplexity ;
282+
283+ // Calculate slope for remaining points
284+ let t_nom = 0.0 ;
285+ let t_denom = 0.0 ;
286+ for ( var i = bestIndex + 1 ; i < sortedSamples . length ; i ++ ) {
287+ const tx = sortedSamples [ i ] [ complexityIndex ] - complexity ;
288+ const ty = sortedSamples [ i ] [ frameLengthIndex ] - desiredFrameLength ;
289+ t_nom += tx * ty ;
290+ t_denom += tx * tx ;
291+ }
292+
293+ var s1 = desiredFrameLength ;
294+ var t1 = 0 ;
295+ var t2 = ( t_nom / t_denom ) || 0.0 ;
296+ var s2 = desiredFrameLength - t2 * complexity ;
297+ var n1 = bestIndex + 1 ;
298+ var n2 = sortedSamples . length - bestIndex - 1 ;
299+
300+ function getValueAt ( at_complexity )
301+ {
302+ if ( at_complexity > complexity )
303+ return s2 + t2 * complexity ;
304+ return s1 + t1 * complexity ;
305+ }
306+
307+ let error1 = 0.0 ;
308+ let error2 = 0.0 ;
309+ for ( var i = 0 ; i < n1 ; ++ i ) {
310+ const frameLengthErr = sortedSamples [ i ] [ frameLengthIndex ] - getValueAt ( sortedSamples [ i ] [ complexityIndex ] ) ;
311+ error1 += frameLengthErr * frameLengthErr ;
312+ }
313+ for ( var i = n1 ; i < sortedSamples . length ; ++ i ) {
314+ const frameLengthErr = sortedSamples [ i ] [ frameLengthIndex ] - getValueAt ( sortedSamples [ i ] [ complexityIndex ] ) ;
315+ error2 += frameLengthErr * frameLengthErr ;
316+ }
317+
318+ return {
319+ s1 : s1 ,
320+ t1 : t1 ,
321+ s2 : s2 ,
322+ t2 : t2 ,
323+ complexity : complexity ,
324+ // Number of samples included in the first segment, inclusive of bestIndex
325+ n1 : n1 ,
326+ // Number of samples included in the second segment
327+ n2 : n2 ,
328+ stdev1 : Math . sqrt ( error1 / n1 ) ,
329+ stdev2 : Math . sqrt ( error2 / n2 ) ,
330+ error : error1 + error2 ,
331+ } ;
332+ }
333+
222334 // A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
223335 //
224336 // Minimize sum of (y - y')^2
@@ -250,7 +362,11 @@ class Regression {
250362 }
251363
252364 // Sort by increasing complexity.
253- var sortedSamples = samples . slice ( ) . sort ( ( a , b ) => a [ complexityIndex ] - b [ complexityIndex ] ) ;
365+ var sortedSamples = samples . slice ( ) . sort ( ( a , b ) => {
366+ if ( a [ complexityIndex ] == b [ complexityIndex ] )
367+ return 0 ;
368+ return a [ complexityIndex ] - b [ complexityIndex ]
369+ } ) ;
254370
255371 // x is expected to increase in complexity
256372 var lowComplexity = sortedSamples [ 0 ] [ complexityIndex ] ;
0 commit comments