Files
ffmpeg_build/contrib/ffmpeg-jsonstats.patch
2022-05-12 21:51:49 +02:00

441 lines
15 KiB
Diff

From df7dfb5351f77a672de5a6d613678888f560afcb Mon Sep 17 00:00:00 2001
From: Ingo Oppermann <ingo@oppermann.ch>
Date: Tue, 1 Jun 2021 08:53:52 +0200
Subject: [PATCH v20] JSON progress report (ffmpeg 4.4)
---
fftools/ffmpeg.c | 262 +++++++++++++++++++++++++++++++++++++++++--
fftools/ffmpeg.h | 1 +
fftools/ffmpeg_opt.c | 69 ++++++++++++
3 files changed, 322 insertions(+), 10 deletions(-)
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 46bb014..e5e00c9 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -1699,12 +1699,11 @@ static void print_final_stats(int64_t total_size)
}
}
-static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time)
+static void print_default_report(int is_last_report, int64_t timer_start, int64_t cur_time)
{
AVBPrint buf, buf_script;
OutputStream *ost;
- AVFormatContext *oc;
- int64_t total_size;
+ int64_t total_size = 0;
AVCodecContext *enc;
int frame_number, vid, i;
double bitrate;
@@ -1733,13 +1732,6 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
t = (cur_time-timer_start) / 1000000.0;
-
- oc = output_files[0]->ctx;
-
- total_size = avio_size(oc->pb);
- if (total_size <= 0) // FIXME improve avio_size() so it works with non seekable output too
- total_size = avio_tell(oc->pb);
-
vid = 0;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC);
@@ -1822,6 +1814,9 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
if (is_last_report)
nb_frames_drop += ost->last_dropped;
+
+ total_size += ost->data_size;
+ total_size += ost->enc_ctx->extradata_size;
}
secs = FFABS(pts) / AV_TIME_BASE;
@@ -1909,6 +1904,251 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
print_final_stats(total_size);
}
+static void print_json_report(int is_last_report, int64_t timer_start, int64_t cur_time)
+{
+ AVBPrint buf;
+ InputStream *ist;
+ OutputStream *ost;
+ uint64_t stream_size, total_size = 0;
+ AVCodecContext *enc, *dec;
+ int i, j;
+ uint64_t first_vid, first_frame_number = 0, first_packet_number = 0;
+ double speed;
+ int64_t min_pts = INT64_MIN + 1, pts = INT64_MIN + 1;
+ static int64_t last_time = -1;
+ int hours, mins, secs, us;
+ const char *hours_sign;
+ float t, q;
+ float first_q = -1;
+
+ if (!print_jsonstats && !is_last_report && !progress_avio) {
+ return;
+ }
+
+ if (!is_last_report) {
+ if (last_time == -1) {
+ last_time = cur_time;
+ return;
+ }
+ if ((cur_time - last_time) < 500000) {
+ return;
+ }
+ last_time = cur_time;
+ }
+
+ t = (cur_time-timer_start) / 1000000.0;
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ av_bprintf(&buf, "ffmpeg.progress:{");
+ av_bprintf(&buf, "\"inputs\":[");
+ for (i = 0; i < nb_input_files; i++) {
+ InputFile *f = input_files[i];
+
+ for (j = 0; j < f->nb_streams; j++) {
+ ist = input_streams[f->ist_index + j];
+ dec = ist->dec_ctx;
+
+ av_bprintf(&buf, "{");
+ av_bprintf(&buf, "\"index\":%d,\"stream\":%d,", i, j);
+
+ av_bprintf(&buf, "\"frame\":%"PRIu64",\"packet\":%"PRIu64",", ist->frames_decoded == 0 ? ist->nb_packets : ist->frames_decoded, ist->nb_packets);
+
+ av_bprintf(&buf, "\"size_kb\":%.0f", ist->data_size / 1024.0);
+
+ if(i == (nb_input_files - 1) && j == (f->nb_streams - 1)) {
+ av_bprintf(&buf, "}");
+ }
+ else {
+ av_bprintf(&buf, "},");
+ }
+ }
+ }
+
+ av_bprintf(&buf, "],");
+
+ // check libavcodec/utils.c:avcodec_string
+ // check libavformat/dump.c:av_dump_format
+ // check libavcodec/avcodec.h:struct AVCodec and struct AVCodecContext
+ // check libavformat/avformat.h:struct AVStream
+ // check fftools/ffmpeg.h:struct OutputStream
+
+ first_vid = 1;
+
+ av_bprintf(&buf, "\"outputs\":[");
+ for (i = 0; i < nb_output_streams; i++) {
+ OutputFile *f;
+ q = -1;
+ ost = output_streams[i];
+ f = output_files[ost->file_index];
+ enc = ost->enc_ctx;
+ if (!ost->stream_copy) {
+ q = ost->quality / (float) FF_QP2LAMBDA;
+ }
+
+ av_bprintf(&buf, "{");
+ av_bprintf(&buf, "\"index\":%d,\"stream\":%d,", ost->file_index, ost->index);
+
+ av_bprintf(&buf, "\"frame\":%"PRIu64",\"packet\":%"PRIu64",", ost->frames_encoded == 0 ? ost->packets_written : ost->frames_encoded, ost->packets_written);
+
+ if(enc->codec_type == AVMEDIA_TYPE_VIDEO) {
+ av_bprintf(&buf, "\"q\":%.1f,", q);
+
+ if(first_vid == 1) {
+ first_frame_number = ost->frames_encoded == 0 ? ost->packets_written : ost->frames_encoded;
+ first_packet_number = ost->packets_written;
+ first_q = q;
+
+ first_vid = 0;
+ }
+ }
+
+ /* compute min output value */
+ pts = INT64_MIN + 1;
+ if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) {
+ pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), ost->st->time_base, AV_TIME_BASE_Q));
+ min_pts = FFMAX(min_pts, av_rescale_q(av_stream_get_end_pts(ost->st), ost->st->time_base, AV_TIME_BASE_Q));
+ }
+
+ if (is_last_report) {
+ nb_frames_drop += ost->last_dropped;
+ }
+
+ stream_size = ost->data_size + ost->enc_ctx->extradata_size;
+ total_size += stream_size;
+
+ av_bprintf(&buf, "\"size_kb\":%.0f", stream_size / 1024.0);
+
+ if(i == (nb_output_streams - 1)) {
+ av_bprintf(&buf, "}");
+ }
+ else {
+ av_bprintf(&buf, "},");
+ }
+ }
+
+ av_bprintf(&buf, "],");
+
+ av_bprintf(&buf, "\"frame\":%"PRIu64",\"packet\":%"PRIu64",\"q\":%.1f,", first_frame_number, first_packet_number, first_q);
+
+ av_bprintf(&buf, "\"size_kb\":%.0f,", total_size / 1024.0);
+
+ secs = FFABS(min_pts) / AV_TIME_BASE;
+ us = FFABS(min_pts) % AV_TIME_BASE;
+ mins = secs / 60;
+ secs %= 60;
+ hours = mins / 60;
+ mins %= 60;
+ hours_sign = (min_pts < 0) ? "-" : "";
+
+ if(min_pts != AV_NOPTS_VALUE) {
+ av_bprintf(&buf, "\"time\":\"%s%dh%dm%d.%ds\",", hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE);
+ }
+
+ speed = t != 0.0 ? (double)min_pts / AV_TIME_BASE / t : -1;
+ av_bprintf(&buf, "\"speed\":%.3g,", speed);
+
+ av_bprintf(&buf, "\"dup\":%d,\"drop\":%d", nb_frames_dup, nb_frames_drop);
+ av_bprintf(&buf, "}");
+
+ if (print_jsonstats || is_last_report) {
+ fprintf(stderr, "%s\n", buf.str);
+ fflush(stderr);
+ }
+
+ av_bprint_finalize(&buf, NULL);
+}
+
+static void print_json_outputs() {
+ static int ost_all_initialized = 0;
+ int i;
+ int nb_initialized = 0;
+
+ if(print_jsonstats != 1 && print_stats != -1) {
+ return;
+ }
+
+ if(ost_all_initialized == 1) {
+ return;
+ }
+
+ // check if all outputs are initialized
+ for (i = 0; i < nb_output_streams; i++) {
+ OutputStream *ost = output_streams[i];
+ if (ost->initialized) {
+ nb_initialized++;
+ }
+ }
+
+ // only if all outputs are initialized, dump the outputs
+ if (nb_initialized == nb_output_streams) {
+ ost_all_initialized = 1;
+
+ AVBPrint buf;
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ av_bprintf(&buf, "ffmpeg.outputs:[");
+ for (i = 0; i < nb_output_streams; i++) {
+ OutputStream *ost = output_streams[i];
+ OutputFile *f = output_files[ost->file_index];
+ AVFormatContext *ctx = f->ctx;
+ AVStream *st = ost->st;
+ AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
+ AVCodecContext *enc = ost->enc_ctx;
+
+ av_bprintf(&buf, "{");
+ av_bprintf(&buf, "\"url\":\"%s\",\"format\":\"%s\",\"index\":%d,\"stream\":%d,", ctx->url, ctx->oformat->name, ost->file_index, ost->index);
+ av_bprintf(&buf, "\"type\":\"%s\",\"codec\":\"%s\",\"coder\":\"%s\",\"bitrate_kbps\":%"PRId64",", media_type_string(enc->codec_type), avcodec_get_name(enc->codec_id), ost->stream_copy ? "copy" : (enc->codec ? enc->codec->name : "unknown"), enc->bit_rate / 1000);
+ av_bprintf(&buf, "\"duration_sec\":%f,\"language\":\"%s\"", 0.0, lang != NULL ? lang->value : "und");
+
+ if(enc->codec_type == AVMEDIA_TYPE_VIDEO) {
+ float fps = 0;
+ if(st->avg_frame_rate.den && st->avg_frame_rate.num) {
+ fps = av_q2d(st->avg_frame_rate);
+ }
+
+ av_bprintf(&buf, ",\"fps\":%f,\"pix_fmt\":\"%s\",\"width\":%d,\"height\":%d", fps, st->codecpar->format == AV_PIX_FMT_NONE ? "none" : av_get_pix_fmt_name(st->codecpar->format), st->codecpar->width, st->codecpar->height);
+ }
+ else if(enc->codec_type == AVMEDIA_TYPE_AUDIO) {
+ char layout[128];
+
+ av_get_channel_layout_string(layout, sizeof(layout), enc->channels, enc->channel_layout);
+
+ av_bprintf(&buf, ",\"sampling_hz\":%d,\"layout\":\"%s\",\"channels\":%d", enc->sample_rate, layout, enc->channels);
+ }
+
+ if(i == (nb_output_streams - 1)) {
+ av_bprintf(&buf, "}");
+ }
+ else {
+ av_bprintf(&buf, "},");
+ }
+ }
+
+ av_bprintf(&buf, "]");
+
+ fprintf(stderr, "%s\n", buf.str);
+
+ av_bprint_clear(&buf);
+ }
+
+ return;
+}
+
+static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time)
+{
+ if (!print_stats && !print_jsonstats && !is_last_report && !progress_avio)
+ return;
+
+ if (print_stats == 1) {
+ print_default_report(is_last_report, timer_start, cur_time);
+ }
+ else {
+ print_json_report(is_last_report, timer_start, cur_time);
+ }
+}
+
static void ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par)
{
// We never got any input. Set a fake format, which will
@@ -3048,6 +3288,8 @@ static int check_init_output_file(OutputFile *of, int file_index)
av_dump_format(of->ctx, file_index, of->ctx->url, 1);
nb_output_dumped++;
+ print_json_outputs();
+
if (sdp_filename || want_sdp)
print_sdp();
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 606f2af..e7e9a46 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -621,6 +621,7 @@ extern int debug_ts;
extern int exit_on_error;
extern int abort_on_flags;
extern int print_stats;
+extern int print_jsonstats;
extern int64_t stats_period;
extern int qp_hist;
extern int stdin_interaction;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 807e783..d48c02a 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -41,6 +41,7 @@
#include "libavutil/parseutils.h"
#include "libavutil/pixdesc.h"
#include "libavutil/pixfmt.h"
+#include "libavutil/bprint.h"
#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass"
@@ -167,6 +168,7 @@ int debug_ts = 0;
int exit_on_error = 0;
int abort_on_flags = 0;
int print_stats = -1;
+int print_jsonstats = -1;
int qp_hist = 0;
int stdin_interaction = 1;
int frame_bits_per_raw_sample = 0;
@@ -3348,6 +3350,69 @@ static int open_files(OptionGroupList *l, const char *inout,
return 0;
}
+static void print_json_inputs() {
+ if(print_jsonstats != 1 && print_stats != -1) {
+ return;
+ }
+
+ AVBPrint buf;
+ int i, j;
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ av_bprintf(&buf, "ffmpeg.inputs:[");
+ for (i = 0; i < nb_input_files; i++) {
+ InputFile *f = input_files[i];
+ AVFormatContext *ctx = f->ctx;
+
+ float duration = 0;
+ if (ctx->duration != AV_NOPTS_VALUE) {
+ duration = (float)(ctx->duration + (ctx->duration <= INT64_MAX - 5000 ? 5000 : 0)) / (float)AV_TIME_BASE;
+ }
+
+ for (j = 0; j < f->nb_streams; j++) {
+ InputStream *ist = input_streams[f->ist_index + j];
+ AVCodecContext *dec = ist->dec_ctx;
+ AVStream *st = ist->st;
+ AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
+
+ av_bprintf(&buf, "{");
+ av_bprintf(&buf, "\"url\":\"%s\",\"format\":\"%s\",\"index\":%d,\"stream\":%d,", ctx->url, ctx->iformat->name, i, j);
+ av_bprintf(&buf, "\"type\":\"%s\",\"codec\":\"%s\",\"coder\":\"%s\",\"bitrate_kbps\":%"PRId64",", media_type_string(dec->codec_type), avcodec_get_name(dec->codec_id), dec->codec ? dec->codec->name : "unknown", dec->bit_rate / 1000);
+ av_bprintf(&buf, "\"duration_sec\":%f,\"language\":\"%s\"", duration, lang != NULL ? lang->value : "und");
+
+ if(dec->codec_type == AVMEDIA_TYPE_VIDEO) {
+ float fps = 0;
+ if(st->avg_frame_rate.den && st->avg_frame_rate.num) {
+ fps = av_q2d(st->avg_frame_rate);
+ }
+
+ av_bprintf(&buf, ",\"fps\":%f,\"pix_fmt\":\"%s\",\"width\":%d,\"height\":%d", fps, st->codecpar->format == AV_PIX_FMT_NONE ? "none" : av_get_pix_fmt_name(st->codecpar->format), st->codecpar->width, st->codecpar->height);
+ }
+ else if(dec->codec_type == AVMEDIA_TYPE_AUDIO) {
+ char layout[128];
+
+ av_get_channel_layout_string(layout, sizeof(layout), dec->channels, dec->channel_layout);
+
+ av_bprintf(&buf, ",\"sampling_hz\":%d,\"layout\":\"%s\",\"channels\":%d", dec->sample_rate, layout, dec->channels);
+ }
+
+ if(i == (nb_input_files - 1) && j == (f->nb_streams - 1)) {
+ av_bprintf(&buf, "}");
+ }
+ else {
+ av_bprintf(&buf, "},");
+ }
+ }
+ }
+
+ av_bprintf(&buf, "]");
+
+ fprintf(stderr, "%s\n", buf.str);
+
+ return;
+}
+
int ffmpeg_parse_options(int argc, char **argv)
{
OptionParseContext octx;
@@ -3381,6 +3446,8 @@ int ffmpeg_parse_options(int argc, char **argv)
goto fail;
}
+ print_json_inputs();
+
/* create the complex filtergraphs */
ret = init_complex_filters();
if (ret < 0) {
@@ -3584,6 +3651,8 @@ const OptionDef options[] = {
"enable automatic conversion filters globally" },
{ "stats", OPT_BOOL, { &print_stats },
"print progress report during encoding", },
+ { "jsonstats", OPT_BOOL, { &print_jsonstats },
+ "print JSON progress report during encoding", },
{ "stats_period", HAS_ARG | OPT_EXPERT, { .func_arg = opt_stats_period },
"set the period at which ffmpeg updates stats and -progress output", "time" },
{ "attach", HAS_ARG | OPT_PERFILE | OPT_EXPERT |
base-commit: 9dcc10e319b3533e0cef975c013b5fdf02e67ea2
--
2.24.3 (Apple Git-128)