shithub: dav1d

Download patch

ref: d400361524ce739db30d552a9e54809d812710c6
parent: 8f7af99687533d15a9b5d16abc7b9d7b0cd4dcd0
author: Pablo Stebler <pablo@stebler.xyz>
date: Thu May 9 11:25:21 EDT 2019

Add fps counter and --realtime, --frametimes and --realtimecache options

Fixes #262.

--- a/tools/dav1d.c
+++ b/tools/dav1d.c
@@ -30,9 +30,11 @@
 
 #include <assert.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <math.h>
 #include <stdio.h>
 #include <string.h>
+#include <time.h>
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
@@ -39,6 +41,9 @@
 #ifdef HAVE_IO_H
 # include <io.h>
 #endif
+#ifdef _WIN32
+# include <windows.h>
+#endif
 
 #include "dav1d/dav1d.h"
 
@@ -48,18 +53,70 @@
 
 #include "dav1d_cli_parse.h"
 
-static void print_stats(const int istty, const unsigned n,
-                        const unsigned num)
+static uint64_t get_time_nanos(void) {
+#ifdef _WIN32
+    LARGE_INTEGER frequency;
+    QueryPerformanceFrequency(&frequency);
+    LARGE_INTEGER t;
+    QueryPerformanceCounter(&t);
+    return 1000000000 * t.QuadPart / frequency.QuadPart;
+#else
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return 1000000000ULL * ts.tv_sec + ts.tv_nsec;
+#endif
+}
+
+static void sleep_nanos(uint64_t d) {
+#ifdef _WIN32
+    Sleep((unsigned)(d / 1000000));
+#else
+    const struct timespec ts = {
+        .tv_sec = (time_t)(d / 1000000000),
+        .tv_nsec = d % 1000000000,
+    };
+    nanosleep(&ts, NULL);
+#endif
+}
+
+static void synchronize(const int realtime, const unsigned cache,
+                        const unsigned n_out, const uint64_t nspf,
+                        const uint64_t tfirst, uint64_t *const elapsed,
+                        FILE *const frametimes)
 {
-    const char *pre_string = istty ? "\r" : "";
-    const char *post_string = istty ? "" : "\n";
+    const uint64_t tcurr = get_time_nanos();
+    const uint64_t last = *elapsed;
+    *elapsed = tcurr - tfirst;
+    if (realtime) {
+        const uint64_t deadline = nspf * n_out;
+        if (*elapsed < deadline) {
+            const uint64_t remaining = deadline - *elapsed;
+            if (remaining > nspf * cache) sleep_nanos(remaining - nspf * cache);
+            *elapsed = deadline;
+        }
+    }
+    if (frametimes) {
+        const uint64_t frametime = *elapsed - last;
+        fprintf(frametimes, "%" PRIu64 "\n", frametime);
+        fflush(frametimes);
+    }
+}
 
-    if (num == 0xFFFFFFFFU) {
-        fprintf(stderr, "%sDecoded %u frames%s", pre_string, n, post_string);
+static void print_stats(const int istty, const unsigned n, const unsigned num,
+                        const uint64_t elapsed, const double i_fps)
+{
+    if (istty) fputs("\r", stderr);
+    const double d_fps = 1e9 * n / elapsed;
+    const double speed = d_fps / i_fps;
+    if (num == 0xFFFFFFFF) {
+        fprintf(stderr, "Decoded %u frames", n);
     } else {
-        fprintf(stderr, "%sDecoded %u/%u frames (%.1lf%%)%s",
-                pre_string, n, num, 100.0 * n / num, post_string);
+        fprintf(stderr, "Decoded %u/%u frames (%.1lf%%)", n, num,
+                100.0 * n / num);
     }
+    if (i_fps)
+        fprintf(stderr, " - %.2lf/%.2lf fps (%.2lfx)", d_fps, i_fps, speed);
+    if (!istty) fputs("\n", stderr);
 }
 
 int main(const int argc, char *const *const argv) {
@@ -73,6 +130,9 @@
     Dav1dContext *c;
     Dav1dData data;
     unsigned n_out = 0, total, fps[2];
+    uint64_t nspf, tfirst, elapsed;
+    double i_fps;
+    FILE *frametimes = NULL;
     const char *version = dav1d_version();
 
     if (strcmp(version, DAV1D_VERSION)) {
@@ -126,6 +186,23 @@
     if ((res = dav1d_open(&c, &lib_settings)))
         return res;
 
+    if (cli_settings.frametimes)
+        frametimes = fopen(cli_settings.frametimes, "w");
+
+    if (cli_settings.realtime != REALTIME_CUSTOM) {
+        if (fps[1] == 0) {
+            i_fps = 0;
+            nspf = 0;
+        } else {
+            i_fps = (double)fps[0] / fps[1];
+            nspf = 1000000000ULL * fps[1] / fps[0];
+        }
+    } else {
+        i_fps = cli_settings.realtime_fps;
+        nspf = 1000000000.0 / cli_settings.realtime_fps;
+    }
+    tfirst = get_time_nanos();
+
     do {
         memset(&p, 0, sizeof(p));
         if ((res = dav1d_send_data(c, &data)) < 0) {
@@ -149,6 +226,7 @@
                                        cli_settings.outputfile,
                                        &p.p, fps)) < 0)
                 {
+                    if (frametimes) fclose(frametimes);
                     return res;
                 }
             }
@@ -155,8 +233,12 @@
             if ((res = output_write(out, &p)) < 0)
                 break;
             n_out++;
+            if (nspf) {
+                synchronize(cli_settings.realtime, cli_settings.realtime_cache,
+                            n_out, nspf, tfirst, &elapsed, frametimes);
+            }
             if (!cli_settings.quiet)
-                print_stats(istty, n_out, total);
+                print_stats(istty, n_out, total, elapsed, i_fps);
         }
 
         if (cli_settings.limit && n_out == cli_settings.limit)
@@ -181,6 +263,7 @@
                                        cli_settings.outputfile,
                                        &p.p, fps)) < 0)
                 {
+                    if (frametimes) fclose(frametimes);
                     return res;
                 }
             }
@@ -187,10 +270,16 @@
             if ((res = output_write(out, &p)) < 0)
                 break;
             n_out++;
+            if (nspf) {
+                synchronize(cli_settings.realtime, cli_settings.realtime_cache,
+                            n_out, nspf, tfirst, &elapsed, frametimes);
+            }
             if (!cli_settings.quiet)
-                print_stats(istty, n_out, total);
+                print_stats(istty, n_out, total, elapsed, i_fps);
         }
     }
+
+    if (frametimes) fclose(frametimes);
 
     input_close(in);
     if (out) {
--- a/tools/dav1d_cli_parse.c
+++ b/tools/dav1d_cli_parse.c
@@ -46,6 +46,9 @@
 enum {
     ARG_DEMUXER = 256,
     ARG_MUXER,
+    ARG_FRAME_TIMES,
+    ARG_REALTIME,
+    ARG_REALTIME_CACHE,
     ARG_FRAME_THREADS,
     ARG_TILE_THREADS,
     ARG_VERIFY,
@@ -62,8 +65,11 @@
     { "demuxer",        1, NULL, ARG_DEMUXER },
     { "muxer",          1, NULL, ARG_MUXER },
     { "version",        0, NULL, 'v' },
+    { "frametimes",     1, NULL, ARG_FRAME_TIMES },
     { "limit",          1, NULL, 'l' },
     { "skip",           1, NULL, 's' },
+    { "realtime",       2, NULL, ARG_REALTIME },
+    { "realtimecache",  1, NULL, ARG_REALTIME_CACHE },
     { "framethreads",   1, NULL, ARG_FRAME_THREADS },
     { "tilethreads",    1, NULL, ARG_TILE_THREADS },
     { "verify",         1, NULL, ARG_VERIFY },
@@ -94,21 +100,24 @@
     }
     fprintf(stderr, "Usage: %s [options]\n\n", app);
     fprintf(stderr, "Supported options:\n"
-            " --input/-i  $file:   input file\n"
-            " --output/-o $file:   output file\n"
-            " --demuxer $name:     force demuxer type ('ivf' or 'annexb'; default: detect from extension)\n"
-            " --muxer $name:       force muxer type ('md5', 'yuv', 'yuv4mpeg2' or 'null'; default: detect from extension)\n"
-            " --quiet/-q:          disable status messages\n"
-            " --limit/-l $num:     stop decoding after $num frames\n"
-            " --skip/-s $num:      skip decoding of the first $num frames\n"
-            " --version/-v:        print version and exit\n"
-            " --framethreads $num: number of frame threads (default: 1)\n"
-            " --tilethreads $num:  number of tile threads (default: 1)\n"
-            " --filmgrain $num:    enable film grain application (default: 1, except if muxer is md5)\n"
-            " --oppoint $num:      select an operating point of a scalable AV1 bitstream (0 - 32)\n"
-            " --alllayers $num:    output all spatial layers of a scalable AV1 bitstream (default: 1)\n"
-            " --verify $md5:       verify decoded md5. implies --muxer md5, no output\n"
-            " --cpumask $mask:     restrict permitted CPU instruction sets (0" ALLOWED_CPU_MASKS "; default: -1)\n");
+            " --input/-i  $file:    input file\n"
+            " --output/-o $file:    output file\n"
+            " --demuxer $name:      force demuxer type ('ivf' or 'annexb'; default: detect from extension)\n"
+            " --muxer $name:        force muxer type ('md5', 'yuv', 'yuv4mpeg2' or 'null'; default: detect from extension)\n"
+            " --quiet/-q:           disable status messages\n"
+            " --frametimes $file:   dump frame times to file\n"
+            " --limit/-l $num:      stop decoding after $num frames\n"
+            " --skip/-s $num:       skip decoding of the first $num frames\n"
+            " --realtime [$fract]:  limit framerate, optional argument to override input framerate\n"
+            " --realtimecache $num: set the size of the cache in realtime mode (default: 0)\n"
+            " --version/-v:         print version and exit\n"
+            " --framethreads $num:  number of frame threads (default: 1)\n"
+            " --tilethreads $num:   number of tile threads (default: 1)\n"
+            " --filmgrain $num:     enable film grain application (default: 1, except if muxer is md5)\n"
+            " --oppoint $num:       select an operating point of a scalable AV1 bitstream (0 - 32)\n"
+            " --alllayers $num:     output all spatial layers of a scalable AV1 bitstream (default: 1)\n"
+            " --verify $md5:        verify decoded md5. implies --muxer md5, no output\n"
+            " --cpumask $mask:      restrict permitted CPU instruction sets (0" ALLOWED_CPU_MASKS "; default: -1)\n");
     exit(1);
 }
 
@@ -132,7 +141,9 @@
           optarg, optname, shouldbe);
 }
 
-static unsigned parse_unsigned(char *optarg, const int option, const char *app) {
+static unsigned parse_unsigned(const char *const optarg, const int option,
+                               const char *const app)
+{
     char *end;
     const unsigned res = (unsigned) strtoul(optarg, &end, 0);
     if (*end || end == optarg) error(app, optarg, option, "an integer");
@@ -139,6 +150,22 @@
     return res;
 }
 
+static int parse_optional_fraction(const char *const optarg, const int option,
+                                   const char *const app, double *value)
+{
+    if (optarg == NULL) return 0;
+    char *end;
+    *value = strtod(optarg, &end);
+    if (*end == '/' && end != optarg) {
+        const char *optarg2 = end + 1;
+        *value /= strtod(optarg2, &end);
+        if (*end || end == optarg2) error(app, optarg, option, "a fraction");
+    } else if (*end || end == optarg) {
+        error(app, optarg, option, "a fraction");
+    }
+    return 1;
+}
+
 typedef struct EnumParseTable {
     const char *str;
     const int val;
@@ -237,6 +264,25 @@
             break;
         case ARG_MUXER:
             cli_settings->muxer = optarg;
+            break;
+        case ARG_FRAME_TIMES:
+            cli_settings->frametimes = optarg;
+            break;
+        case ARG_REALTIME:
+            // workaround to parse an optional argument of the form `--a b`
+            // (getopt only allows `--a=b`)
+            if (optarg == NULL && optind < argc && argv[optind] != NULL &&
+                argv[optind][0] != '-')
+            {
+                optarg = argv[optind];
+                optind++;
+            }
+            cli_settings->realtime = 1 + parse_optional_fraction(optarg,
+                ARG_REALTIME, argv[0], &cli_settings->realtime_fps);
+            break;
+        case ARG_REALTIME_CACHE:
+            cli_settings->realtime_cache =
+                parse_unsigned(optarg, ARG_REALTIME_CACHE, argv[0]);
             break;
         case ARG_FRAME_THREADS:
             lib_settings->n_frame_threads =
--- a/tools/dav1d_cli_parse.h
+++ b/tools/dav1d_cli_parse.h
@@ -35,9 +35,17 @@
     const char *inputfile;
     const char *demuxer;
     const char *muxer;
+    const char *frametimes;
     const char *verify;
     unsigned limit, skip;
     int quiet;
+    enum {
+        REALTIME_DISABLE = 0,
+        REALTIME_INPUT,
+        REALTIME_CUSTOM,
+    } realtime;
+    double realtime_fps;
+    unsigned realtime_cache;
 } CLISettings;
 
 void parse(const int argc, char *const *const argv,