[gtk+/wip/frame-synchronization: 32/33] video-timer: add a test case for display at a constant frame rate
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wip/frame-synchronization: 32/33] video-timer: add a test case for display at a constant frame rate
- Date: Tue, 4 Dec 2012 18:00:43 +0000 (UTC)
commit 47be35bf5dcfa97b797a0b91b9f03d5a75ad32d9
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Thu Nov 15 17:39:30 2012 -0500
video-timer: add a test case for display at a constant frame rate
Add a test case that simulates the timing operaton that goes on
when showing a constant frame rate stream like a video - each
frame is shown at the VBlank interval that is closest to when it
would ideally be timed.
https://bugzilla.gnome.org/show_bug.cgi?id=685460
tests/Makefile.am | 7 +
tests/video-timer.c | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 335 insertions(+), 0 deletions(-)
---
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 91a97fd..1a0dbe0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -30,6 +30,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
simple \
flicker \
print-editor \
+ video-timer \
testaccel \
testadjustsize \
testappchooser \
@@ -155,6 +156,7 @@ flicker_DEPENDENCIES = $(TEST_DEPS)
motion_compression_DEPENDENCIES = $(TEST_DEPS)
simple_DEPENDENCIES = $(TEST_DEPS)
print_editor_DEPENDENCIES = $(TEST_DEPS)
+video_timer_DEPENDENCIES = $(TEST_DEPS)
testheightforwidth_DEPENDENCIES = $(TEST_DEPS)
testicontheme_DEPENDENCIES = $(TEST_DEPS)
testiconview_DEPENDENCIES = $(TEST_DEPS)
@@ -257,6 +259,11 @@ animated_resizing_SOURCES = \
variable.c \
variable.h
+video_timer_SOURCES = \
+ video-timer.c \
+ variable.c \
+ variable.h
+
testboxcss_SOURCES = \
testboxcss.c \
prop-editor.c
diff --git a/tests/video-timer.c b/tests/video-timer.c
new file mode 100644
index 0000000..29b671e
--- /dev/null
+++ b/tests/video-timer.c
@@ -0,0 +1,328 @@
+#include <math.h>
+#include <gtk/gtk.h>
+
+#include "variable.h"
+
+typedef struct {
+ gdouble angle;
+ gint64 stream_time;
+ gint64 clock_time;
+ gint64 frame_counter;
+} FrameData;
+
+static FrameData *displayed_frame;
+static GtkWidget *window;
+static GList *past_frames;
+static Variable latency_error = VARIABLE_INIT;
+static int dropped_frames = 0;
+static int n_frames = 0;
+
+static int fps = 24;
+
+/* Thread-safe frame queue */
+
+#define MAX_QUEUE_LENGTH 5
+
+static GQueue *frame_queue;
+static GMutex frame_mutex;
+static GCond frame_cond;
+
+static void
+queue_frame (FrameData *frame_data)
+{
+ g_mutex_lock (&frame_mutex);
+
+ while (frame_queue->length == MAX_QUEUE_LENGTH)
+ g_cond_wait (&frame_cond, &frame_mutex);
+
+ g_queue_push_tail (frame_queue, frame_data);
+
+ g_mutex_unlock (&frame_mutex);
+}
+
+static FrameData *
+unqueue_frame (void)
+{
+ FrameData *frame_data;
+
+ g_mutex_lock (&frame_mutex);
+
+ if (frame_queue->length > 0)
+ {
+ frame_data = g_queue_pop_head (frame_queue);
+ g_cond_signal (&frame_cond);
+ }
+ else
+ {
+ frame_data = NULL;
+ }
+
+ g_mutex_unlock (&frame_mutex);
+
+ return frame_data;
+}
+
+static FrameData *
+peek_pending_frame (void)
+{
+ FrameData *frame_data;
+
+ g_mutex_lock (&frame_mutex);
+
+ if (frame_queue->head)
+ frame_data = frame_queue->head->data;
+ else
+ frame_data = NULL;
+
+ g_mutex_unlock (&frame_mutex);
+
+ return frame_data;
+}
+
+static FrameData *
+peek_next_frame (void)
+{
+ FrameData *frame_data;
+
+ g_mutex_lock (&frame_mutex);
+
+ if (frame_queue->head && frame_queue->head->next)
+ frame_data = frame_queue->head->next->data;
+ else
+ frame_data = NULL;
+
+ g_mutex_unlock (&frame_mutex);
+
+ return frame_data;
+}
+
+/* Frame producer thread */
+
+static gpointer
+create_frames_thread (gpointer data)
+{
+ int frame_count = 0;
+
+ while (TRUE)
+ {
+ FrameData *frame_data = g_slice_new0 (FrameData);
+ frame_data->angle = 2 * M_PI * (frame_count % fps) / (double)fps;
+ frame_data->stream_time = (G_GINT64_CONSTANT (1000000) * frame_count) / fps;
+
+ queue_frame (frame_data);
+ frame_count++;
+ }
+
+ return NULL;
+}
+
+/* Clock management */
+
+#define PRE_BUFFER_TIME 500000
+
+static gint64 start_time;
+
+static gint64
+stream_time_to_clock_time (gint64 stream_time)
+{
+ return start_time + stream_time + PRE_BUFFER_TIME;
+}
+
+/* Drawing */
+
+static void
+on_window_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdkRectangle allocation;
+ double cx, cy, r;
+
+ cairo_set_source_rgb (cr, 1., 1., 1.);
+ cairo_paint (cr);
+
+ cairo_set_source_rgb (cr, 0., 0., 0.);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ cx = allocation.width / 2.;
+ cy = allocation.height / 2.;
+ r = MIN (allocation.width, allocation.height) / 2.;
+
+ cairo_arc (cr, cx, cy, r,
+ 0, 2 * M_PI);
+ cairo_stroke (cr);
+ if (displayed_frame)
+ {
+ cairo_move_to (cr, cx, cy);
+ cairo_line_to (cr,
+ cx + r * cos(displayed_frame->angle - M_PI / 2),
+ cy + r * sin(displayed_frame->angle - M_PI / 2));
+ cairo_stroke (cr);
+
+ if (displayed_frame->frame_counter == 0)
+ {
+ GdkPaintClock *paint_clock = gtk_widget_get_paint_clock (window);
+ GdkFrameHistory *history = gdk_paint_clock_get_history (paint_clock);
+
+ displayed_frame->frame_counter = gdk_frame_history_get_frame_counter (history);
+ }
+ }
+}
+
+static void
+collect_old_frames (void)
+{
+ GdkPaintClock *paint_clock = gtk_widget_get_paint_clock (window);
+ GdkFrameHistory *history = gdk_paint_clock_get_history (paint_clock);
+ GList *l, *l_next;
+
+ for (l = past_frames; l; l = l_next)
+ {
+ FrameData *frame_data = l->data;
+ gboolean remove = FALSE;
+ l_next = l->next;
+
+ GdkFrameTimings *timings = gdk_frame_history_get_timings (history,
+ frame_data->frame_counter);
+ if (timings == NULL)
+ {
+ remove = TRUE;
+ }
+ else if (gdk_frame_timings_get_complete (timings))
+ {
+ gint64 presentation_time = gdk_frame_timings_get_presentation_time (timings);
+ if (presentation_time)
+ variable_add (&latency_error,
+ presentation_time - frame_data->clock_time);
+
+ remove = TRUE;
+ }
+
+ if (remove)
+ {
+ past_frames = g_list_delete_link (past_frames, l);
+ g_slice_free (FrameData, frame_data);
+ }
+ }
+}
+
+static void
+print_statistics (void)
+{
+ gint64 now = g_get_monotonic_time ();
+ static gint64 last_print_time = 0;
+
+ if (last_print_time == 0)
+ last_print_time = now;
+ else if (now -last_print_time > 5000000)
+ {
+ g_print ("dropped_frames: %d/%d\n",
+ dropped_frames, n_frames);
+ g_print ("collected_frames: %g/%d\n",
+ latency_error.weight, n_frames);
+ g_print ("latency_error: %g +/- %g\n",
+ variable_mean (&latency_error),
+ variable_standard_deviation (&latency_error));
+ variable_reset (&latency_error);
+ dropped_frames = 0;
+ n_frames = 0;
+ last_print_time = now;
+ }
+}
+
+static void
+on_update (GdkPaintClock *paint_clock,
+ gpointer data)
+{
+ GdkFrameTimings *timings = gdk_paint_clock_get_current_frame_timings (paint_clock);
+ gint64 frame_time = gdk_frame_timings_get_frame_time (timings);
+ gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
+ gint64 refresh_interval;
+ FrameData *pending_frame;
+
+ if (start_time == 0)
+ start_time = frame_time;
+
+ gdk_paint_clock_get_refresh_info (paint_clock, frame_time,
+ &refresh_interval, NULL);
+
+ pending_frame = peek_pending_frame ();
+ if (stream_time_to_clock_time (pending_frame->stream_time)
+ < predicted_presentation_time + refresh_interval / 2)
+ {
+ while (TRUE)
+ {
+ FrameData *next_frame = peek_next_frame ();
+ if (next_frame &&
+ stream_time_to_clock_time (next_frame->stream_time)
+ < predicted_presentation_time + refresh_interval / 2)
+ {
+ g_slice_free (FrameData, unqueue_frame ());
+ n_frames++;
+ dropped_frames++;
+ pending_frame = next_frame;
+ }
+ else
+ break;
+ }
+
+ if (displayed_frame)
+ past_frames = g_list_prepend (past_frames, displayed_frame);
+
+ n_frames++;
+ displayed_frame = unqueue_frame ();
+ displayed_frame->clock_time = stream_time_to_clock_time (displayed_frame->stream_time);
+ displayed_frame->frame_counter = gdk_frame_timings_get_frame_counter (timings);
+
+ collect_old_frames ();
+ print_statistics ();
+
+ gtk_widget_queue_draw (window);
+ }
+
+ gdk_paint_clock_request_phase (paint_clock, GDK_PAINT_CLOCK_PHASE_UPDATE);
+}
+
+static GOptionEntry options[] = {
+ { "fps", 'f', 0, G_OPTION_ARG_INT, &fps, "Frame rate", "FPS" },
+ { NULL }
+};
+
+int
+main(int argc, char **argv)
+{
+ GError *error = NULL;
+ GdkPaintClock *paint_clock;
+
+ if (!gtk_init_with_args (&argc, &argv, "",
+ options, NULL, &error))
+ {
+ g_printerr ("Option parsing failed: %s\n", error->message);
+ return 1;
+ }
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_app_paintable (window, TRUE);
+ gtk_window_set_default_size (GTK_WINDOW (window), 300, 300);
+
+ g_signal_connect (window, "draw",
+ G_CALLBACK (on_window_draw), NULL);
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ gtk_widget_show (window);
+
+ frame_queue = g_queue_new ();
+ g_mutex_init (&frame_mutex);
+ g_cond_init (&frame_cond);
+
+ g_thread_new ("Create Frames", create_frames_thread, NULL);
+
+ paint_clock = gtk_widget_get_paint_clock (window);
+ g_signal_connect (paint_clock, "update",
+ G_CALLBACK (on_update), NULL);
+ gdk_paint_clock_request_phase (paint_clock, GDK_PAINT_CLOCK_PHASE_UPDATE);
+
+ gtk_main ();
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]