ply-image: Attempt to improve animation consistency.

When displaying an animation, ply-image previously slept
before loading and displaying each image.  The code was
written such that intervals between frames would be
reasonably consistent when faced with consistent load times
for each image, but less so for inconsistent load times.

This change makes ply-image load the next image before
sleeping the required amount and displaying the image.

In my testing, this leads to very consistent frame intervals
during boot on Lumpy -- out of 18 frames, all but one or two
are shown 25 ms after the previous frame and the others are
all 26 ms.  Doing the same testing with the old code, I see
intervals ranging between 22 and 29 ms.

BUG=chromium-os:33971
TEST=update /etc/init/boot-splash.conf to pass --debug to
     ply-image and to redirect stderr to a file in /tmp;
     after booting, grep it for "showing" and check
     timestamps

Change-Id: Ibb82f8d38d8a19dce555028f9288b70789362edb
Reviewed-on: https://gerrit.chromium.org/gerrit/38433
Commit-Ready: Daniel Erat <derat@chromium.org>
Reviewed-by: Daniel Erat <derat@chromium.org>
Tested-by: Daniel Erat <derat@chromium.org>
diff --git a/src/ply-image.c b/src/ply-image.c
index dc35649..aa59708 100644
--- a/src/ply-image.c
+++ b/src/ply-image.c
@@ -32,6 +32,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <getopt.h>
+#include <inttypes.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -52,6 +53,12 @@
 #include "ply-monitor.h"
 #include "ply-utils.h"
 
+#define MS_PER_SEC 1000LL
+#define NS_PER_SEC (1000LL * 1000LL * 1000LL)
+#define NS_PER_MS  (NS_PER_SEC / MS_PER_SEC)
+
+/* Default framerate for animations. */
+#define DEFAULT_FRAMES_PER_SEC 20
 
 typedef union {
   uint32_t *as_pixels;
@@ -71,6 +78,13 @@
 } ply_image_t;
 
 
+/* Returns the current CLOCK_MONOTONIC time in milliseconds. */
+static int64_t get_monotonic_time_ms() {
+  struct timespec spec;
+  assert(clock_gettime(CLOCK_MONOTONIC, &spec) == 0);
+  return MS_PER_SEC * spec.tv_sec + spec.tv_nsec / NS_PER_MS;
+}
+
 static bool ply_image_open_file(ply_image_t *image) {
   assert(image != NULL);
 
@@ -351,13 +365,8 @@
 }
 
 
-#define MS_PER_SEC      1000
-#define NS_PER_SEC      (1000 * 1000 * 1000)
-#define NS_PER_MS       (NS_PER_SEC / MS_PER_SEC)
-#define FRAMES_PER_SEC  20
-
 /* Frame rate as milliseconds per frame */
-static long frame_interval_ms = MS_PER_SEC / FRAMES_PER_SEC;
+static int64_t frame_interval_ms = MS_PER_SEC / DEFAULT_FRAMES_PER_SEC;
 
 static bool set_frame_interval(const char *arg) {
   char *endptr;
@@ -373,49 +382,46 @@
 
 
 /*
- * Animate the series of images named on the command line.  The
- * animation is timed so that the average time per frame is
- * frame_interval_ms milliseconds.
+ * Animate the series of images named on the command line.  An attempt is made
+ * to let frame_interval_ms elapse between frames being displayed onscreen,
+ * although longer delays can occur.
  */
 static void process_images(
     ply_frame_buffer_t *buffer, int argc, char *argv[]) {
-  ply_image_t* image = NULL;
-  struct timespec curr_time, last_time;
-  int64_t sleep_ns = 0;
+  int64_t last_show_ms = -1;
 
-  clock_gettime(CLOCK_MONOTONIC, &last_time);
-  while (argc > 0) {
-    if (debug) {
-      fprintf(stderr, "time now %ld.%06ld",
-                      last_time.tv_sec,
-                      (last_time.tv_nsec + 500) / 1000);
-    }
-    if (sleep_ns > 0) {
-      struct timespec sleep_time;
-      sleep_time.tv_sec = sleep_ns / NS_PER_SEC;
-      sleep_time.tv_nsec = sleep_ns % NS_PER_SEC;
-      nanosleep(&sleep_time, NULL);
-    } else {
-      sleep_ns = 0;
-    }
+  for (; argc > 0; --argc, ++argv) {
+    int64_t now_ms = -1;
+    ply_image_t* image = NULL;
 
     if (debug) {
-      fprintf(stderr, "; slept for %ld.%06ld\n",
-                      sleep_ns / NS_PER_SEC,
-                      (sleep_ns + 500) / 1000);
+      fprintf(stderr, "loading %s at %" PRId64 "\n", argv[0],
+              get_monotonic_time_ms());
     }
     image = ply_image_from_file(argv[0]);
+
+    now_ms = get_monotonic_time_ms();
+    if (last_show_ms > 0) {
+      int64_t sleep_ms = frame_interval_ms - (now_ms - last_show_ms);
+      if (sleep_ms > 0) {
+        struct timespec sleep_spec;
+        if (debug) {
+          fprintf(stderr, "sleeping %" PRId64 " ms at %" PRId64 "\n",
+                  sleep_ms, now_ms);
+        }
+        sleep_spec.tv_sec = sleep_ms / MS_PER_SEC;
+        sleep_spec.tv_nsec = (sleep_ms % MS_PER_SEC) * NS_PER_MS;
+        nanosleep(&sleep_spec, NULL);
+      }
+    }
+
+    now_ms = get_monotonic_time_ms();
+    if (debug) {
+      fprintf(stderr, "showing %s at %" PRId64 "\n", argv[0], now_ms);
+    }
     ply_frame_buffer_show_image(buffer, image);
     ply_image_free(image);
-
-    clock_gettime(CLOCK_MONOTONIC, &curr_time);
-    sleep_ns += frame_interval_ms * NS_PER_MS +
-        NS_PER_SEC * (last_time.tv_sec - curr_time.tv_sec) +
-        last_time.tv_nsec - curr_time.tv_nsec;
-    last_time = curr_time;
-
-    argc--;
-    argv++;
+    last_show_ms = now_ms;
   }
 }