1#include <math.h>
2#include <gtk/gtk.h>
3
4#include "variable.h"
5
6typedef struct {
7 double angle;
8 gint64 stream_time;
9 gint64 clock_time;
10 gint64 frame_counter;
11} FrameData;
12
13static FrameData *displayed_frame;
14static GtkWidget *window;
15static GList *past_frames;
16static Variable latency_error = VARIABLE_INIT;
17static Variable time_factor_stats = VARIABLE_INIT;
18static int dropped_frames = 0;
19static int n_frames = 0;
20
21static gboolean pll;
22static int fps = 24;
23
24/* Thread-safe frame queue */
25
26#define MAX_QUEUE_LENGTH 5
27
28static GQueue *frame_queue;
29static GMutex frame_mutex;
30static GCond frame_cond;
31
32static void
33queue_frame (FrameData *frame_data)
34{
35 g_mutex_lock (mutex: &frame_mutex);
36
37 while (frame_queue->length == MAX_QUEUE_LENGTH)
38 g_cond_wait (cond: &frame_cond, mutex: &frame_mutex);
39
40 g_queue_push_tail (queue: frame_queue, data: frame_data);
41
42 g_mutex_unlock (mutex: &frame_mutex);
43}
44
45static FrameData *
46unqueue_frame (void)
47{
48 FrameData *frame_data;
49
50 g_mutex_lock (mutex: &frame_mutex);
51
52 if (frame_queue->length > 0)
53 {
54 frame_data = g_queue_pop_head (queue: frame_queue);
55 g_cond_signal (cond: &frame_cond);
56 }
57 else
58 {
59 frame_data = NULL;
60 }
61
62 g_mutex_unlock (mutex: &frame_mutex);
63
64 return frame_data;
65}
66
67static FrameData *
68peek_pending_frame (void)
69{
70 FrameData *frame_data;
71
72 g_mutex_lock (mutex: &frame_mutex);
73
74 if (frame_queue->head)
75 frame_data = frame_queue->head->data;
76 else
77 frame_data = NULL;
78
79 g_mutex_unlock (mutex: &frame_mutex);
80
81 return frame_data;
82}
83
84static FrameData *
85peek_next_frame (void)
86{
87 FrameData *frame_data;
88
89 g_mutex_lock (mutex: &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;
95
96 g_mutex_unlock (mutex: &frame_mutex);
97
98 return frame_data;
99}
100
101/* Frame producer thread */
102
103static gpointer
104create_frames_thread (gpointer data)
105{
106 int frame_count = 0;
107
108 while (TRUE)
109 {
110 FrameData *frame_data = g_slice_new0 (FrameData);
111 frame_data->angle = 2 * M_PI * (frame_count % fps) / (double)fps;
112 frame_data->stream_time = (G_GINT64_CONSTANT (1000000) * frame_count) / fps;
113
114 queue_frame (frame_data);
115 frame_count++;
116 }
117
118 return NULL;
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_TIME 500000
138
139static gint64 stream_time_base;
140static gint64 clock_time_base;
141static double time_factor = 1.0;
142static double frequency_time_factor = 1.0;
143static double phase_time_factor = 1.0;
144
145static gint64
146stream_time_to_clock_time (gint64 stream_time)
147{
148 return clock_time_base + (stream_time - stream_time_base) * time_factor;
149}
150
151static void
152adjust_clock_for_phase (gint64 frame_clock_time,
153 gint64 presentation_time)
154{
155 static int 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
193static void
194on_draw (GtkDrawingArea *da,
195 cairo_t *cr,
196 int width,
197 int height,
198 gpointer data)
199{
200 double cx, cy, r;
201
202 cairo_set_source_rgb (cr, red: 1., green: 1., blue: 1.);
203 cairo_paint (cr);
204
205 cairo_set_source_rgb (cr, red: 0., green: 0., blue: 0.);
206
207 cx = width / 2.;
208 cy = height / 2.;
209 r = MIN (width, height) / 2.;
210
211 cairo_arc (cr, xc: cx, yc: cy, radius: r,
212 angle1: 0, angle2: 2 * M_PI);
213 cairo_stroke (cr);
214 if (displayed_frame)
215 {
216 cairo_move_to (cr, x: cx, y: cy);
217 cairo_line_to (cr,
218 x: cx + r * cos(x: displayed_frame->angle - M_PI / 2),
219 y: cy + r * sin(x: displayed_frame->angle - M_PI / 2));
220 cairo_stroke (cr);
221
222 if (displayed_frame->frame_counter == 0)
223 {
224 GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (widget: window);
225 displayed_frame->frame_counter = gdk_frame_clock_get_frame_counter (frame_clock);
226 }
227 }
228}
229
230static void
231collect_old_frames (void)
232{
233 GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (widget: window);
234 GList *l, *l_next;
235
236 for (l = past_frames; l; l = l_next)
237 {
238 FrameData *frame_data = l->data;
239 gboolean remove = FALSE;
240 l_next = l->next;
241
242 GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock,
243 frame_counter: frame_data->frame_counter);
244 if (timings == NULL)
245 {
246 remove = TRUE;
247 }
248 else if (gdk_frame_timings_get_complete (timings))
249 {
250 gint64 presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
251 gint64 refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
252
253 if (pll &&
254 presentation_time && refresh_interval &&
255 presentation_time > frame_data->clock_time - refresh_interval / 2 &&
256 presentation_time < frame_data->clock_time + refresh_interval / 2)
257 adjust_clock_for_phase (frame_clock_time: frame_data->clock_time, presentation_time);
258
259 if (presentation_time)
260 variable_add (variable: &latency_error,
261 value: presentation_time - frame_data->clock_time);
262
263 remove = TRUE;
264 }
265
266 if (remove)
267 {
268 past_frames = g_list_delete_link (list: past_frames, link_: l);
269 g_slice_free (FrameData, frame_data);
270 }
271 }
272}
273
274static void
275print_statistics (void)
276{
277 gint64 now = g_get_monotonic_time ();
278 static gint64 last_print_time = 0;
279
280 if (last_print_time == 0)
281 last_print_time = now;
282 else if (now -last_print_time > 5000000)
283 {
284 g_print (format: "dropped_frames: %d/%d\n",
285 dropped_frames, n_frames);
286 g_print (format: "collected_frames: %g/%d\n",
287 latency_error.weight, n_frames);
288 g_print (format: "latency_error: %g +/- %g\n",
289 variable_mean (variable: &latency_error),
290 variable_standard_deviation (variable: &latency_error));
291 if (pll)
292 g_print (format: "playback rate adjustment: %g +/- %g %%\n",
293 (variable_mean (variable: &time_factor_stats) - 1) * 100,
294 variable_standard_deviation (variable: &time_factor_stats) * 100);
295 variable_init (variable: &latency_error);
296 variable_init (variable: &time_factor_stats);
297 dropped_frames = 0;
298 n_frames = 0;
299 last_print_time = now;
300 }
301}
302
303static void
304on_update (GdkFrameClock *frame_clock,
305 gpointer data)
306{
307 GdkFrameTimings *timings = gdk_frame_clock_get_current_timings (frame_clock);
308 gint64 frame_time = gdk_frame_timings_get_frame_time (timings);
309 gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
310 gint64 refresh_interval;
311 FrameData *pending_frame;
312
313 if (clock_time_base == 0)
314 clock_time_base = frame_time + PRE_BUFFER_TIME;
315
316 gdk_frame_clock_get_refresh_info (frame_clock, base_time: frame_time,
317 refresh_interval_return: &refresh_interval, NULL);
318
319 pending_frame = peek_pending_frame ();
320 g_assert (pending_frame);
321
322 if (stream_time_to_clock_time (stream_time: pending_frame->stream_time)
323 < predicted_presentation_time + refresh_interval / 2)
324 {
325 while (TRUE)
326 {
327 FrameData *next_frame = peek_next_frame ();
328 if (next_frame &&
329 stream_time_to_clock_time (stream_time: next_frame->stream_time)
330 < predicted_presentation_time + refresh_interval / 2)
331 {
332 g_slice_free (FrameData, unqueue_frame ());
333 n_frames++;
334 dropped_frames++;
335 pending_frame = next_frame;
336 }
337 else
338 break;
339 }
340
341 if (displayed_frame)
342 past_frames = g_list_prepend (list: past_frames, data: displayed_frame);
343
344 n_frames++;
345 displayed_frame = unqueue_frame ();
346 g_assert (displayed_frame);
347 displayed_frame->clock_time = stream_time_to_clock_time (stream_time: displayed_frame->stream_time);
348
349 displayed_frame->frame_counter = gdk_frame_timings_get_frame_counter (timings);
350 variable_add (variable: &time_factor_stats, value: time_factor);
351
352 collect_old_frames ();
353 print_statistics ();
354
355 gtk_widget_queue_draw (widget: window);
356 }
357}
358
359static GOptionEntry options[] = {
360 { "pll", 'p', 0, G_OPTION_ARG_NONE, &pll, "Sync frame rate to refresh", NULL },
361 { "fps", 'f', 0, G_OPTION_ARG_INT, &fps, "Frame rate", "FPS" },
362 { NULL }
363};
364
365static void
366quit_cb (GtkWidget *widget,
367 gpointer data)
368{
369 gboolean *done = data;
370
371 *done = TRUE;
372
373 g_main_context_wakeup (NULL);
374}
375
376int
377main(int argc, char **argv)
378{
379 GtkWidget *da;
380 GError *error = NULL;
381 GdkFrameClock *frame_clock;
382 GOptionContext *context;
383 gboolean done = FALSE;
384
385 context = g_option_context_new (parameter_string: "");
386 g_option_context_add_main_entries (context, entries: options, NULL);
387
388 if (!g_option_context_parse (context, argc: &argc, argv: &argv, error: &error))
389 {
390 g_printerr (format: "Option parsing failed: %s\n", error->message);
391 return 1;
392 }
393
394 g_option_context_free (context);
395
396 gtk_init ();
397
398 window = gtk_window_new ();
399 gtk_window_set_default_size (GTK_WINDOW (window), width: 300, height: 300);
400 g_signal_connect (window, "destroy",
401 G_CALLBACK (quit_cb), &done);
402
403 da = gtk_drawing_area_new ();
404 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func: on_draw, NULL, NULL);
405 gtk_window_set_child (GTK_WINDOW (window), child: da);
406
407 gtk_widget_show (widget: window);
408
409 frame_queue = g_queue_new ();
410 g_mutex_init (mutex: &frame_mutex);
411 g_cond_init (cond: &frame_cond);
412
413 g_thread_new (name: "Create Frames", func: create_frames_thread, NULL);
414
415 frame_clock = gtk_widget_get_frame_clock (widget: window);
416 g_signal_connect (frame_clock, "update",
417 G_CALLBACK (on_update), NULL);
418 gdk_frame_clock_begin_updating (frame_clock);
419
420 while (!done)
421 g_main_context_iteration (NULL, TRUE);
422
423 return 0;
424}
425

source code of gtk/tests/video-timer.c