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;
24 /* Thread-safe frame queue */
26 #define MAX_QUEUE_LENGTH 5
28 static GQueue *frame_queue;
29 static GMutex frame_mutex;
30 static GCond frame_cond;
33 queue_frame (FrameData *frame_data)
35 g_mutex_lock (&frame_mutex);
37 while (frame_queue->length == MAX_QUEUE_LENGTH)
38 g_cond_wait (&frame_cond, &frame_mutex);
40 g_queue_push_tail (frame_queue, frame_data);
42 g_mutex_unlock (&frame_mutex);
48 FrameData *frame_data;
50 g_mutex_lock (&frame_mutex);
52 if (frame_queue->length > 0)
54 frame_data = g_queue_pop_head (frame_queue);
55 g_cond_signal (&frame_cond);
62 g_mutex_unlock (&frame_mutex);
68 peek_pending_frame (void)
70 FrameData *frame_data;
72 g_mutex_lock (&frame_mutex);
74 if (frame_queue->head)
75 frame_data = frame_queue->head->data;
79 g_mutex_unlock (&frame_mutex);
85 peek_next_frame (void)
87 FrameData *frame_data;
89 g_mutex_lock (&frame_mutex);
91 if (frame_queue->head && frame_queue->head->next)
92 frame_data = frame_queue->head->next->data;
96 g_mutex_unlock (&frame_mutex);
101 /* Frame producer thread */
104 create_frames_thread (gpointer data)
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;
114 queue_frame (frame_data);
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
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.
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.
137 #define PRE_BUFFER_TIME 500000
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;
146 stream_time_to_clock_time (gint64 stream_time)
148 return clock_time_base + (stream_time - stream_time_base) * time_factor;
152 adjust_clock_for_phase (gint64 frame_clock_time,
153 gint64 presentation_time)
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;
161 if (count >= fps) /* Give a second of warmup */
163 gint64 time_delta = frame_clock_time - previous_frame_clock_time;
164 gint64 previous_phase = previous_presentation_time - previous_frame_clock_time;
166 double expected_phase_delta;
168 stream_time_base += (frame_clock_time - clock_time_base) / time_factor;
169 clock_time_base = frame_clock_time;
171 expected_phase_delta = time_delta * (1 - phase_time_factor);
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
177 frequency_time_factor += (phase - previous_phase - expected_phase_delta) / (double)time_delta / fps;
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
182 phase_time_factor = 1 + phase / 2000000.;
184 time_factor = frequency_time_factor * phase_time_factor;
187 previous_frame_clock_time = frame_clock_time;
188 previous_presentation_time = presentation_time;
194 on_window_draw (GtkWidget *widget,
197 GdkRectangle allocation;
200 cairo_set_source_rgb (cr, 1., 1., 1.);
203 cairo_set_source_rgb (cr, 0., 0., 0.);
204 gtk_widget_get_allocation (widget, &allocation);
206 cx = allocation.width / 2.;
207 cy = allocation.height / 2.;
208 r = MIN (allocation.width, allocation.height) / 2.;
210 cairo_arc (cr, cx, cy, r,
215 cairo_move_to (cr, cx, cy);
217 cx + r * cos(displayed_frame->angle - M_PI / 2),
218 cy + r * sin(displayed_frame->angle - M_PI / 2));
221 if (displayed_frame->frame_counter == 0)
223 GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
224 displayed_frame->frame_counter = gdk_frame_clock_get_frame_counter (frame_clock);
230 collect_old_frames (void)
232 GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
235 for (l = past_frames; l; l = l_next)
237 FrameData *frame_data = l->data;
238 gboolean remove = FALSE;
241 GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock,
242 frame_data->frame_counter);
247 else if (gdk_frame_timings_get_complete (timings))
249 gint64 presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
250 gint64 refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
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);
258 if (presentation_time)
259 variable_add (&latency_error,
260 presentation_time - frame_data->clock_time);
267 past_frames = g_list_delete_link (past_frames, l);
268 g_slice_free (FrameData, frame_data);
274 print_statistics (void)
276 gint64 now = g_get_monotonic_time ();
277 static gint64 last_print_time = 0;
279 if (last_print_time == 0)
280 last_print_time = now;
281 else if (now -last_print_time > 5000000)
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));
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);
298 last_print_time = now;
303 on_update (GdkFrameClock *frame_clock,
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;
312 if (clock_time_base == 0)
313 clock_time_base = frame_time + PRE_BUFFER_TIME;
315 gdk_frame_clock_get_refresh_info (frame_clock, frame_time,
316 &refresh_interval, NULL);
318 pending_frame = peek_pending_frame ();
319 if (stream_time_to_clock_time (pending_frame->stream_time)
320 < predicted_presentation_time + refresh_interval / 2)
324 FrameData *next_frame = peek_next_frame ();
326 stream_time_to_clock_time (next_frame->stream_time)
327 < predicted_presentation_time + refresh_interval / 2)
329 g_slice_free (FrameData, unqueue_frame ());
332 pending_frame = next_frame;
339 past_frames = g_list_prepend (past_frames, displayed_frame);
342 displayed_frame = unqueue_frame ();
343 displayed_frame->clock_time = stream_time_to_clock_time (displayed_frame->stream_time);
345 displayed_frame->frame_counter = gdk_frame_timings_get_frame_counter (timings);
346 variable_add (&time_factor_stats, time_factor);
348 collect_old_frames ();
351 gtk_widget_queue_draw (window);
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" },
362 main(int argc, char **argv)
364 GError *error = NULL;
365 GdkFrameClock *frame_clock;
367 if (!gtk_init_with_args (&argc, &argv, "",
368 options, NULL, &error))
370 g_printerr ("Option parsing failed: %s\n", error->message);
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);
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);
383 gtk_widget_show (window);
385 frame_queue = g_queue_new ();
386 g_mutex_init (&frame_mutex);
387 g_cond_init (&frame_cond);
389 g_thread_new ("Create Frames", create_frames_thread, NULL);
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);