@@ -178,8 +178,10 @@ class Regression {
178
178
constructor ( samples , startIndex , endIndex , options )
179
179
{
180
180
const desiredFrameLength = options . desiredFrameLength ;
181
- var profile ;
181
+ const kWindowSizeMultiple = 0.1 ;
182
182
183
+ var profile ;
184
+
183
185
if ( ! options . preferredProfile || options . preferredProfile == Strings . json . profiles . slope ) {
184
186
profile = this . _calculateRegression ( samples , {
185
187
shouldClip : true ,
@@ -195,6 +197,11 @@ class Regression {
195
197
t2 : 0
196
198
} ) ;
197
199
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 ;
198
205
}
199
206
200
207
this . startIndex = Math . min ( startIndex , endIndex ) ;
@@ -219,6 +226,111 @@ class Regression {
219
226
return this . s1 + this . t1 * complexity ;
220
227
}
221
228
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
+
222
334
// A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
223
335
//
224
336
// Minimize sum of (y - y')^2
@@ -250,7 +362,11 @@ class Regression {
250
362
}
251
363
252
364
// 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
+ } ) ;
254
370
255
371
// x is expected to increase in complexity
256
372
var lowComplexity = sortedSamples [ 0 ] [ complexityIndex ] ;
0 commit comments