Am 07.07.19 um 15:45 schrieb Paul B Mahol: > Could you just send whole patch? Here it is Please excuse the "under construction" state. The command line t use:
./ffmpeg -y -v warning -i in.mp4 -vf lineshiftrecover=10:-564:60:-60:report=shifts -q 5 out.mp4 I can send you the in.mp4 I use. -Ulf
From a54db59a5303a12f87c37cc963b03bc52726d258 Mon Sep 17 00:00:00 2001 From: Ulf Zibis <[email protected]> Date: 07.07.2019, 16:21:49 avfilter/lineshiftrecover: new filter (has memory access error) diff --git a/Changelog b/Changelog index 57476c3..41b7af9 100644 --- a/Changelog +++ b/Changelog @@ -34,6 +34,7 @@ - VP4 video decoder - IFV demuxer - derain filter +- lineshiftrecover video filter version 4.1: diff --git a/doc/filters.texi b/doc/filters.texi index 2d9af46..479fb19 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -16770,6 +16770,64 @@ @section swapuv Swap U & V plane. +@section lineshiftrecover (tapeglitchrecover) + +Detect irregularly horizontally shifted lines and recover them to their proper position. +Videos, grabbed from analog tape, e.g. VHS cassette, often have such artefacts, +typically at the bottom of the screen, mostly from +@url{https://bavc.github.io/avaa/artifacts/head_switching_noise.html head switching}. +Currently only the luminance of the signal is taken into account for the matching. +After using this filter, using a denoise filter on these lines likely makes sense. + +To find the most matching shift, each line is compared against it's before line (reference line). + +This filter accepts the following options: + +@table @option +@item lines +Number of lines to investigate. If negative, investigate upwards. Default is 2 % of @var{h}. + +@item start +Start the detection here. Default is @var{h - lines}, or @var{-lines} if negative. +If negative, crosscheck the found shift of the last line against the following line and approximate the shifts accordingly. + +@item span +Horizontal span where to search for the best matching shift. Default is 10 % of @var{w}. + +@item spanl +Selectively set the left span where to search. Because a shift to the left is rare, default is @var{-span / 2}. + +@item ignore +Pixels at the borders of the relative reference line to ignore for comparison. Default is @var{span}. + +@item ignorel +Pixels at the left border of the reference line to ignore. Default is @var{ignore * -spanl / span} +if span is both-sided, otherwise @var{ignore} if set or @var{-spanl}. + +To evaluate the impact of the options use @var{report=debug}. + +@item report +Set report mode. Default is @var{fails}. + +It accepts the following values: +@table @samp +@item fails +If no optimal shift value is found for a line, report the nearest and actually used shift. + +@item shifts +Report found and actually used shift values except 0, includes @var{fails}. + +@item verbose +Verbosely report all line differences (quadratic) per possible shift values, includes @var{shifts}. +It is recommended to only use this on one or very few single frames. + +@item debug +Report all single pixel differences (quadratic) per possible shift values, includes @var{verbose}. +As these report lines become very long, it is recommended to redirect the output into a file. +@end table + +@end table + @section telecine Apply telecine process to the video. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 07ea8d7..1da97b6 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -277,6 +277,7 @@ OBJS-$(CONFIG_LENSFUN_FILTER) += vf_lensfun.o OBJS-$(CONFIG_LIBVMAF_FILTER) += vf_libvmaf.o framesync.o OBJS-$(CONFIG_LIMITER_FILTER) += vf_limiter.o +OBJS-$(CONFIG_LINESHIFTRECOVER_FILTER) += vf_lineshiftrecover.o OBJS-$(CONFIG_LOOP_FILTER) += f_loop.o OBJS-$(CONFIG_LUMAKEY_FILTER) += vf_lumakey.o OBJS-$(CONFIG_LUT1D_FILTER) += vf_lut3d.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 9c846b1..93b0a73 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -262,6 +262,7 @@ extern AVFilter ff_vf_lensfun; extern AVFilter ff_vf_libvmaf; extern AVFilter ff_vf_limiter; +extern AVFilter ff_vf_lineshiftrecover; extern AVFilter ff_vf_loop; extern AVFilter ff_vf_lumakey; extern AVFilter ff_vf_lut; diff --git a/libavfilter/vf_lineshiftrecover.c b/libavfilter/vf_lineshiftrecover.c new file mode 100644 index 0000000..cc5acd6 --- /dev/null +++ b/libavfilter/vf_lineshiftrecover.c @@ -0,0 +1,759 @@ +/* + * Copyright (c) 2019 Ulf Zibis + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/colorspace.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/timestamp.h" +#include "drawutils.h" +#include "internal.h" + +typedef struct Borders { + int left, right, top, bottom; +} Borders; + +typedef struct LineShiftContext { + const AVClass *class; + /* Command line options: */ + int lines, start, span, spanl, ignore, ignorel; + int report; + uint8_t rgba_color[4]; + /* Internal: */ + int crosscheck; + int span_l, span_r, ign_l, ign_r; + int nb_planes; + int planewidth[4]; + int planeheight[4]; + Borders borders[4]; + void *filler[4]; + int16_t *shifts; + int16_t **shifts_sums; + + void (*recoverlineshifts)(struct AVFilterLink *inlink, AVFrame *frame); +} LineShiftContext; + +enum Reports { R_FAILS, R_SHIFTS, R_VERBOSE, R_DEBUG, R_MAX }; + +#define OFFSET(x) offsetof(LineShiftContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM +static const AVOption lineshiftrecover_options[] = { + { "lines", "set lines to investigate", OFFSET(lines), AV_OPT_TYPE_INT, {.i64=INT_MAX}, INT_MIN, INT_MAX, FLAGS }, + { "start", "set start of investigation", OFFSET(start), AV_OPT_TYPE_INT, {.i64=INT_MAX}, INT_MIN, INT_MAX, FLAGS }, + { "span", "set span to investigate", OFFSET(span), AV_OPT_TYPE_INT, {.i64=INT_MAX}, INT_MIN, INT_MAX, FLAGS }, + { "spanl", "set left span", OFFSET(spanl), AV_OPT_TYPE_INT, {.i64=INT_MAX}, INT_MIN, INT_MAX, FLAGS }, + { "ignore", "set border to ignore", OFFSET(ignore), AV_OPT_TYPE_INT, {.i64=INT_MAX}, INT_MIN, INT_MAX, FLAGS }, + { "ignorel", "set left border to ignore", OFFSET(ignorel), AV_OPT_TYPE_INT, {.i64=INT_MAX}, INT_MIN, INT_MAX, FLAGS }, + { "report", "set the report mode", OFFSET(report), AV_OPT_TYPE_INT, {.i64=R_FAILS}, 0, R_MAX-1, FLAGS, "report" }, + { "fails", NULL, 0, AV_OPT_TYPE_CONST, {.i64=R_FAILS}, 0, 0, FLAGS, "report" }, + { "shifts", NULL, 0, AV_OPT_TYPE_CONST, {.i64=R_SHIFTS}, 0, 0, FLAGS, "report" }, + { "verbose", NULL, 0, AV_OPT_TYPE_CONST, {.i64=R_VERBOSE}, 0, 0, FLAGS, "report" }, + { "debug", NULL, 0, AV_OPT_TYPE_CONST, {.i64=R_DEBUG}, 0, 0, FLAGS, "report" }, +/* + { "color", "set the color for the fixed mode", OFFSET(rgba_color), AV_OPT_TYPE_COLOR, {.str = "black"}, .flags = FLAGS }, +*/ + { NULL } +}; + +AVFILTER_DEFINE_CLASS(lineshiftrecover); + +/***********************/ +/* Private functions */ +/***********************/ + +static float diff8(LineShiftContext *s, uint8_t *lineref, int shift_ref, int width, uint8_t *line, int shift) +{ + int startref = FFMAX(-shift, s->ign_l); + int tailref = FFMAX(shift, s->ign_r); +/* + int widthdiff = width - FFMAX(-shift, s->ign_r) - FFMAX(shift, s->ign_l); +*/ + int widthdiff = width - startref - tailref; + int start = FFMAX(shift + s->ign_l, 0); + float sum = 0; + char printwidth[7]; + + int debug = s->report == R_DEBUG; + if (debug) { + int tab = 6; + char format1[16] = "x: "; + char format2[16] = "\nlineref:"; + char format3[16] = "\nline: "; + char format4[16] = "\ndiff: "; + const char space[] = ""; + snprintf(printwidth, sizeof(printwidth), "%%%ds", startref * tab); + strcat(format3, printwidth); + strcat(format4, printwidth); + snprintf(printwidth, sizeof(printwidth), "%%%dd", tab); +/* + av_log(NULL, AV_LOG_ERROR, "shift: %4d, lineref: %x, startref: %d\n", shift, lineref, startref); +*/ + av_log(NULL, AV_LOG_ERROR, format1, space); + for (int x = 0; x < width; x++) + av_log(NULL, AV_LOG_ERROR, printwidth, x); + av_log(NULL, AV_LOG_ERROR, format2, space); + for (int x = 0; x < width; x++) + av_log(NULL, AV_LOG_ERROR, printwidth, lineref[x]); + av_log(NULL, AV_LOG_ERROR, format3, space); + for (int x = start; x < start + widthdiff; x++) + av_log(NULL, AV_LOG_ERROR, printwidth, line[x]); + av_log(NULL, AV_LOG_ERROR, format4, space); + } + + for (int x = 0; x < widthdiff; x++) { + float diff = pow(line[start + x] - lineref[startref + x], 2); + sum += diff; + if (debug) + av_log(NULL, AV_LOG_ERROR, printwidth, (int)diff); + } + if (debug) + av_log(NULL, AV_LOG_ERROR, "\n"); + return sum / widthdiff; +} + +static float diff8_3(LineShiftContext *s, uint8_t *lineref, int shift_ref, int width, uint8_t *line, int shift) +{ + int startref = FFMAX3(-shift_ref, -shift, s->ign_l); + int tailref = FFMAX3(shift_ref, shift, s->ign_r); + int widthdiff = width - startref - tailref; + int start = FFMAX(shift + s->ign_l, 0); + float sum = 0; + + lineref += shift_ref; + if (s->report < R_DEBUG) + for (int x = widthdiff; x-- > 0; ) // loop against 0 is faster + sum += pow(line[start + x] - lineref[startref + x], 2); + else { + av_log(NULL, AV_LOG_ERROR, "lineref: %p, shift_ref: %4d, startref: %d, tailref: %d, line: %p, shift: %4d, start: %d\n", + lineref, shift_ref, startref, tailref, line, shift, start); + int tab = 6; + char fieldformat[16]; + char formatx[16] = "x: "; + char formatlr[16] = "\nlineref:"; + char formatln[16] = "\nline: "; + char formatdf[16] = "\ndiff: "; + const char space[] = ""; + snprintf(fieldformat, sizeof(fieldformat), "%%%ds", startref * tab); + strcat(formatln, fieldformat); + strcat(formatdf, fieldformat); + snprintf(fieldformat, sizeof(fieldformat), "%%%dd", tab); + av_log(NULL, AV_LOG_ERROR, formatx, space); + for (int x = 0; x < width; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, x); + av_log(NULL, AV_LOG_ERROR, formatlr, space); + for (int x = 0; x < width; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, lineref[x]); + av_log(NULL, AV_LOG_ERROR, formatln, space); + for (int x = start; x < start + widthdiff; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, line[x]); + av_log(NULL, AV_LOG_ERROR, formatdf, space); + for (int x = 0; x < widthdiff; x++) { + float diff = pow(line[start + x] - lineref[startref + x], 2); + av_log(NULL, AV_LOG_ERROR, fieldformat, (int)diff); + sum += diff; + } + av_log(NULL, AV_LOG_ERROR, "\n"); + } + return sum / widthdiff; +} + +static float diff8_debug(LineShiftContext *s, uint8_t *lineref, int shift_ref, int width, uint8_t *line, int shift) +{ + av_log(NULL, AV_LOG_ERROR, "lineref: %p, shift_ref: %4d, width: %d, line: %p, shift: %4d\n", + lineref, shift_ref, width, line, shift); + int ref_start = FFMAX(-shift_ref, 0); + int ref_end = width - FFMAX(shift_ref, 0); + int line_start = FFMAX(shift, 0); + int line_end = width - FFMAX(-shift, 0); + int diff_start = FFMAX3(-shift_ref, -shift, s->ign_l); + int diff_width = width - diff_start - FFMAX3(shift_ref, shift, s->ign_r); + int line_diff_start = FFMAX(shift + s->ign_l, 0); + float sum = 0; + lineref += shift_ref; + av_log(NULL, AV_LOG_ERROR, "lineref: %p, ref_start: %d, ref_end: %d, line_start: %d, line_end: %d, diff_start: %d, diff_width: %d, line_diff_start: %d\n", + lineref, ref_start, ref_end, line_start, line_end, diff_start, diff_width, line_diff_start); + + int tab = 6; + char fieldformat[16]; + char formatx[16] = "x: "; + char formatlr[16] = "\nlineref:"; + char formatln[16] = "\nline: "; + char formatdf[16] = "\ndiff: "; + const char indent[] = ""; + snprintf(fieldformat, sizeof(fieldformat), "%%%ds", ref_start * tab); + strcat(formatlr, fieldformat); + snprintf(fieldformat, sizeof(fieldformat), "%%%ds", (width - line_end) * tab); + strcat(formatln, fieldformat); + snprintf(fieldformat, sizeof(fieldformat), "%%%ds", diff_start * tab); + strcat(formatdf, fieldformat); + snprintf(fieldformat, sizeof(fieldformat), "%%%dd", tab); + av_log(NULL, AV_LOG_ERROR, formatx, indent); + for (int x = 0; x < width; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, x); + av_log(NULL, AV_LOG_ERROR, formatlr, indent); + for (int x = ref_start; x < ref_end; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, lineref[x]); + av_log(NULL, AV_LOG_ERROR, formatln, indent); + for (int x = line_start; x < line_end; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, line[x]); + av_log(NULL, AV_LOG_ERROR, formatdf, indent); + + lineref += diff_start; + line += line_diff_start; + for (int x = 0; x < diff_width; x++) { + float diff = pow(lineref[x] - line[x], 2); + av_log(NULL, AV_LOG_ERROR, fieldformat, (int)diff); + sum += diff; + } + av_log(NULL, AV_LOG_ERROR, "\n"); + return sum / diff_width; +} + +inline static float diff8_5(uint8_t *lineref, uint8_t *line, int width) +{ + float sum = 0; + for (int x = width; x-- > 0; ) // loop against 0 is faster + sum += pow(lineref[x] - line[x], 2); + return sum / width; + +} + +inline static float diff8_5_debug(uint8_t *lineref, int shift_ref, uint8_t *line, int shift, int width, int diff_start, int diff_width) +{ + av_log(NULL, AV_LOG_ERROR, "lineref: %p, shift_ref: %4d, width: %d, line: %p, shift: %4d\n", + lineref, shift_ref, width, line, shift); + int ref_start = FFMAX(-shift_ref, 0); + int ref_end = width - FFMAX(shift_ref, 0); + int line_start = FFMAX(shift, 0); + int line_end = width - FFMAX(-shift, 0); + float sum = 0; + av_log(NULL, AV_LOG_ERROR, "ref_start: %d, ref_end: %d, line_start: %d, line_end: %d, diff_start: %d, diff_width: %d\n", + ref_start, ref_end, line_start, line_end, diff_start, diff_width); + + int tab = 6; + char fieldformat[16]; + char formatx[16] = "x: "; + char formatlr[16] = "\nlineref:"; + char formatln[16] = "\nline: "; + char formatdf[16] = "\ndiff: "; + const char indent[] = ""; + snprintf(fieldformat, sizeof(fieldformat), "%%%ds", ref_start * tab); + strcat(formatlr, fieldformat); + snprintf(fieldformat, sizeof(fieldformat), "%%%ds", (width - line_end) * tab); + strcat(formatln, fieldformat); + snprintf(fieldformat, sizeof(fieldformat), "%%%ds", diff_start * tab); + strcat(formatdf, fieldformat); + snprintf(fieldformat, sizeof(fieldformat), "%%%dd", tab); + av_log(NULL, AV_LOG_ERROR, formatx, indent); + for (int x = 0; x < width; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, x); + av_log(NULL, AV_LOG_ERROR, formatlr, indent); + for (int x = ref_start; x < ref_end; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, lineref[x]); + av_log(NULL, AV_LOG_ERROR, formatln, indent); + for (int x = line_start; x < line_end; x++) + av_log(NULL, AV_LOG_ERROR, fieldformat, line[x]); + av_log(NULL, AV_LOG_ERROR, formatdf, indent); + + lineref += diff_start; + line += diff_start + shift; + for (int x = 0; x < diff_width; x++) { + float diff = pow(lineref[x] - line[x], 2); + av_log(NULL, AV_LOG_ERROR, fieldformat, (int)diff); + sum += diff; + } + av_log(NULL, AV_LOG_ERROR, "\n"); + return sum / diff_width; +} + +static int calculate_shift8(LineShiftContext *s, uint8_t *lineref, int shift_ref, uint8_t *line, int width) +{ +/* + av_log(NULL, AV_LOG_ERROR, "line: %16X, lineref: %16X, width: %4d\n", line, lineref, width); +*/ + int shift_best = 0; + float diff, diff_min = MAXFLOAT; + lineref += shift_ref; + + for (int shift = s->span_l; shift <= s->span_r; shift++) { + int diff_start = FFMAX3(s->ign_l, -shift_ref, -shift); + int diff_width = width - diff_start - FFMAX3(s->ign_r, shift_ref, shift); + + if (s->report < R_DEBUG) + diff = diff8_5(lineref + diff_start, line + diff_start + shift, diff_width); + else + diff = diff8_5_debug(lineref, shift_ref, line, shift, width, diff_start, diff_width); +/* + if (diff <= diff_min) { + if (diff < diff_min || abs(x) < abs(shift)) // prefer smaller shift if ambiguous + shift_best = shift; + if (diff < diff_min) + diff_min = diff; + } +*/ + if (diff < diff_min) + goto all; + if (diff == diff_min && abs(shift) < abs(shift_best)) // prefer smaller shift if ambiguous + goto shift; + else + goto none; +all: diff_min = diff; +shift: shift_best = shift; +none: + + if (s->report >= R_VERBOSE) + av_log(NULL, AV_LOG_ERROR, "shift: %3d, diff: %8.2f, diff_min: %8.2f, shift_best: %3d\n", shift, diff, diff_min, shift_best); + } + return shift_best; +} + +static void recoverlineshifts8(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + LineShiftContext *s = ctx->priv; + char *ptsstr = av_ts2str(frame->pts); + char *timestr = av_ts2timestr(frame->pts, &inlink->time_base); + char *minutestr = av_ts2minutestr(frame->pts, &inlink->time_base); + uint8_t *data = frame->data[0]; + int16_t lz = frame->linesize[0] / sizeof(*data); + int width = s->planewidth[0]; + int height = s->planeheight[0]; + int increment = s->lines >= 0 ? 1 : -1; // if negative, run upwards +/* + uint8_t *temp = (uint8_t *)s->filler[0]; +*/ + int16_t *shifts = s->shifts; + +/* + av_log(NULL, AV_LOG_ERROR, "start: %d, lines: %d, increment: %d, width: %d, span_l: %d, span_r: %d, ign_l: %d, ign_r: %d\n", + s->start, s->lines, increment, width, s->span_l, s->span_r, s->ign_l, s->ign_r); +*/ + if (s->report >= R_SHIFTS) +/* + av_log(NULL, AV_LOG_ERROR, "calculated shifts of frame %6d ...\n", frame->display_picture_number); + av_log(NULL, AV_LOG_ERROR, "calculated shifts of frame %6d ...\n", frame->coded_picture_number); +*/ + av_log(NULL, AV_LOG_ERROR, "calculated shifts of frame %6ld, pts: %-10s, time: %7s ...\n", + inlink->frame_count_out, ptsstr, minutestr); + int y = s->start; + for (; y != s->start + s->lines; y += increment) { +/* + av_log(NULL, AV_LOG_ERROR, "y: %d, yref: %d\n", y, y - increment); + av_log(NULL, AV_LOG_ERROR, "y: %4d, yref: %4d, data: %X, y*lz: %4d, yref*lz: %4d\nlineref:", y, y - increment, data, y*lz, (y - increment)*lz); +*/ + if (s->report >= R_VERBOSE) + av_log(NULL, AV_LOG_ERROR, "differences of line %4d against %4d in span %4d to %4d ...\n", + y, y - increment, s->span_l, s->span_r); + shifts[y] = calculate_shift8(s, data + (y - increment) * lz, shifts[y - increment], data + y * lz, width); + + switch (s->report) { + case R_DEBUG: + case R_VERBOSE: + case R_SHIFTS: if (s->report > R_SHIFTS || shifts[y] != 0) + av_log(NULL, AV_LOG_ERROR, "shift of line %d against %d was: %d\n", + y, y - increment, shifts[y]); + case R_FAILS: if (shifts[y] == s->span_l || shifts[y] == s->span_r) + av_log(ctx, AV_LOG_WARNING, + "frame: %6ld, pts: %7s, time: %-10s - optimal shift in line %d against %d seems outside of span (%d - %d), was: %d\n", +/* + "frame: %6d, pts: %10ld - optimal shift in line %d against %d seems outside of span (%d - %d), was: %d\n", + frame->display_picture_number, frame->pts, y, y - increment, s->span_l, s->span_r, shifts[y]); + frame->coded_picture_number, frame->pts, y, y - increment, s->span_l, s->span_r, shifts[y]); + frame_count, frame->pts, y, y - increment, s->span_l, s->span_r, shifts[y]); +*/ + inlink->frame_count_out, ptsstr, minutestr, y, y - increment, s->span_l, s->span_r, shifts[y]); + } + } + if (s->crosscheck) { + if (s->report >= R_VERBOSE) + av_log(NULL, AV_LOG_ERROR, "crosscheck of line %d against %d ...\n", y - increment, y); + int shift_cc = calculate_shift8(s, data + y * lz, 0, data + (y - increment) * lz, width); + if (shift_cc != shifts[y - increment]) { + if (s->report >= R_SHIFTS) + av_log(NULL, AV_LOG_ERROR, "shift crosscheck of line %d against %d was: %d\nshifts of: ", + y - increment, y, shift_cc); + for (int y = 0; y != s->lines; y += increment) { + if (s->report >= R_SHIFTS) + av_log(NULL, AV_LOG_ERROR, "%4d", shifts[s->start + y]); + shifts[s->start + y] -= (shifts[s->start + s->lines - increment] - shift_cc) * (y + increment) / s->lines; + } + if (s->report >= R_SHIFTS) { + av_log(NULL, AV_LOG_ERROR, "\nappproximated to:"); + for (int y = 0; y != s->lines; y += increment) + av_log(NULL, AV_LOG_ERROR, "%4d", shifts[s->start + y]); + av_log(NULL, AV_LOG_ERROR, "\n"); + } + } + if (shift_cc == s->span_l || shift_cc == s->span_r) + av_log(ctx, AV_LOG_WARNING, + "frame: %6ld, pts: %7s, time: %-10s - optimal shift crosscheck in line %d against %d seems outside of span (%d - %d), was: %d\n", +/* + "frame: %6d, pts: %10ld - optimal shift crosscheck in line %d against %d seems outside of span (%d - %d), was: %d\n", + frame->display_picture_number, frame->pts, y, y - increment, s->span_l, s->span_r, shift_cc); + frame->coded_picture_number, frame->pts, y, y - increment, s->span_l, s->span_r, shift_cc); + frame_count, frame->pts, y, y - increment, s->span_l, s->span_r, shift_cc); +*/ + inlink->frame_count_out, ptsstr, minutestr, y, y - increment, s->span_l, s->span_r, shift_cc); + if (s->report >= R_SHIFTS) + s->shifts_sums[s->lines][shift_cc - s->span_l] += 1; + } + if (s->report >= R_SHIFTS) +/* + for (int y = s->start; y != s->start + s->lines; y += increment) { + for (int l = s->lines; --l >= 0; ) { + s->shifts_sums[l][shifts[y] - s->span_l] += 1; + } + } +*/ + for (int l = s->lines; --l >= 0; ) { + av_log(NULL, AV_LOG_ERROR, "line: %4d, shifts[%4d] %3d:, shifts_sums[%4d[%3d]: %6d\n", + l, s->start + l * increment, shifts[s->start + l * increment], l, shifts[s->start + l * increment] - s->span_l, s->shifts_sums[l][shifts[s->start + l * increment] - s->span_l]); + s->shifts_sums[l][shifts[s->start + l * increment] - s->span_l] += 1; + } + /* move the lines to their correct position */ + for (int y = s->start; y != s->start + s->lines; y += increment) { + if (shifts[y] != 0) { // shift current line + memmove(data + y * lz + FFMAX(0, -shifts[y]), data + y * lz + FFMAX(0, shifts[y]), + (width - abs(shifts[y])) * sizeof(*data)); + memcpy(data + y * lz, data + (y - increment) * lz, + FFMAX(0, -shifts[y]) * sizeof(*data)); // fill left gap from reference line + memcpy(data + y * lz + width - FFMAX(0, shifts[y]), data + (y - increment) * lz + width - FFMAX(0, shifts[y]), + FFMAX(0, shifts[y]) * sizeof(*data)); // fill right gap from reference line + } + if (shifts[y - increment] != 0) { // average the gaps of before line with content of current line +/* + memcpy(temp, data + (y - increment) * lz, width); + if (s->report >= R_VERBOSE) + av_log(NULL, AV_LOG_ERROR, "diff old line %4d against it's now averaged with new %4d ...\n", y - increment, y); +*/ +/* + for (int x = -shift_old; x-- > 0; ) + data[(y - increment) * lz + x] = (data[(y - increment) * lz + x] + data[y * lz + x]) / 2; + for (int x = width - shift_old; x < width; x++) + data[(y - increment) * lz + x] = (data[(y - increment) * lz + x] + data[y * lz + x]) / 2; +*/ +/* + int ign_l = s->ign_l, ign_r = s->ign_r; + s->ign_l = 0; s->ign_r = 0; + float diff = diff8(s, temp, width, data + (y - increment) * lz, 0); + s->ign_l = ign_l; s->ign_r = ign_r; + if (s->report >= R_VERBOSE) + av_log(NULL, AV_LOG_ERROR, "diff shifted against averaged: %8.2f\n", diff); +*/ + } + } +} + +static void recoverlineshifts16(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + LineShiftContext *s = ctx->priv; + for (int p = 0; p < s->nb_planes; p++) { + uint16_t *data = (uint16_t *)frame->data[p]; + int lz = frame->linesize[p] / sizeof(*data); + int width = s->planewidth[p]; + int left = s->borders[p].left; + int right = width - s->borders[p].right; + int height = s->planeheight[p] * lz; + int top = s->borders[p].top * lz; + int bottom = height - s->borders[p].bottom * lz; + + /* fill left and right borders from top to bottom border */ + if (left + width > right) // in case skip for performance + for (int y = top; y < bottom; y += lz) { + for (int x = left; x > 0; ) + data[y + --x] = data[y + left]; + for (int x = right; x < width; x++) + data[y + x] = data[y + right - 1]; + } + + /* fill top and bottom borders */ + for (int y = top; y > 0; ) + memcpy(data + (y -= lz), data + top, width * sizeof(*data)); + for (int y = bottom; y < height; y += lz) + memcpy(data + y, data + bottom - lz, width * sizeof(*data)); + } +} + +/*****************************/ +/* Global access functions */ +/*****************************/ + +static av_cold int init(AVFilterContext *ctx) +{ + LineShiftContext *s = ctx->priv; + + return 0; +} + +static int config_props(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + LineShiftContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + int depth = desc->comp[0].depth; + + s->nb_planes = desc->nb_components; + s->planewidth [0] = s->planewidth [3] = inlink->w; + s->planeheight[0] = s->planeheight[3] = inlink->h; + s->planewidth [1] = s->planewidth [2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); + s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + + s->crosscheck = s->start < 0 ? 1 : 0; + s->lines = (s->lines != INT_MAX) ? s->lines : inlink->h * 0.02; + s->start = (s->start != INT_MAX) ? abs(s->start) : (s->lines >= 0 ? inlink->h : 0) - s->lines; + s->span_r = (s->span != INT_MAX) ? s->span : inlink->w * 0.1; + s->span_l = (s->spanl != INT_MAX) ? s->spanl : -s->span_r / 2; + s->ign_r = (s->ignore != INT_MAX) ? s->ignore : FFMAX(s->span_r, 0); + s->ign_l = (s->ignorel != INT_MAX) ? s->ignorel : + (s->span_l < 0) && (s->span_r > 0) + ? s->ign_r * -s->span_l / s->span_r // for both-sided span + : (s->ignore != INT_MAX) ? s->ignore : FFMAX(-s->span_l, 0); // one-sided span +/* + s->ign_r = (s->ignore != INT_MIN) ? + (s->span_r >= 0 ? s->ignore : FFMAX(s->ignore + s->span_l, 0) : FFMAX(s->span_r, 0); + s->ign_l = (s->ignorel != INT_MIN) ? s->ignorel : + (s->span_l < 0) + ? (s->span_r > 0) + ? s->ign_r * -s->span_l / s->span_r // normal case + : (s->ignore != INT_MIN) ? s->ignore - s->span_l : -s->span_l + : FFMAX(s->ign_r - s->span_r, 0); // span leftsided +*/ + av_log(ctx, s->report >= R_SHIFTS ? AV_LOG_WARNING : AV_LOG_INFO, + "calculated from options -> crosscheck: %d, start: %d, lines: %d, span_l: %d, span_r: %d, ign_l: %d, ign_r: %d\n", + s->crosscheck, s->start, s->lines, s->span_l, s->span_r, s->ign_l, s->ign_r); + for (int p = 0; p < s->nb_planes; p++) + s->filler[p] = av_malloc(s->planewidth[p] * (depth <= 8 ? sizeof(uint8_t) : sizeof(uint16_t))); + s->shifts = av_malloc(s->planeheight[0] * sizeof(*s->shifts)); + if (s->report >= R_SHIFTS) { +/* + s->shifts_sums = av_malloc((s->lines + 1) * sizeof(*s->shifts_sums)); + av_log(NULL, AV_LOG_WARNING, "sizeof ptr: %ld, width: %ld\n", sizeof(*s->shifts_sums), (s->lines + 1) * sizeof(*s->shifts_sums)); + for (int l = s->lines; l >= 0; l--) { + s->shifts_sums[l] = av_malloc((s->span_r - s->span_l + 1) * sizeof(**s->shifts_sums)); + av_log(NULL, AV_LOG_WARNING, "sizeof elem: %ld, line width: %ld\n", sizeof(**s->shifts_sums), (s->span_r - s->span_l + 1) * sizeof(**s->shifts_sums)); + av_log(NULL, AV_LOG_WARNING, "Line: %2d ->", l); + for (int y = s->span_l; y <= s->span_r; y++) + av_log(NULL, AV_LOG_WARNING, "%4d>%6d", y, s->shifts_sums[l][-s->span_l + y]); + av_log(NULL, AV_LOG_WARNING, "\n"); + memset(s->shifts_sums[l], 0, (s->span_r - s->span_l + 1) * sizeof(**s->shifts_sums)); + av_log(NULL, AV_LOG_WARNING, "Line: %2d ->", l); + for (int y = s->span_l; y <= s->span_r; y++) + av_log(NULL, AV_LOG_WARNING, "%4d>%6d", y, s->shifts_sums[l][-s->span_l + y]); + av_log(NULL, AV_LOG_WARNING, "\n"); + bzero(s->shifts_sums[l], (s->span_r - s->span_l + 1) * sizeof(**s->shifts_sums)); + av_log(NULL, AV_LOG_WARNING, "Line: %2d ->", l); + for (int y = s->span_l; y <= s->span_r; y++) + av_log(NULL, AV_LOG_WARNING, "%4d>%6d", y, s->shifts_sums[l][-s->span_l + y]); + av_log(NULL, AV_LOG_WARNING, "\n"); + } +*/ + s->shifts_sums = av_malloc(s->lines + 1 * sizeof(**s->shifts_sums)); +/* + s->shifts_sums = av_calloc(s->lines + 1, sizeof(**s->shifts_sums)); +*/ + for (int l = s->lines; l >= 0; l--) { + s->shifts_sums[l] = av_calloc(s->span_r - s->span_l + 1, sizeof(**s->shifts_sums)); +/* + memset(s->shifts_sums[l], 0, (s->span_r - s->span_l + 1) * sizeof(**s->shifts_sums)); +*/ + } + } + + s->recoverlineshifts = depth <= 8 ? recoverlineshifts8 : recoverlineshifts16; + + /* + if (inlink->w < s->left + s->right || + inlink->w <= s->left || + inlink->w <= s->right || + inlink->h < s->top + s->bottom || + inlink->h <= s->top || + inlink->h <= s->bottom || + inlink->w < s->left * 2 || + inlink->w < s->right * 2 || + inlink->h < s->top * 2 || + inlink->h < s->bottom * 2) { + av_log(ctx, AV_LOG_ERROR, "Borders are bigger than input frame size.\n"); + return AVERROR(EINVAL); + } + + s->nb_planes = desc->nb_components; + + s->planewidth [0] = s->planewidth [3] = inlink->w; + s->planeheight[0] = s->planeheight[3] = inlink->h; + s->borders[0].left = s->borders[3].left = s->left; + s->borders[0].right = s->borders[3].right = s->right; + s->borders[0].top = s->borders[3].top = s->top; + s->borders[0].bottom = s->borders[3].bottom = s->bottom; + + s->planewidth [1] = s->planewidth [2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); + s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + s->borders[1].left = s->borders[2].left = s->left >> desc->log2_chroma_w; + s->borders[1].right = s->borders[2].right = s->right >> desc->log2_chroma_w; + s->borders[1].top = s->borders[2].top = s->top >> desc->log2_chroma_h; + s->borders[1].bottom = s->borders[2].bottom = s->bottom >> desc->log2_chroma_h; + + switch (s->report) { + case FM_FAILS: s->recoverlineshiftes = depth <= 8 ? report_shifts8 : report_shifts16; break; + case FM_SHIFTS: s->recoverlineshiftes = depth <= 8 ? smear_borders8 : smear_borders16; break; + case FM_VERBOSE: s->recoverlineshiftes = depth <= 8 ? mirror_borders8 : mirror_borders16; break; + case FM_FIXED: s->recoverlineshiftes = depth <= 8 ? fixed_borders8 : fixed_borders16; { + uint8_t filler[4]; + if (desc->flags & AV_PIX_FMT_FLAG_RGB) { + uint8_t rgba_map[4]; + ff_fill_rgba_map(rgba_map, inlink->format); + for (int i = 0; i < sizeof(rgba_map); i++) + filler[rgba_map[i]] = s->rgba_color[i]; + } else { + enum { Y, U, V, A }; + enum { R, G, B }; + filler[Y] = RGB_TO_Y_CCIR(s->rgba_color[R], s->rgba_color[G], s->rgba_color[B]); + filler[U] = RGB_TO_U_CCIR(s->rgba_color[R], s->rgba_color[G], s->rgba_color[B], 0); + filler[V] = RGB_TO_V_CCIR(s->rgba_color[R], s->rgba_color[G], s->rgba_color[B], 0); + filler[A] = s->rgba_color[A]; + } + for (int p = 0; p < s->nb_planes; p++) { + int fill_sz = FFMAX3(s->borders[p].left, s->borders[p].right, + s->top + s->bottom > 0 ? s->planewidth [p] : 0); + s->filler[p] = av_malloc(fill_sz * (depth <= 8 ? sizeof(uint8_t) : sizeof(uint16_t))); + if (depth <= 8) + memset((uint8_t *)s->filler[p], filler[p], fill_sz); + else + for (int i = fill_sz; i-- > 0; ) + ((uint16_t *)s->filler[p])[i] = filler[p] << (depth - 8); + } + } break; + } +*/ + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + LineShiftContext *s = ctx->priv; + + s->recoverlineshifts(inlink, frame); + + return ff_filter_frame(inlink->dst->outputs[0], frame); +} + +static int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, + AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, + AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, + AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12, + AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, + AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, + AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, + AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, + AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, + AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, + AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16, + AV_PIX_FMT_NONE + }; + AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); + if (!fmts_list) + return AVERROR(ENOMEM); + return ff_set_common_formats(ctx, fmts_list); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + LineShiftContext *s = ctx->priv; + av_log(ctx, s->report >= R_SHIFTS ? AV_LOG_WARNING : AV_LOG_INFO, "start freeing memory ...\n"); + for (int p = 0; p < s->nb_planes; p++) + av_freep(&s->filler[p]); + av_freep(&s->shifts); +/* + printf("printf(): filler and shifts freed!\n"); +*/ + av_log(ctx, s->report >= R_SHIFTS ? AV_LOG_WARNING : AV_LOG_INFO, "filler and shifts freed!\n"); + if (s->report >= R_SHIFTS) { +/* + printf("do printf() before statistic of found shifts!\n"); + av_log(ctx, AV_LOG_WARNING, "statistic of found shifts ...\n"); + av_log(NULL, AV_LOG_WARNING, "shift: "); // * increment); + for (int shift = 0; shift <= s->span_r - s->span_l; shift++) + av_log(NULL, AV_LOG_WARNING, "%6d", s->span_l + shift); + for (int l = 0; l <= s->lines; l++) { + av_log(NULL, AV_LOG_WARNING, "\nline %4d:", s->start + l); + for (int shift = 0; shift <= s->span_r - s->span_l; shift++) + av_log(NULL, AV_LOG_WARNING, "%6d", s->shifts_sums[l][shift]); + av_log(NULL, AV_LOG_WARNING, "\n"); + } +*/ +/* + printf("printf(): now start freeing shifts_sums ...\n"); +*/ + av_log(ctx, AV_LOG_WARNING, "now start freeing shifts_sums ...\n"); + for (int l = 0; l <= s->lines; l++) { + av_log(ctx, AV_LOG_WARNING, "freeing line %d ...\n", l); + if (s->shifts_sums[l]) + av_freep(&s->shifts_sums[l]); + } + av_log(ctx, AV_LOG_WARNING, "lines of shifts_sums freed!\n"); + av_freep(&s->shifts_sums); +/* + printf("printf(): shifts_sums freed!\n"); +*/ + av_log(ctx, AV_LOG_WARNING, "shifts_sums freed!\n"); + } +} + +static const AVFilterPad lineshiftrecover_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_props, + .filter_frame = filter_frame, + .needs_writable = 1, + }, + { NULL } +}; + +static const AVFilterPad lineshiftrecover_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { NULL } +}; + +AVFilter ff_vf_lineshiftrecover = { + .name = "lineshiftrecover", + .description = NULL_IF_CONFIG_SMALL("Recover video tape glitches."), + .priv_size = sizeof(LineShiftContext), + .priv_class = &lineshiftrecover_class, + .init = init, + .uninit = uninit, + .query_formats = query_formats, + .inputs = lineshiftrecover_inputs, + .outputs = lineshiftrecover_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, +}; diff --git a/libavutil/timestamp.h b/libavutil/timestamp.h index e082f01..97dc0a1 100644 --- a/libavutil/timestamp.h +++ b/libavutil/timestamp.h @@ -33,6 +33,17 @@ #define AV_TS_MAX_STRING_SIZE 32 /** + * Convert a time base scaled timestamp to micro seconds. + * + * @param ts the timestamp to convert, must be less than 2**63/1,000,000/tb->num + * @param tb the timebase of the timestamp + * @return the timestamp in micro seconds + */ +static inline int64_t av_ts2us(int64_t ts, AVRational *tb) { + return ts * 1000000 * tb->num / tb->den; +} + +/** * Fill the provided buffer with a string containing a timestamp * representation. * @@ -55,7 +66,7 @@ /** * Fill the provided buffer with a string containing a timestamp time - * representation. + * representation in seconds. * * @param buf a buffer with size in bytes of at least AV_TS_MAX_STRING_SIZE * @param ts the timestamp to represent @@ -75,4 +86,80 @@ */ #define av_ts2timestr(ts, tb) av_ts_make_time_string((char[AV_TS_MAX_STRING_SIZE]){0}, ts, tb) +/** + * Fill the provided buffer with a string containing a timestamp time + * representation in minutes and seconds. + * + * @param buf a buffer with size in bytes of at least AV_TS_MAX_STRING_SIZE + * @param ts the timestamp to represent, must be less than 2**63/1,000,000/tb->num + * @param tb the timebase of the timestamp + * @return the buffer in input + */ +static char *av_ts_make_minute_string(char *buf, int64_t ts, AVRational *tb) +{ + if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS"); + else { + int64_t us = av_ts2us(ts, tb); + int len = snprintf(buf, AV_TS_MAX_STRING_SIZE, "%3ld:%02d.%06d", + us / 60000000, (int)(us / 1000000 % 60), (int)(us % 1000000)); +// int len; +// double time = av_q2d(*tb) * ts; +// const char *format = (time >= 0 ? "%3d:%09.6f" : "-%3d:%09.6f"); +// time = (fabs(time) > INT_MAX * 60.0 ? INT_MAX * 60.0 : fabs(time)); +// len = snprintf(buf, AV_TS_MAX_STRING_SIZE, format, (int)(time / 60), fmod(time, 60)); +// if (len - 9 >= 0 && buf[len - 9] > '5') // correct rare rounding issue +// len = snprintf(buf, AV_TS_MAX_STRING_SIZE, format, (int)(time / 60) + 1, .0); + while (buf[--len] == '0'); // search trailing zeros or ... + buf[len + (buf[len] != '.')] = '\0'; // dot and strip them + } + return buf; +} + +/** + * Convenience macro. The return value should be used only directly in + * function arguments but never stand-alone. + */ +#define av_ts2minutestr(ts, tb) av_ts_make_minute_string((char[AV_TS_MAX_STRING_SIZE]){'\0'}, ts, tb) + +/** + * Fill the provided buffer with a string containing a timestamp time + * representation in hours, minutes and seconds. + * + * @param buf a buffer with size in bytes of at least AV_TS_MAX_STRING_SIZE + * @param ts the timestamp to represent, must be less than 2**63/1,000,000/tb->num + * @param tb the timebase of the timestamp + * @return the buffer in input + */ +static char *av_ts_make_hour_string(char *buf, int64_t ts, AVRational *tb) +{ + if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS"); + else { + int64_t us = av_ts2us(ts, tb); + int len = snprintf(buf, AV_TS_MAX_STRING_SIZE, "%ld:%02d:%02d.%06d", + us / 3600000000L, (int)(us / 60000000 % 60), (int)(us / 1000000 % 60), (int)(us % 1000000)); +// int len, hours, minutes; +// double time = av_q2d(*tb) * ts; +// const char *format = (time >= 0 ? "%d:%02d:%09.6f" : "-%d:%02d:%09.6f"); +// time = (fabs(time) > INT_MAX * 60.0 * 60.0 ? INT_MAX * 60.0 * 60.0 : fabs(time)); +// hours = time / 60 / 60; minutes = fmod(time / 60, 60); +// len = snprintf(buf, AV_TS_MAX_STRING_SIZE, format, hours, minutes, fmod(time, 60)); +// if (len - 9 >= 0 && buf[len - 9] > '5') { // correct rare rounding issue +// if (++minutes > 59) { +// minutes = 0; +// hours++; +// } +// len = snprintf(buf, AV_TS_MAX_STRING_SIZE, format, hours, minutes, .0); +// } + while (buf[--len] == '0'); // search trailing zeros or ... + buf[len + (buf[len] != '.')] = '\0'; // dot and strip them + } + return buf; +} + +/** + * Convenience macro. The return value should be used only directly in + * function arguments but never stand-alone. + */ +#define av_ts2hourstr(ts, tb) av_ts_make_hour_string((char[AV_TS_MAX_STRING_SIZE]){'\0'}, ts, tb) + #endif /* AVUTIL_TIMESTAMP_H */
_______________________________________________ ffmpeg-user mailing list [email protected] https://ffmpeg.org/mailman/listinfo/ffmpeg-user To unsubscribe, visit link above, or email [email protected] with subject "unsubscribe".
