]> Pileus Git - ~andy/gtk/blob - tests/video-timer.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / tests / video-timer.c
1 #include <math.h>
2 #include <gtk/gtk.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 GtkWidget *window;
15 static GList *past_frames;
16 static Variable latency_error = VARIABLE_INIT;
17 static Variable time_factor_stats = VARIABLE_INIT;
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_LENGTH 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_LENGTH)
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;
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;
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;
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)
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
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 void
194 on_window_draw (GtkWidget *widget,
195                 cairo_t   *cr)
196 {
197   GdkRectangle 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   gtk_widget_get_allocation (widget, &allocation);
205
206   cx = allocation.width / 2.;
207   cy = allocation.height / 2.;
208   r = MIN (allocation.width, allocation.height) / 2.;
209
210   cairo_arc (cr, cx, cy, r,
211              0, 2 * M_PI);
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_PI / 2),
218                      cy + r * sin(displayed_frame->angle - M_PI / 2));
219       cairo_stroke (cr);
220
221       if (displayed_frame->frame_counter == 0)
222         {
223           GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
224           displayed_frame->frame_counter = gdk_frame_clock_get_frame_counter (frame_clock);
225         }
226     }
227 }
228
229 static void
230 collect_old_frames (void)
231 {
232   GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
233   GList *l, *l_next;
234
235   for (l = past_frames; l; l = l_next)
236     {
237       FrameData *frame_data = l->data;
238       gboolean remove = FALSE;
239       l_next = l->next;
240
241       GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock,
242                                                               frame_data->frame_counter);
243       if (timings == NULL)
244         {
245           remove = TRUE;
246         }
247       else if (gdk_frame_timings_get_complete (timings))
248         {
249           gint64 presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
250           gint64 refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
251
252           if (pll &&
253               presentation_time && refresh_interval &&
254               presentation_time > frame_data->clock_time - refresh_interval / 2 &&
255               presentation_time < frame_data->clock_time + refresh_interval / 2)
256             adjust_clock_for_phase (frame_data->clock_time, presentation_time);
257
258           if (presentation_time)
259             variable_add (&latency_error,
260                           presentation_time - frame_data->clock_time);
261
262           remove = TRUE;
263         }
264
265       if (remove)
266         {
267           past_frames = g_list_delete_link (past_frames, l);
268           g_slice_free (FrameData, frame_data);
269         }
270     }
271 }
272
273 static void
274 print_statistics (void)
275 {
276   gint64 now = g_get_monotonic_time ();
277   static gint64 last_print_time = 0;
278
279   if (last_print_time == 0)
280     last_print_time = now;
281   else if (now -last_print_time > 5000000)
282     {
283       g_print ("dropped_frames: %d/%d\n",
284                dropped_frames, n_frames);
285       g_print ("collected_frames: %g/%d\n",
286                latency_error.weight, n_frames);
287       g_print ("latency_error: %g +/- %g\n",
288                variable_mean (&latency_error),
289                variable_standard_deviation (&latency_error));
290       if (pll)
291         g_print ("playback rate adjustment: %g +/- %g %%\n",
292                  (variable_mean (&time_factor_stats) - 1) * 100,
293                  variable_standard_deviation (&time_factor_stats) * 100);
294       variable_reset (&latency_error);
295       variable_reset (&time_factor_stats);
296       dropped_frames = 0;
297       n_frames = 0;
298       last_print_time = now;
299     }
300 }
301
302 static void
303 on_update (GdkFrameClock *frame_clock,
304            gpointer       data)
305 {
306   GdkFrameTimings *timings = gdk_frame_clock_get_current_timings (frame_clock);
307   gint64 frame_time = gdk_frame_timings_get_frame_time (timings);
308   gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
309   gint64 refresh_interval;
310   FrameData *pending_frame;
311
312   if (clock_time_base == 0)
313     clock_time_base = frame_time + PRE_BUFFER_TIME;
314
315   gdk_frame_clock_get_refresh_info (frame_clock, frame_time,
316                                     &refresh_interval, NULL);
317
318   pending_frame = peek_pending_frame ();
319   if (stream_time_to_clock_time (pending_frame->stream_time)
320       < predicted_presentation_time + refresh_interval / 2)
321     {
322       while (TRUE)
323         {
324           FrameData *next_frame = peek_next_frame ();
325           if (next_frame &&
326               stream_time_to_clock_time (next_frame->stream_time)
327               < predicted_presentation_time + refresh_interval / 2)
328             {
329               g_slice_free (FrameData, unqueue_frame ());
330               n_frames++;
331               dropped_frames++;
332               pending_frame = next_frame;
333             }
334           else
335             break;
336         }
337
338       if (displayed_frame)
339         past_frames = g_list_prepend (past_frames, displayed_frame);
340
341       n_frames++;
342       displayed_frame = unqueue_frame ();
343       displayed_frame->clock_time = stream_time_to_clock_time (displayed_frame->stream_time);
344
345       displayed_frame->frame_counter = gdk_frame_timings_get_frame_counter (timings);
346       variable_add (&time_factor_stats, time_factor);
347
348       collect_old_frames ();
349       print_statistics ();
350
351       gtk_widget_queue_draw (window);
352     }
353 }
354
355 static GOptionEntry options[] = {
356   { "pll", 'p', 0, G_OPTION_ARG_NONE, &pll, "Sync frame rate to refresh", NULL },
357   { "fps", 'f', 0, G_OPTION_ARG_INT, &fps, "Frame rate", "FPS" },
358   { NULL }
359 };
360
361 int
362 main(int argc, char **argv)
363 {
364   GError *error = NULL;
365   GdkFrameClock *frame_clock;
366
367   if (!gtk_init_with_args (&argc, &argv, "",
368                            options, NULL, &error))
369     {
370       g_printerr ("Option parsing failed: %s\n", error->message);
371       return 1;
372     }
373
374   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
375   gtk_widget_set_app_paintable (window, TRUE);
376   gtk_window_set_default_size (GTK_WINDOW (window), 300, 300);
377
378   g_signal_connect (window, "draw",
379                     G_CALLBACK (on_window_draw), NULL);
380   g_signal_connect (window, "destroy",
381                     G_CALLBACK (gtk_main_quit), NULL);
382
383   gtk_widget_show (window);
384
385   frame_queue = g_queue_new ();
386   g_mutex_init (&frame_mutex);
387   g_cond_init (&frame_cond);
388
389   g_thread_new ("Create Frames", create_frames_thread, NULL);
390
391   frame_clock = gtk_widget_get_frame_clock (window);
392   g_signal_connect (frame_clock, "update",
393                     G_CALLBACK (on_update), NULL);
394   gdk_frame_clock_begin_updating (frame_clock);
395
396   gtk_main ();
397
398   return 0;
399 }