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,