2 * Quality Control Interfaces
4 * Copyright 2010 Maarten Lankhorst for CodeWeavers
6 * rendering qos functions based on, the original can be found at
7 * gstreamer/libs/gst/base/gstbasesink.c which has copyright notice:
9 * Copyright (C) 2005-2007 Wim Taymans <wim.taymans@gmail.com>
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
29 #include "wine/strmbase.h"
30 #include "strmbase_private.h"
33 #include "wine/debug.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(strmbase_qc
);
39 #define XTIME_FMT "%u.%03u"
40 #define XTIME(u) (int)(u/10000000), (int)((u / 10000)%1000)
42 HRESULT
QualityControlImpl_Create(IPin
*input
, IBaseFilter
*self
, QualityControlImpl
**ppv
)
44 QualityControlImpl
*This
;
45 TRACE("%p, %p, %p\n", input
, self
, ppv
);
46 *ppv
= HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY
,sizeof(QualityControlImpl
));
52 This
->tonotify
= NULL
;
54 This
->current_rstart
= This
->current_rstop
= -1;
55 TRACE("-> %p\n", This
);
59 void QualityControlImpl_Destroy(QualityControlImpl
*This
)
61 HeapFree(GetProcessHeap(),0,This
);
64 static inline QualityControlImpl
*impl_from_IQualityControl(IQualityControl
*iface
)
66 return CONTAINING_RECORD(iface
, QualityControlImpl
, IQualityControl_iface
);
69 HRESULT WINAPI
QualityControlImpl_QueryInterface(IQualityControl
*iface
, REFIID riid
, void **ppv
)
71 QualityControlImpl
*This
= impl_from_IQualityControl(iface
);
72 return IBaseFilter_QueryInterface(This
->self
, riid
, ppv
);
75 ULONG WINAPI
QualityControlImpl_AddRef(IQualityControl
*iface
)
77 QualityControlImpl
*This
= impl_from_IQualityControl(iface
);
78 return IBaseFilter_AddRef(This
->self
);
81 ULONG WINAPI
QualityControlImpl_Release(IQualityControl
*iface
)
83 QualityControlImpl
*This
= impl_from_IQualityControl(iface
);
84 return IBaseFilter_Release(This
->self
);
87 HRESULT WINAPI
QualityControlImpl_Notify(IQualityControl
*iface
, IBaseFilter
*sender
, Quality qm
)
89 QualityControlImpl
*This
= impl_from_IQualityControl(iface
);
92 TRACE("%p %p { 0x%x %u " XTIME_FMT
" " XTIME_FMT
" }\n",
93 This
, sender
, qm
.Type
, qm
.Proportion
,
94 XTIME(qm
.Late
), XTIME(qm
.TimeStamp
));
97 return IQualityControl_Notify(This
->tonotify
, This
->self
, qm
);
101 IPin_ConnectedTo(This
->input
, &to
);
103 IQualityControl
*qc
= NULL
;
104 IPin_QueryInterface(to
, &IID_IQualityControl
, (void**)&qc
);
106 hr
= IQualityControl_Notify(qc
, This
->self
, qm
);
107 IQualityControl_Release(qc
);
116 HRESULT WINAPI
QualityControlImpl_SetSink(IQualityControl
*iface
, IQualityControl
*tonotify
)
118 QualityControlImpl
*This
= impl_from_IQualityControl(iface
);
119 TRACE("%p %p\n", This
, tonotify
);
120 This
->tonotify
= tonotify
;
124 /* Macros copied from gstreamer, weighted average between old average and new ones */
125 #define DO_RUNNING_AVG(avg,val,size) (((val) + ((size)-1) * (avg)) / (size))
127 /* generic running average, this has a neutral window size */
128 #define UPDATE_RUNNING_AVG(avg,val) DO_RUNNING_AVG(avg,val,8)
130 /* the windows for these running averages are experimentally obtained.
131 * possitive values get averaged more while negative values use a small
132 * window so we can react faster to badness. */
133 #define UPDATE_RUNNING_AVG_P(avg,val) DO_RUNNING_AVG(avg,val,16)
134 #define UPDATE_RUNNING_AVG_N(avg,val) DO_RUNNING_AVG(avg,val,4)
136 void QualityControlRender_Start(QualityControlImpl
*This
, REFERENCE_TIME tStart
)
138 TRACE("%p " XTIME_FMT
"\n", This
, XTIME(tStart
));
139 This
->avg_render
= This
->last_in_time
= This
->last_left
= This
->avg_duration
= This
->avg_pt
= -1;
140 This
->clockstart
= tStart
;
141 This
->avg_rate
= -1.0;
142 This
->rendered
= This
->dropped
= 0;
143 This
->is_dropped
= FALSE
;
144 This
->qos_handled
= TRUE
; /* Lie that will be corrected on first adjustment */
148 void QualityControlRender_SetClock(QualityControlImpl
*This
, IReferenceClock
*clock
)
150 TRACE("%p %p\n", This
, clock
);
154 static BOOL
QualityControlRender_IsLate(QualityControlImpl
*This
, REFERENCE_TIME jitter
,
155 REFERENCE_TIME start
, REFERENCE_TIME stop
)
157 REFERENCE_TIME max_lateness
= 200000;
159 TRACE("%p " XTIME_FMT
" " XTIME_FMT
" " XTIME_FMT
"\n",
160 This
, XTIME(jitter
), XTIME(start
), XTIME(stop
));
162 /* we can add a valid stop time */
164 max_lateness
+= stop
;
166 max_lateness
+= start
;
168 /* if the jitter bigger than duration and lateness we are too late */
169 if (start
+ jitter
> max_lateness
) {
170 WARN("buffer is too late %i > %i\n", (int)((start
+ jitter
)/10000), (int)(max_lateness
/10000));
171 /* !!emergency!!, if we did not receive anything valid for more than a
172 * second, render it anyway so the user sees something */
173 if (This
->last_in_time
< 0 ||
174 start
- This
->last_in_time
< 10000000)
176 FIXME("A lot of buffers are being dropped.\n");
177 FIXME("There may be a timestamping problem, or this computer is too slow.\n");
179 This
->last_in_time
= start
;
183 HRESULT
QualityControlRender_WaitFor(QualityControlImpl
*This
, IMediaSample
*sample
, HANDLE ev
)
185 REFERENCE_TIME start
= -1, stop
= -1, jitter
= 0;
187 TRACE("%p %p %p\n", This
, sample
, ev
);
189 This
->current_rstart
= This
->current_rstop
= -1;
190 This
->current_jitter
= 0;
191 if (!This
->clock
|| FAILED(IMediaSample_GetTime(sample
, &start
, &stop
)))
196 IReferenceClock_GetTime(This
->clock
, &now
);
197 now
-= This
->clockstart
;
199 jitter
= now
- start
;
200 if (jitter
<= -10000) {
202 IReferenceClock_AdviseTime(This
->clock
, This
->clockstart
, start
, (HEVENT
)ev
, &cookie
);
203 WaitForSingleObject(ev
, INFINITE
);
204 IReferenceClock_Unadvise(This
->clock
, cookie
);
209 This
->current_rstart
= start
;
210 This
->current_rstop
= stop
> start
? stop
: start
;
211 This
->current_jitter
= jitter
;
212 This
->is_dropped
= QualityControlRender_IsLate(This
, jitter
, start
, stop
);
213 TRACE("Dropped: %i %i %i %i\n", This
->is_dropped
, (int)(start
/10000), (int)(stop
/10000), (int)(jitter
/ 10000));
214 if (This
->is_dropped
) {
216 if (!This
->qos_handled
)
223 void QualityControlRender_DoQOS(QualityControlImpl
*priv
)
225 REFERENCE_TIME start
, stop
, jitter
, pt
, entered
, left
, duration
;
230 if (!priv
->clock
|| priv
->current_rstart
< 0)
233 start
= priv
->current_rstart
;
234 stop
= priv
->current_rstop
;
235 jitter
= priv
->current_jitter
;
238 /* this is the time the buffer entered the sink */
242 entered
= start
+ jitter
;
245 /* this is the time the buffer entered the sink */
246 entered
= start
+ jitter
;
247 /* this is the time the buffer left the sink */
248 left
= start
+ jitter
;
251 /* calculate duration of the buffer */
253 duration
= stop
- start
;
257 /* if we have the time when the last buffer left us, calculate
259 if (priv
->last_left
>= 0) {
260 if (entered
> priv
->last_left
) {
261 pt
= entered
- priv
->last_left
;
269 TRACE("start: " XTIME_FMT
", entered " XTIME_FMT
", left " XTIME_FMT
", pt: " XTIME_FMT
", "
270 "duration " XTIME_FMT
", jitter " XTIME_FMT
"\n", XTIME(start
), XTIME(entered
),
271 XTIME(left
), XTIME(pt
), XTIME(duration
), XTIME(jitter
));
273 TRACE("avg_duration: " XTIME_FMT
", avg_pt: " XTIME_FMT
", avg_rate: %g\n",
274 XTIME(priv
->avg_duration
), XTIME(priv
->avg_pt
), priv
->avg_rate
);
276 /* collect running averages. for first observations, we copy the
278 if (priv
->avg_duration
< 0)
279 priv
->avg_duration
= duration
;
281 priv
->avg_duration
= UPDATE_RUNNING_AVG (priv
->avg_duration
, duration
);
283 if (priv
->avg_pt
< 0)
286 priv
->avg_pt
= UPDATE_RUNNING_AVG (priv
->avg_pt
, pt
);
288 if (priv
->avg_duration
!= 0)
290 (double)priv
->avg_pt
/
291 (double)priv
->avg_duration
;
295 if (priv
->last_left
>= 0) {
296 if (priv
->is_dropped
|| priv
->avg_rate
< 0.0) {
297 priv
->avg_rate
= rate
;
300 priv
->avg_rate
= UPDATE_RUNNING_AVG_N (priv
->avg_rate
, rate
);
302 priv
->avg_rate
= UPDATE_RUNNING_AVG_P (priv
->avg_rate
, rate
);
306 if (priv
->avg_rate
>= 0.0) {
309 /* if we have a valid rate, start sending QoS messages */
310 if (priv
->current_jitter
< 0) {
311 /* make sure we never go below 0 when adding the jitter to the
313 if (priv
->current_rstart
< -priv
->current_jitter
)
314 priv
->current_jitter
= -priv
->current_rstart
;
317 priv
->current_jitter
+= (priv
->current_rstop
- priv
->current_rstart
);
318 q
.Type
= (jitter
> 0 ? Famine
: Flood
);
319 q
.Proportion
= (LONG
)(1000. / priv
->avg_rate
);
320 if (q
.Proportion
< 200)
322 else if (q
.Proportion
> 5000)
324 q
.Late
= priv
->current_jitter
;
325 q
.TimeStamp
= priv
->current_rstart
;
326 TRACE("Late: %i from %i, rate: %g\n", (int)(q
.Late
/10000), (int)(q
.TimeStamp
/10000), 1./priv
->avg_rate
);
327 hr
= IQualityControl_Notify(&priv
->IQualityControl_iface
, priv
->self
, q
);
328 priv
->qos_handled
= hr
== S_OK
;
331 /* record when this buffer will leave us */
332 priv
->last_left
= left
;
336 void QualityControlRender_BeginRender(QualityControlImpl
*This
)
345 IReferenceClock_GetTime(This
->clock
, &This
->start
);
346 TRACE("at: " XTIME_FMT
"\n", XTIME(This
->start
));
349 void QualityControlRender_EndRender(QualityControlImpl
*This
)
351 REFERENCE_TIME elapsed
;
355 if (!This
->clock
|| This
->start
< 0 || FAILED(IReferenceClock_GetTime(This
->clock
, &This
->stop
)))
358 elapsed
= This
->start
- This
->stop
;
361 if (This
->avg_render
< 0)
362 This
->avg_render
= elapsed
;
364 This
->avg_render
= UPDATE_RUNNING_AVG (This
->avg_render
, elapsed
);