Add fps counter and --realtime, --frametimes and --realtimecache options
Fixes #262.
This commit is contained in:
committed by
Jean-Baptiste Kempf
parent
8f7af99687
commit
d400361524
+101
-12
@@ -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
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user