File: | tests/video-timer.c |
Warning: | line 335, column 15 Value stored to 'pending_frame' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | #include <math.h> |
2 | #include <ctk/ctk.h> |
3 | |
4 | #include "variable.h" |
5 | |
6 | typedef struct { |
7 | gdouble angle; |
8 | gint64 stream_time; |
9 | gint64 clock_time; |
10 | gint64 frame_counter; |
11 | } FrameData; |
12 | |
13 | static FrameData *displayed_frame; |
14 | static CtkWidget *window; |
15 | static GList *past_frames; |
16 | static Variable latency_error = VARIABLE_INIT{ 0.0, 0.0, 0.0 }; |
17 | static Variable time_factor_stats = VARIABLE_INIT{ 0.0, 0.0, 0.0 }; |
18 | static int dropped_frames = 0; |
19 | static int n_frames = 0; |
20 | |
21 | static gboolean pll; |
22 | static int fps = 24; |
23 | |
24 | /* Thread-safe frame queue */ |
25 | |
26 | #define MAX_QUEUE_LENGTH5 5 |
27 | |
28 | static GQueue *frame_queue; |
29 | static GMutex frame_mutex; |
30 | static GCond frame_cond; |
31 | |
32 | static void |
33 | queue_frame (FrameData *frame_data) |
34 | { |
35 | g_mutex_lock (&frame_mutex); |
36 | |
37 | while (frame_queue->length == MAX_QUEUE_LENGTH5) |
38 | g_cond_wait (&frame_cond, &frame_mutex); |
39 | |
40 | g_queue_push_tail (frame_queue, frame_data); |
41 | |
42 | g_mutex_unlock (&frame_mutex); |
43 | } |
44 | |
45 | static FrameData * |
46 | unqueue_frame (void) |
47 | { |
48 | FrameData *frame_data; |
49 | |
50 | g_mutex_lock (&frame_mutex); |
51 | |
52 | if (frame_queue->length > 0) |
53 | { |
54 | frame_data = g_queue_pop_head (frame_queue); |
55 | g_cond_signal (&frame_cond); |
56 | } |
57 | else |
58 | { |
59 | frame_data = NULL((void*)0); |
60 | } |
61 | |
62 | g_mutex_unlock (&frame_mutex); |
63 | |
64 | return frame_data; |
65 | } |
66 | |
67 | static FrameData * |
68 | peek_pending_frame (void) |
69 | { |
70 | FrameData *frame_data; |
71 | |
72 | g_mutex_lock (&frame_mutex); |
73 | |
74 | if (frame_queue->head) |
75 | frame_data = frame_queue->head->data; |
76 | else |
77 | frame_data = NULL((void*)0); |
78 | |
79 | g_mutex_unlock (&frame_mutex); |
80 | |
81 | return frame_data; |
82 | } |
83 | |
84 | static FrameData * |
85 | peek_next_frame (void) |
86 | { |
87 | FrameData *frame_data; |
88 | |
89 | g_mutex_lock (&frame_mutex); |
90 | |
91 | if (frame_queue->head && frame_queue->head->next) |
92 | frame_data = frame_queue->head->next->data; |
93 | else |
94 | frame_data = NULL((void*)0); |
95 | |
96 | g_mutex_unlock (&frame_mutex); |
97 | |
98 | return frame_data; |
99 | } |
100 | |
101 | /* Frame producer thread */ |
102 | |
103 | static gpointer |
104 | create_frames_thread (gpointer data G_GNUC_UNUSED__attribute__ ((__unused__))) |
105 | { |
106 | int frame_count = 0; |
107 | |
108 | while (TRUE(!(0))) |
109 | { |
110 | FrameData *frame_data = g_slice_new0 (FrameData)((FrameData*) g_slice_alloc0 (sizeof (FrameData))); |
111 | frame_data->angle = 2 * M_PI3.14159265358979323846 * (frame_count % fps) / (double)fps; |
112 | frame_data->stream_time = (G_GINT64_CONSTANT (1000000)(1000000L) * frame_count) / fps; |
113 | |
114 | queue_frame (frame_data); |
115 | frame_count++; |
116 | } |
117 | |
118 | return NULL((void*)0); |
119 | } |
120 | |
121 | /* Clock management: |
122 | * |
123 | * The logic here, which is activated by the --pll argument |
124 | * demonstrates adjusting the playback rate so that the frames exactly match |
125 | * when they are displayed both frequency and phase. If there was an |
126 | * accompanying audio track, you would need to resample the audio to match |
127 | * the clock. |
128 | * |
129 | * The algorithm isn't exactly a PLL - I wrote it first that way, but |
130 | * it oscillicated before coming into sync and this approach was easier than |
131 | * fine-tuning the PLL filter. |
132 | * |
133 | * A more complicated algorithm could also establish sync when the playback |
134 | * rate isn't exactly an integral divisor of the VBlank rate, such as 24fps |
135 | * video on a 60fps display. |
136 | */ |
137 | #define PRE_BUFFER_TIME500000 500000 |
138 | |
139 | static gint64 stream_time_base; |
140 | static gint64 clock_time_base; |
141 | static double time_factor = 1.0; |
142 | static double frequency_time_factor = 1.0; |
143 | static double phase_time_factor = 1.0; |
144 | |
145 | static gint64 |
146 | stream_time_to_clock_time (gint64 stream_time) |
147 | { |
148 | return clock_time_base + (stream_time - stream_time_base) * time_factor; |
149 | } |
150 | |
151 | static void |
152 | adjust_clock_for_phase (gint64 frame_clock_time, |
153 | gint64 presentation_time) |
154 | { |
155 | static gint count = 0; |
156 | static gint64 previous_frame_clock_time; |
157 | static gint64 previous_presentation_time; |
158 | gint64 phase = presentation_time - frame_clock_time; |
159 | |
160 | count++; |
161 | if (count >= fps) /* Give a second of warmup */ |
162 | { |
163 | gint64 time_delta = frame_clock_time - previous_frame_clock_time; |
164 | gint64 previous_phase = previous_presentation_time - previous_frame_clock_time; |
165 | |
166 | double expected_phase_delta; |
167 | |
168 | stream_time_base += (frame_clock_time - clock_time_base) / time_factor; |
169 | clock_time_base = frame_clock_time; |
170 | |
171 | expected_phase_delta = time_delta * (1 - phase_time_factor); |
172 | |
173 | /* If the phase is increasing that means the computed clock times are |
174 | * increasing too slowly. We increase the frequency time factor to compensate, |
175 | * but decrease the compensation so that it takes effect over 1 second to |
176 | * avoid jitter */ |
177 | frequency_time_factor += (phase - previous_phase - expected_phase_delta) / (double)time_delta / fps; |
178 | |
179 | /* We also want to increase or decrease the frequency to bring the phase |
180 | * into sync. We do that again so that the phase should sync up over 1 seconds |
181 | */ |
182 | phase_time_factor = 1 + phase / 2000000.; |
183 | |
184 | time_factor = frequency_time_factor * phase_time_factor; |
185 | } |
186 | |
187 | previous_frame_clock_time = frame_clock_time; |
188 | previous_presentation_time = presentation_time; |
189 | } |
190 | |
191 | /* Drawing */ |
192 | |
193 | static gboolean |
194 | on_window_draw (CtkWidget *widget, |
195 | cairo_t *cr) |
196 | { |
197 | CdkRectangle allocation; |
198 | double cx, cy, r; |
199 | |
200 | cairo_set_source_rgb (cr, 1., 1., 1.); |
201 | cairo_paint (cr); |
202 | |
203 | cairo_set_source_rgb (cr, 0., 0., 0.); |
204 | ctk_widget_get_allocation (widget, &allocation); |
205 | |
206 | cx = allocation.width / 2.; |
207 | cy = allocation.height / 2.; |
208 | r = MIN (allocation.width, allocation.height)(((allocation.width) < (allocation.height)) ? (allocation. width) : (allocation.height)) / 2.; |
209 | |
210 | cairo_arc (cr, cx, cy, r, |
211 | 0, 2 * M_PI3.14159265358979323846); |
212 | cairo_stroke (cr); |
213 | if (displayed_frame) |
214 | { |
215 | cairo_move_to (cr, cx, cy); |
216 | cairo_line_to (cr, |
217 | cx + r * cos(displayed_frame->angle - M_PI3.14159265358979323846 / 2), |
218 | cy + r * sin(displayed_frame->angle - M_PI3.14159265358979323846 / 2)); |
219 | cairo_stroke (cr); |
220 | |
221 | if (displayed_frame->frame_counter == 0) |
222 | { |
223 | CdkFrameClock *frame_clock = ctk_widget_get_frame_clock (window); |
224 | displayed_frame->frame_counter = cdk_frame_clock_get_frame_counter (frame_clock); |
225 | } |
226 | } |
227 | |
228 | return FALSE(0); |
229 | } |
230 | |
231 | static void |
232 | collect_old_frames (void) |
233 | { |
234 | CdkFrameClock *frame_clock = ctk_widget_get_frame_clock (window); |
235 | GList *l, *l_next; |
236 | |
237 | for (l = past_frames; l; l = l_next) |
238 | { |
239 | FrameData *frame_data = l->data; |
240 | CdkFrameTimings *timings; |
241 | gboolean remove = FALSE(0); |
242 | l_next = l->next; |
243 | |
244 | timings = cdk_frame_clock_get_timings (frame_clock, |
245 | frame_data->frame_counter); |
246 | if (timings == NULL((void*)0)) |
247 | { |
248 | remove = TRUE(!(0)); |
249 | } |
250 | else if (cdk_frame_timings_get_complete (timings)) |
251 | { |
252 | gint64 presentation_time = cdk_frame_timings_get_predicted_presentation_time (timings); |
253 | gint64 refresh_interval = cdk_frame_timings_get_refresh_interval (timings); |
254 | |
255 | if (pll && |
256 | presentation_time && refresh_interval && |
257 | presentation_time > frame_data->clock_time - refresh_interval / 2 && |
258 | presentation_time < frame_data->clock_time + refresh_interval / 2) |
259 | adjust_clock_for_phase (frame_data->clock_time, presentation_time); |
260 | |
261 | if (presentation_time) |
262 | variable_add (&latency_error, |
263 | presentation_time - frame_data->clock_time); |
264 | |
265 | remove = TRUE(!(0)); |
266 | } |
267 | |
268 | if (remove) |
269 | { |
270 | past_frames = g_list_delete_link (past_frames, l); |
271 | g_slice_free (FrameData, frame_data)do { if (1) g_slice_free1 (sizeof (FrameData), (frame_data)); else (void) ((FrameData*) 0 == (frame_data)); } while (0); |
272 | } |
273 | } |
274 | } |
275 | |
276 | static void |
277 | print_statistics (void) |
278 | { |
279 | gint64 now = g_get_monotonic_time (); |
280 | static gint64 last_print_time = 0; |
281 | |
282 | if (last_print_time == 0) |
283 | last_print_time = now; |
284 | else if (now -last_print_time > 5000000) |
285 | { |
286 | g_print ("dropped_frames: %d/%d\n", |
287 | dropped_frames, n_frames); |
288 | g_print ("collected_frames: %g/%d\n", |
289 | latency_error.weight, n_frames); |
290 | g_print ("latency_error: %g +/- %g\n", |
291 | variable_mean (&latency_error), |
292 | variable_standard_deviation (&latency_error)); |
293 | if (pll) |
294 | g_print ("playback rate adjustment: %g +/- %g %%\n", |
295 | (variable_mean (&time_factor_stats) - 1) * 100, |
296 | variable_standard_deviation (&time_factor_stats) * 100); |
297 | variable_init (&latency_error); |
298 | variable_init (&time_factor_stats); |
299 | dropped_frames = 0; |
300 | n_frames = 0; |
301 | last_print_time = now; |
302 | } |
303 | } |
304 | |
305 | static void |
306 | on_update (CdkFrameClock *frame_clock, |
307 | gpointer data G_GNUC_UNUSED__attribute__ ((__unused__))) |
308 | { |
309 | CdkFrameTimings *timings = cdk_frame_clock_get_current_timings (frame_clock); |
310 | gint64 frame_time = cdk_frame_timings_get_frame_time (timings); |
311 | gint64 predicted_presentation_time = cdk_frame_timings_get_predicted_presentation_time (timings); |
312 | gint64 refresh_interval; |
313 | FrameData *pending_frame; |
314 | |
315 | if (clock_time_base == 0) |
316 | clock_time_base = frame_time + PRE_BUFFER_TIME500000; |
317 | |
318 | cdk_frame_clock_get_refresh_info (frame_clock, frame_time, |
319 | &refresh_interval, NULL((void*)0)); |
320 | |
321 | pending_frame = peek_pending_frame (); |
322 | if (stream_time_to_clock_time (pending_frame->stream_time) |
323 | < predicted_presentation_time + refresh_interval / 2) |
324 | { |
325 | while (TRUE(!(0))) |
326 | { |
327 | FrameData *next_frame = peek_next_frame (); |
328 | if (next_frame && |
329 | stream_time_to_clock_time (next_frame->stream_time) |
330 | < predicted_presentation_time + refresh_interval / 2) |
331 | { |
332 | g_slice_free (FrameData, unqueue_frame ())do { if (1) g_slice_free1 (sizeof (FrameData), (unqueue_frame ())); else (void) ((FrameData*) 0 == (unqueue_frame ())); } while (0); |
333 | n_frames++; |
334 | dropped_frames++; |
335 | pending_frame = next_frame; |
Value stored to 'pending_frame' is never read | |
336 | } |
337 | else |
338 | break; |
339 | } |
340 | |
341 | if (displayed_frame) |
342 | past_frames = g_list_prepend (past_frames, displayed_frame); |
343 | |
344 | n_frames++; |
345 | displayed_frame = unqueue_frame (); |
346 | displayed_frame->clock_time = stream_time_to_clock_time (displayed_frame->stream_time); |
347 | |
348 | displayed_frame->frame_counter = cdk_frame_timings_get_frame_counter (timings); |
349 | variable_add (&time_factor_stats, time_factor); |
350 | |
351 | collect_old_frames (); |
352 | print_statistics (); |
353 | |
354 | ctk_widget_queue_draw (window); |
355 | } |
356 | } |
357 | |
358 | static GOptionEntry options[] = { |
359 | { "pll", 'p', 0, G_OPTION_ARG_NONE, &pll, "Sync frame rate to refresh", NULL((void*)0) }, |
360 | { "fps", 'f', 0, G_OPTION_ARG_INT, &fps, "Frame rate", "FPS" }, |
361 | { NULL((void*)0) } |
362 | }; |
363 | |
364 | int |
365 | main(int argc, char **argv) |
366 | { |
367 | GError *error = NULL((void*)0); |
368 | CdkFrameClock *frame_clock; |
369 | |
370 | if (!ctk_init_with_args (&argc, &argv, "", |
371 | options, NULL((void*)0), &error)) |
372 | { |
373 | g_printerr ("Option parsing failed: %s\n", error->message); |
374 | return 1; |
375 | } |
376 | |
377 | window = ctk_window_new (CTK_WINDOW_TOPLEVEL); |
378 | ctk_widget_set_app_paintable (window, TRUE(!(0))); |
379 | ctk_window_set_default_size (CTK_WINDOW (window)((((CtkWindow*) (void *) g_type_check_instance_cast ((GTypeInstance *) ((window)), ((ctk_window_get_type ())))))), 300, 300); |
380 | |
381 | g_signal_connect (window, "draw",g_signal_connect_data ((window), ("draw"), (((GCallback) (on_window_draw ))), (((void*)0)), ((void*)0), (GConnectFlags) 0) |
382 | G_CALLBACK (on_window_draw), NULL)g_signal_connect_data ((window), ("draw"), (((GCallback) (on_window_draw ))), (((void*)0)), ((void*)0), (GConnectFlags) 0); |
383 | g_signal_connect (window, "destroy",g_signal_connect_data ((window), ("destroy"), (((GCallback) ( ctk_main_quit))), (((void*)0)), ((void*)0), (GConnectFlags) 0 ) |
384 | G_CALLBACK (ctk_main_quit), NULL)g_signal_connect_data ((window), ("destroy"), (((GCallback) ( ctk_main_quit))), (((void*)0)), ((void*)0), (GConnectFlags) 0 ); |
385 | |
386 | ctk_widget_show (window); |
387 | |
388 | frame_queue = g_queue_new (); |
389 | g_mutex_init (&frame_mutex); |
390 | g_cond_init (&frame_cond); |
391 | |
392 | g_thread_new ("Create Frames", create_frames_thread, NULL((void*)0)); |
393 | |
394 | frame_clock = ctk_widget_get_frame_clock (window); |
395 | g_signal_connect (frame_clock, "update",g_signal_connect_data ((frame_clock), ("update"), (((GCallback ) (on_update))), (((void*)0)), ((void*)0), (GConnectFlags) 0) |
396 | G_CALLBACK (on_update), NULL)g_signal_connect_data ((frame_clock), ("update"), (((GCallback ) (on_update))), (((void*)0)), ((void*)0), (GConnectFlags) 0); |
397 | cdk_frame_clock_begin_updating (frame_clock); |
398 | |
399 | ctk_main (); |
400 | |
401 | return 0; |
402 | } |