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

Fixes #262.
This commit is contained in:
Pablo Stebler
2019-05-09 15:25:21 +02:00
committed by Jean-Baptiste Kempf
parent 8f7af99687
commit d400361524
3 changed files with 171 additions and 28 deletions
+101 -12
View File
@@ -30,15 +30,20 @@
#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
#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)
{
const char *pre_string = istty ? "\r" : "";
const char *post_string = istty ? "" : "\n";
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
}
if (num == 0xFFFFFFFFU) {
fprintf(stderr, "%sDecoded %u frames%s", pre_string, n, post_string);
} else {
fprintf(stderr, "%sDecoded %u/%u frames (%.1lf%%)%s",
pre_string, n, num, 100.0 * n / num, post_string);
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 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);
}
}
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, "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 @@ int main(const int argc, char *const *const argv) {
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 @@ int main(const int argc, char *const *const argv) {
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,14 +226,19 @@ int main(const int argc, char *const *const argv) {
cli_settings.outputfile,
&p.p, fps)) < 0)
{
if (frametimes) fclose(frametimes);
return res;
}
}
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,17 +263,24 @@ int main(const int argc, char *const *const argv) {
cli_settings.outputfile,
&p.p, fps)) < 0)
{
if (frametimes) fclose(frametimes);
return res;
}
}
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) {
if (!cli_settings.quiet && istty)
+62 -16
View File
@@ -46,6 +46,9 @@ static const char short_opts[] = "i:o:vql:s:";
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 @@ static const struct option long_opts[] = {
{ "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 @@ static void usage(const char *const app, const char *const reason, ...) {
}
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,13 +141,31 @@ static void error(const char *const app, const char *const optarg,
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");
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;
@@ -238,6 +265,25 @@ void parse(const int argc, char *const *const argv,
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 =
parse_unsigned(optarg, ARG_FRAME_THREADS, argv[0]);
+8
View File
@@ -35,9 +35,17 @@ typedef struct {
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,