From: Paul B Mahol <onemda@gmail.com> To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH] avfilter: add filmgrain filter Date: Sat, 19 Feb 2022 02:02:07 +0100 Message-ID: <20220219010207.153384-1-onemda@gmail.com> (raw) Signed-off-by: Paul B Mahol <onemda@gmail.com> --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_filmgrain.c | 489 +++++++++++++++++++++++++++++++++++++ 3 files changed, 491 insertions(+) create mode 100644 libavfilter/vf_filmgrain.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 56d33e6480..39d98881bb 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -283,6 +283,7 @@ OBJS-$(CONFIG_FIELDHINT_FILTER) += vf_fieldhint.o OBJS-$(CONFIG_FIELDMATCH_FILTER) += vf_fieldmatch.o OBJS-$(CONFIG_FIELDORDER_FILTER) += vf_fieldorder.o OBJS-$(CONFIG_FILLBORDERS_FILTER) += vf_fillborders.o +OBJS-$(CONFIG_FILMGRAIN_FILTER) += vf_filmgrain.o OBJS-$(CONFIG_FIND_RECT_FILTER) += vf_find_rect.o lavfutils.o OBJS-$(CONFIG_FLOODFILL_FILTER) += vf_floodfill.o OBJS-$(CONFIG_FORMAT_FILTER) += vf_format.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index f5caee3a62..20e889880f 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -266,6 +266,7 @@ extern const AVFilter ff_vf_fieldhint; extern const AVFilter ff_vf_fieldmatch; extern const AVFilter ff_vf_fieldorder; extern const AVFilter ff_vf_fillborders; +extern const AVFilter ff_vf_filmgrain; extern const AVFilter ff_vf_find_rect; extern const AVFilter ff_vf_flip_vulkan; extern const AVFilter ff_vf_floodfill; diff --git a/libavfilter/vf_filmgrain.c b/libavfilter/vf_filmgrain.c new file mode 100644 index 0000000000..c87118dad0 --- /dev/null +++ b/libavfilter/vf_filmgrain.c @@ -0,0 +1,489 @@ +/* + * 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 <float.h> + +#include "libavutil/opt.h" +#include "libavutil/imgutils.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/random_seed.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +#define STRETCH_3D (-1.f / 6.f) +#define SQUISH_3D (1.f / 3.f) +#define NORM_3D (1.f / 103.f) + +typedef struct Contribution3 { + float dx, dy, dz; + int xsb, ysb, zsb; + struct Contribution3 *next; +} Contribution3; + +typedef struct OpenSimplexNoise { + uint8_t perm[256]; + uint8_t perm3D[256]; + + Contribution3 *lookup3D[2048]; + Contribution3 *contributions3D[24]; +} OpenSimplexNoise; + +typedef struct ThreadData { + AVFrame *in, *out; + int plane; + float strength; +} ThreadData; + +typedef struct FilmGrainContext { + const AVClass *class; + + int depth; + int nb_planes; + int linesize[4]; + int planewidth[4]; + int planeheight[4]; + + float size; + float speed; + float strength; + float bias; + int planes; + + int64_t seed[4]; + + int (*grain_plane_slice)(AVFilterContext *ctx, ThreadData *td, int jobnr, int nb_jobs, int p, int max); + + OpenSimplexNoise osn[4]; +} FilmGrainContext; + +#define OFFSET(x) offsetof(FilmGrainContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM + +static const AVOption filmgrain_options[] = { + { "size", "set grain size", OFFSET(size), AV_OPT_TYPE_FLOAT, {.dbl=1600},20, 6400,FLAGS }, + { "strength", "set grain strength", OFFSET(strength), AV_OPT_TYPE_FLOAT, {.dbl=.25}, 0, 1, FLAGS }, + { "speed", "set grain change speed",OFFSET(speed), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 10, FLAGS }, + { "bias", "set grain bias", OFFSET(bias), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, FLAGS }, + { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=1}, 0, 0xF, FLAGS }, + { "0seed", "set random seed #0", OFFSET(seed[0]), AV_OPT_TYPE_INT64, {.i64=-1}, -1, UINT_MAX, FLAGS }, + { "1seed", "set random seed #1", OFFSET(seed[1]), AV_OPT_TYPE_INT64, {.i64=-1}, -1, UINT_MAX, FLAGS }, + { "2seed", "set random seed #2", OFFSET(seed[2]), AV_OPT_TYPE_INT64, {.i64=-1}, -1, UINT_MAX, FLAGS }, + { "3seed", "set random seed #3", OFFSET(seed[3]), AV_OPT_TYPE_INT64, {.i64=-1}, -1, UINT_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(filmgrain); + +static const enum AVPixelFormat pixel_fmts[] = { + 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_GRAYF32, + AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P, + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, + AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P, + AV_PIX_FMT_YUVJ411P, + 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_YUV440P10, + AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV420P12, + AV_PIX_FMT_YUV440P12, + AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14, + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, + 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_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P, + AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12, AV_PIX_FMT_YUVA444P16, + AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA422P16, + AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16, + AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, + AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32, + AV_PIX_FMT_NONE +}; + +static void init_noise(OpenSimplexNoise *n, int64_t seed) +{ + int8_t source[256]; + + for (int i = 0; i < 256; i++) + source[i] = i; + + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + + for (int i = 255; i >= 0; i--) { + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + int r = (int)((seed + 31) % (i + 1)); + if (r < 0) + r += (i + 1); + n->perm[i] = source[r]; + n->perm3D[i] = (uint8_t)((n->perm[i] % 24) * 3); + source[r] = source[i]; + } +} + +static const uint8_t base3D[][24] = +{ + { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1 }, + { 2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1, 3, 1, 1, 1 }, + { 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1 } +}; + +static const int8_t p3D[] = +{ + 0, 0, 1, -1, 0, 0, 1, 0, -1, 0, 0, -1, 1, 0, 0, 0, 1, + -1, 0, 0, -1, 0, 1, 0, 0, -1, 1, 0, 2, 1, 1, 0, 1, 1, + 1, -1, 0, 2, 1, 0, 1, 1, 1, -1, 1, 0, 2, 0, 1, 1, 1, + -1, 1, 1, 1, 3, 2, 1, 0, 3, 1, 2, 0, 1, 3, 2, 0, 1, 3, + 1, 0, 2, 1, 3, 0, 2, 1, 3, 0, 1, 2, 1, 1, 1, 0, 0, 2, + 2, 0, 0, 1, 1, 0, 1, 0, 2, 0, 2, 0, 1, 1, 0, 0, 1, 2, + 0, 0, 2, 2, 0, 0, 0, 0, 1, 1, -1, 1, 2, 0, 0, 0, 0, 1, + -1, 1, 1, 2, 0, 0, 0, 0, 1, 1, 1, -1, 2, 3, 1, 1, 1, 2, + 0, 0, 2, 2, 3, 1, 1, 1, 2, 2, 0, 0, 2, 3, 1, 1, 1, 2, 0, + 2, 0, 2, 1, 1, -1, 1, 2, 0, 0, 2, 2, 1, 1, -1, 1, 2, 2, + 0, 0, 2, 1, -1, 1, 1, 2, 0, 0, 2, 2, 1, -1, 1, 1, 2, 0, + 2, 0, 2, 1, 1, 1, -1, 2, 2, 0, 0, 2, 1, 1, 1, -1, 2, 0, 2, 0, +}; + +static const uint16_t lookupPairs3D[] = +{ + 0, 2, 1, 1, 2, 2, 5, 1, 6, 0, 7, 0, 32, 2, 34, 2, 129, 1, 133, 1, + 160, 5, 161, 5, 518, 0, 519, 0, 546, 4, 550, 4, 645, 3, 647, 3, + 672, 5, 673, 5, 674, 4, 677, 3, 678, 4, 679, 3, 680, 13, 681, 13, + 682, 12, 685, 14, 686, 12, 687, 14, 712, 20, 714, 18, 809, 21, 813, + 23, 840, 20, 841, 21, 1198, 19, 1199, 22, 1226, 18, 1230, 19, 1325, + 23, 1327, 22, 1352, 15, 1353, 17, 1354, 15, 1357, 17, 1358, 16, 1359, + 16, 1360, 11, 1361, 10, 1362, 11, 1365, 10, 1366, 9, 1367, 9, 1392, + 11, 1394, 11, 1489, 10, 1493, 10, 1520, 8, 1521, 8, 1878, 9, 1879, 9, + 1906, 7, 1910, 7, 2005, 6, 2007, 6, 2032, 8, 2033, 8, 2034, 7, 2037, + 6, 2038, 7, 2039, 6, +}; + +/* + * Gradients for 3D. They approximate the directions to the + * vertices of a rhombicuboctahedron from the center, skewed so + * that the triangular and square facets can be inscribed inside + * circles of the same radius. + */ +static const int8_t gradients3D[] = { + -11, 4, 4, -4, 11, 4, 4, 4, 11, + 11, 4, 4, 4, 11, 4, 4, 4, 11, + -11, -4, 4, -4, -11, 4, 4, -4, 11, + 11, -4, 4, 4, -11, 4, 4, -4, 11, + -11, 4, -4, -4, 11, -4, 4, 4, -11, + 11, 4, -4, 4, 11, -4, 4, 4, -11, + -11, -4, -4, -4, -11, -4, 4, -4, -11, + 11, -4, -4, 4, -11, -4, 4, -4, -11, +}; + +static void EContribution3(Contribution3 *c, float multiplier, int xsb, int ysb, int zsb) +{ + c->xsb = xsb; + c->ysb = ysb; + c->zsb = zsb; + + c->dx = -xsb - multiplier * SQUISH_3D; + c->dy = -ysb - multiplier * SQUISH_3D; + c->dz = -zsb - multiplier * SQUISH_3D; +} + +static float evaluate(const OpenSimplexNoise *const n, float x, float y, float z) +{ + float stretchOffset = (x + y + z) * STRETCH_3D; + float xs = x + stretchOffset; + float ys = y + stretchOffset; + float zs = z + stretchOffset; + + int xsb = floorf(xs); + int ysb = floorf(ys); + int zsb = floorf(zs); + + float squishOffset = (xsb + ysb + zsb) * SQUISH_3D; + float dx0 = x - (xsb + squishOffset); + float dy0 = y - (ysb + squishOffset); + float dz0 = z - (zsb + squishOffset); + + float xins = xs - xsb; + float yins = ys - ysb; + float zins = zs - zsb; + + float inSum = xins + yins + zins; + + int hash = + (int)(yins - zins + 1) | + (int)(xins - yins + 1) << 1 | + (int)(xins - zins + 1) << 2 | + (int)(inSum) << 3 | + (int)(inSum + zins) << 5 | + (int)(inSum + yins) << 7 | + (int)(inSum + xins) << 9; + + Contribution3 *c = n->lookup3D[hash]; + + float value = 0.0; + + while (c != NULL) { + float dx = dx0 + c->dx; + float dy = dy0 + c->dy; + float dz = dz0 + c->dz; + float attn = 2.f - dx * dx - dy * dy - dz * dz; + + if (attn > 0.f) { + int px = xsb + c->xsb; + int py = ysb + c->ysb; + int pz = zsb + c->zsb; + + int i = n->perm3D[(n->perm[(n->perm[px & 0xFF] + py) & 0xFF] + pz) & 0xFF]; + float valuePart = gradients3D[i] * dx + gradients3D[i + 1] * dy + gradients3D[i + 2] * dz; + + attn *= attn; + value += attn * attn * valuePart; + } + + c = c->next; + } + + return value * NORM_3D; +} + +#define GRAIN_SLICE(name, type, round, clip, scale) \ +static int grain##name##_plane_slice(AVFilterContext *ctx, ThreadData *td, \ + int jobnr, int nb_jobs, int p, int max) \ +{ \ + FilmGrainContext *s = ctx->priv; \ + AVFilterLink *inlink = ctx->inputs[0]; \ + AVFrame *in = td->in; \ + AVFrame *out = td->out; \ + const int width = s->planewidth[p]; \ + const int height = s->planeheight[p]; \ + const float xsize = s->size / width; \ + const float ysize = s->size / height; \ + const int slice_start = (height * jobnr ) / nb_jobs; \ + const int slice_end = (height * (jobnr+1)) / nb_jobs; \ + const type *src = (type *)(in->data[p] + slice_start * in->linesize[p]);\ + type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \ + const float strength = s->strength * 0.3f * scale; \ + const float bias = s->bias; \ + const float z = inlink->frame_count_out * s->speed; \ + OpenSimplexNoise *n = &s->osn[p]; \ + \ + for (int y = slice_start; y < slice_end; y++) { \ + if (!((1 << p) & s->planes)) { \ + if (in != out) \ + av_image_copy_plane((uint8_t *)dst, out->linesize[p], \ + (const uint8_t *)src, \ + in->linesize[p], s->linesize[p], \ + slice_end - slice_start); \ + continue; \ + } \ + \ + for (int x = 0; x < s->planewidth[p]; x++) { \ + float noise = evaluate(n, x * xsize, y * ysize, z); \ + \ + dst[x] = clip(round(src[x] + strength * (noise + bias)), 0, max);\ + } \ + \ + dst += out->linesize[p] / sizeof(type); \ + src += in->linesize[p] / sizeof(type); \ + } \ + \ + return 0; \ +} + +#define CLIP8(x, min, max) av_clip_uint8(x) +#define CLIP16(x, min, max) av_clip(x, min, max) +#define CLIPF(x, min, max) (x) +#define NOP(x) (x) + +GRAIN_SLICE(8, uint8_t, lrintf, CLIP8, max) +GRAIN_SLICE(16, uint16_t, lrintf, CLIP16, max) +GRAIN_SLICE(32, float, NOP, CLIPF, 1.f) + +static int grain_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + FilmGrainContext *s = ctx->priv; + const int max = (1 << s->depth) - 1; + ThreadData *td = arg; + + for (int p = 0; p < s->nb_planes; p++) + s->grain_plane_slice(ctx, td, jobnr, nb_jobs, p, max); + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + FilmGrainContext *s = ctx->priv; + ThreadData td; + AVFrame *out; + + if (av_frame_is_writable(in)) { + out = in; + } else { + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + + td.in = in, td.out = out; + ff_filter_execute(ctx, grain_slice, &td, NULL, + FFMIN(s->planeheight[1], ff_filter_get_nb_threads(ctx))); + + if (in != out) + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +static int noise_lookup(OpenSimplexNoise *n) +{ + for (int i = 0; i < 216; i += 9) { + const uint8_t *baseSet = base3D[p3D[i]]; + const int baseSetSize = p3D[i] == 2 ? 24 : 16; + Contribution3 *previous = NULL, *current = NULL; + + for (int k = 0; k < baseSetSize; k += 4) { + current = av_calloc(1, sizeof(Contribution3)); + if (!current) + return AVERROR(ENOMEM); + + EContribution3(current, baseSet[k], baseSet[k + 1], + baseSet[k + 2], baseSet[k + 3]); + if (previous == NULL) { + n->contributions3D[i / 9] = current; + } else { + previous->next = current; + } + + previous = current; + } + + current->next = av_calloc(1, sizeof(Contribution3)); + if (!current->next) + return AVERROR(ENOMEM); + EContribution3(current->next, p3D[i + 1], p3D[i + 2], p3D[i + 3], p3D[i + 4]); + current->next->next = av_calloc(1, sizeof(Contribution3)); + if (!current->next->next) + return AVERROR(ENOMEM); + EContribution3(current->next->next, p3D[i + 5], p3D[i + 6], p3D[i + 7], p3D[i + 8]); + } + + for (int i = 0; i < FF_ARRAY_ELEMS(lookupPairs3D); i += 2) + n->lookup3D[lookupPairs3D[i]] = n->contributions3D[lookupPairs3D[i + 1]]; + + return 0; +} + +static av_cold int init(AVFilterContext *ctx) +{ + FilmGrainContext *s = ctx->priv; + + for (int p = 0; p < 4; p++) { + if (s->seed[p] == -1) + s->seed[p] = av_get_random_seed(); + init_noise(&s->osn[p], s->seed[p]); + noise_lookup(&s->osn[p]); + } + + return 0; +} + +static int config_input(AVFilterLink *inlink) +{ + FilmGrainContext *s = inlink->dst->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + int ret; + + s->depth = desc->comp[0].depth; + s->nb_planes = av_pix_fmt_count_planes(inlink->format); + + if (s->depth <= 8) + s->grain_plane_slice = grain8_plane_slice; + else if (s->depth <= 16) + s->grain_plane_slice = grain16_plane_slice; + else + s->grain_plane_slice = grain32_plane_slice; + + if ((ret = av_image_fill_linesizes(s->linesize, inlink->format, inlink->w)) < 0) + return ret; + + s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); + s->planewidth[0] = s->planewidth[3] = inlink->w; + s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + s->planeheight[0] = s->planeheight[3] = inlink->h; + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + FilmGrainContext *s = ctx->priv; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 24; j++) { + struct Contribution3 *contr = s->osn[i].contributions3D[j]; + + while (contr != NULL) { + struct Contribution3 *temp = contr; + + contr = contr->next; + av_freep(&temp); + } + } + } +} + +static const AVFilterPad filmgrain_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_input, + }, +}; + +static const AVFilterPad filmgrain_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, +}; + +AVFilter ff_vf_filmgrain = { + .name = "filmgrain", + .description = NULL_IF_CONFIG_SMALL("Add film grain."), + .priv_size = sizeof(FilmGrainContext), + .init = init, + .uninit = uninit, + FILTER_INPUTS(filmgrain_inputs), + FILTER_OUTPUTS(filmgrain_outputs), + FILTER_PIXFMTS_ARRAY(pixel_fmts), + .priv_class = &filmgrain_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, + .process_command = ff_filter_process_command, +}; -- 2.33.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
reply other threads:[~2022-02-19 1:00 UTC|newest] Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20220219010207.153384-1-onemda@gmail.com \ --to=onemda@gmail.com \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git