| 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 | } |