From patchwork Fri Mar 8 13:21:06 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Niklas Haas X-Patchwork-Id: 46903 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c995:b0:1a1:738b:6bc0 with SMTP id gy21csp932733pzb; Fri, 8 Mar 2024 05:21:48 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCV166IauJi3ZN9ZGGzcE4Ryr+mLkKgsBvoHwWoDOJ6rplCtDeBlQwnVrKlNGKKhdO7Iask+tNYyXbZ7crjsJGw+jdu/1eCQMwg9GA== X-Google-Smtp-Source: AGHT+IGG8aTRVTFXv53xf4TffH/ZkZ2Ec17CfGyFVo5sQUqPVM58cf2f1MF7i8MpYwNK+crfCnei X-Received: by 2002:a50:9b58:0:b0:566:f81:41a1 with SMTP id a24-20020a509b58000000b005660f8141a1mr1768487edj.22.1709904108174; Fri, 08 Mar 2024 05:21:48 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1709904108; cv=none; d=google.com; s=arc-20160816; b=0w0TdNSJ44Xu+fbP6AfB2yyUJLzcFOIKz1dmiL4QR9Q+5YCq76B1WhkYXBjbXgnRQX utxKfBP9Bl/9MsS+0iQZWtesEas7oDC0yJ3kcma8a7iD0QocsOjDXRsEfKk167GDXYMO RF5xd0KLEVAOXAFpqSbLMp62eiiQf9gFD7q/ljfZ1qgnp3WAWwnsGEVKc9270VvcHdzO TGCnqgzB26JXan3RbEZjc3DkOf1jA8Iy7HYA5prgYpczltfBUR0YuH4crSglB2tutcpV gZ1hlzMR5bUR3YL3Cy99RsU9+ayeiDp5bTph5opiMdmZ++j0cQq3hx56sHHrrXK3t5NQ YmhA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=povvR8h4WadOH+WFi9PhAFlL5dT2VVMSfQjgK09jhFY=; fh=xmAeKtysnShNOmkhiJmYkS30uw4Fu2hvBJ7qlIwukxQ=; b=xz1TJMMv0Gw26zUSAtGt3JJE9sapp0d8BJfmZpewylnfTaPDSnmzP58+cXNwsPojDN T43TUc3J6qMYMeREEeNaCoQM2ixQ8APDu3rBm5f/zvrk51syuo04I1Owzl5vF5hO+ES2 OuVLvvw2RhIr9bgEeck8VQD7C/egayXfaV80nlQotzWsn7khpbA4vmYp07L/B1vL5/YV v3b0LMlZxcnAfzIO7s5wqCo81Lqep15JvNtfoWbS7eHPlDThTeEz2sTTCZfjHwXPbwGb CHonG+YitvwKSoKUvreBIfgrU0FVunypkjqExFnm3vx1dAVhEnfKFGqOyygime7sKTLq Cmvw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@haasn.xyz header.s=mail header.b=UkuZXTQk; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id f12-20020a50d54c000000b00567f48be643si2180569edj.260.2024.03.08.05.21.47; Fri, 08 Mar 2024 05:21:48 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@haasn.xyz header.s=mail header.b=UkuZXTQk; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 3620968CF50; Fri, 8 Mar 2024 15:21:45 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from haasn.dev (haasn.dev [78.46.187.166]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id AB2AE68BA19 for ; Fri, 8 Mar 2024 15:21:38 +0200 (EET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=haasn.xyz; s=mail; t=1709904098; bh=MVTHRk2BmAvYFGa1UqjEGRxt7HDhzVMUC3Mzi9WG/z0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UkuZXTQkpLs9usZWfXZjq9C4R2TsrbQAoMvqJWFakkpAJ2oP0TmRUkPUYc0l+0XWc MLTGIM0ZShCseGUxzBa07+n+1tSGBAWY8nSahA2yFw62UxIjzLLjWUZS3sySr7An9g qJjGWSUKor6NfW2/3KKOhXdNAcydJnhoCp1VWflM= Received: from haasn.dev (unknown [10.30.0.2]) by haasn.dev (Postfix) with ESMTP id 740074197E; Fri, 8 Mar 2024 14:21:38 +0100 (CET) From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Fri, 8 Mar 2024 14:21:06 +0100 Message-ID: <20240308132108.28337-3-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240308132108.28337-1-ffmpeg@haasn.xyz> References: <20240308132108.28337-1-ffmpeg@haasn.xyz> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2 2/4] avcodec/aom_film_grain: implement AFGS1 X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Niklas Haas Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: +4AJDLpTcZff From: Niklas Haas Changes from v1: - fixed interpretation of num_sets_minus1 (missing +1) --- Based on the AOMedia Film Grain Synthesis 1 (AFGS1) spec: https://aomediacodec.github.io/afgs1-spec/ The parsing has been changed substantially relative to the AV1 film grain OBU. In particular: 1. There is the possibility of maintaining multiple independent film grain parameter sets, and decoders are recommended to pick the one most appropriate for the intended display resolution. This is to support scalable / multi-level codecs, although this could also be used to e.g. switch between different grain profiles without having to re-signal the appropriate coefficients. 2. Supporting this, it's possible to *predict* the grain coefficients from previously signalled parameter sets, transmitting only the residual. 3. When not predicting, the parameter sets are now stored as a series of increments, rather than being directly transmitted. I placed this parser in its own file, rather than h2645_sei.c, since nothing in the generic AFGS1 film grain payload is specific to T.35. --- libavcodec/aom_film_grain.c | 227 ++++++++++++++++++++++++++++++++++++ libavcodec/aom_film_grain.h | 25 ++++ 2 files changed, 252 insertions(+) diff --git a/libavcodec/aom_film_grain.c b/libavcodec/aom_film_grain.c index ffcd71b584b..edea2758d3c 100644 --- a/libavcodec/aom_film_grain.c +++ b/libavcodec/aom_film_grain.c @@ -29,6 +29,7 @@ #include "libavutil/imgutils.h" #include "aom_film_grain.h" +#include "get_bits.h" // Common/shared helpers (not dependent on BIT_DEPTH) static inline int get_random_number(const int bits, unsigned *const state) { @@ -118,6 +119,232 @@ int ff_aom_apply_film_grain(AVFrame *out, const AVFrame *in, return AVERROR_INVALIDDATA; } +int ff_aom_parse_film_grain_sets(AVFilmGrainAOMParamSets *s, + const uint8_t *payload, int payload_size) +{ + GetBitContext gbc, *gb = &gbc; + AVFilmGrainAOMParams *aom; + AVFilmGrainAOMParamSet *fgps, *ref = NULL; + int ret, num_sets, n, i, uv, num_y_coeffs, update_grain, luma_only; + + ret = init_get_bits8(gb, payload, payload_size); + if (ret < 0) + return ret; + + s->enable = get_bits1(gb); + if (!s->enable) + return 0; + + skip_bits(gb, 4); // reserved + num_sets = get_bits(gb, 3) + 1; + for (n = 0; n < num_sets; n++) { + int payload_4byte, payload_size, set_idx, apply_units_log2, vsc_flag; + int predict_scaling, predict_y_scaling, predict_uv_scaling[2]; + int payload_bits, start_position; + + start_position = get_bits_count(gb); + payload_4byte = get_bits1(gb); + payload_size = get_bits(gb, payload_4byte ? 2 : 8); + set_idx = get_bits(gb, 3); + fgps = &s->sets[set_idx]; + + fgps->apply_grain = get_bits1(gb); + if (!fgps->apply_grain) + continue; + + fgps->grain_seed = get_bits(gb, 16); + update_grain = get_bits1(gb); + if (!update_grain) + continue; + + apply_units_log2 = get_bits(gb, 4); + fgps->apply_width = get_bits(gb, 12) << apply_units_log2; + fgps->apply_height = get_bits(gb, 12) << apply_units_log2; + luma_only = get_bits1(gb); + if (luma_only) { + fgps->subx = fgps->suby = 0; + } else { + fgps->subx = get_bits1(gb); + fgps->suby = get_bits1(gb); + } + + vsc_flag = get_bits1(gb); // video_signal_characteristics_flag + if (vsc_flag) { + int cicp_flag; + skip_bits(gb, 3); // bit_depth_minus8 + cicp_flag = get_bits1(gb); + if (cicp_flag) + skip_bits(gb, 8 + 8 + 8 + 1); // cicp_info + } + + aom = &fgps->params; + predict_scaling = get_bits1(gb); + if (predict_scaling && (!ref || ref == fgps)) + goto error; // prediction must be from valid, different set + + predict_y_scaling = predict_scaling ? get_bits1(gb) : 0; + if (predict_y_scaling) { + int y_scale, y_offset, bits_res; + y_scale = get_bits(gb, 9) - 256; + y_offset = get_bits(gb, 9) - 256; + bits_res = get_bits(gb, 3); + if (bits_res) { + int res[14], pred, granularity; + aom->num_y_points = ref->params.num_y_points; + for (i = 0; i < aom->num_y_points; i++) + res[i] = get_bits(gb, bits_res); + granularity = get_bits(gb, 3); + for (i = 0; i < aom->num_y_points; i++) { + pred = ref->params.y_points[i][1]; + pred = ((pred * y_scale + 8) >> 4) + y_offset; + pred += (res[i] - (1 << (bits_res - 1))) * granularity; + aom->y_points[i][0] = ref->params.y_points[i][0]; + aom->y_points[i][1] = av_clip_uint8(pred); + } + } + } else { + aom->num_y_points = get_bits(gb, 4); + if (aom->num_y_points > 14) { + goto error; + } else if (aom->num_y_points) { + int bits_inc, bits_scaling; + int y_value = 0; + bits_inc = get_bits(gb, 3) + 1; + bits_scaling = get_bits(gb, 2) + 5; + for (i = 0; i < aom->num_y_points; i++) { + y_value += get_bits(gb, bits_inc); + if (y_value > UINT8_MAX) + goto error; + aom->y_points[i][0] = y_value; + aom->y_points[i][1] = get_bits(gb, bits_scaling); + } + } + } + + if (luma_only) { + aom->chroma_scaling_from_luma = 0; + aom->num_uv_points[0] = aom->num_uv_points[1] = 0; + } else { + aom->chroma_scaling_from_luma = get_bits1(gb); + if (aom->chroma_scaling_from_luma) { + aom->num_uv_points[0] = aom->num_uv_points[1] = 0; + } else { + for (uv = 0; uv < 2; uv++) { + predict_uv_scaling[uv] = predict_scaling ? get_bits1(gb) : 0; + if (predict_uv_scaling[uv]) { + int uv_scale, uv_offset, bits_res; + uv_scale = get_bits(gb, 9) - 256; + uv_offset = get_bits(gb, 9) - 256; + bits_res = get_bits(gb, 3); + aom->uv_mult[uv] = ref->params.uv_mult[uv]; + aom->uv_mult_luma[uv] = ref->params.uv_mult_luma[uv]; + aom->uv_offset[uv] = ref->params.uv_offset[uv]; + if (bits_res) { + int res[10], pred, granularity; + aom->num_uv_points[uv] = ref->params.num_uv_points[uv]; + for (i = 0; i < aom->num_uv_points[uv]; i++) + res[i] = get_bits(gb, bits_res); + granularity = get_bits(gb, 3); + for (i = 0; i < aom->num_uv_points[uv]; i++) { + pred = ref->params.uv_points[uv][i][1]; + pred = ((pred * uv_scale + 8) >> 4) + uv_offset; + pred += (res[i] - (1 << (bits_res - 1))) * granularity; + aom->uv_points[uv][i][0] = ref->params.uv_points[uv][i][0]; + aom->uv_points[uv][i][1] = av_clip_uint8(pred); + } + } + } else { + int bits_inc, bits_scaling, uv_offset; + int uv_value = 0; + aom->num_uv_points[uv] = get_bits(gb, 4); + if (aom->num_uv_points[uv] > 10) + goto error; + bits_inc = get_bits(gb, 3) + 1; + bits_scaling = get_bits(gb, 2) + 5; + uv_offset = get_bits(gb, 8); + for (i = 0; i < aom->num_uv_points[uv]; i++) { + uv_value += get_bits(gb, bits_inc); + if (uv_value > UINT8_MAX) + goto error; + aom->uv_points[uv][i][0] = uv_value; + aom->uv_points[uv][i][1] = get_bits(gb, bits_scaling) + uv_offset; + } + } + } + } + } + + aom->scaling_shift = get_bits(gb, 2) + 8; + aom->ar_coeff_lag = get_bits(gb, 2); + num_y_coeffs = 2 * aom->ar_coeff_lag * (aom->ar_coeff_lag + 1); + if (aom->num_y_points) { + int ar_bits = get_bits(gb, 2) + 5; + for (i = 0; i < num_y_coeffs; i++) + aom->ar_coeffs_y[i] = get_bits(gb, ar_bits) - (1 << (ar_bits - 1)); + } + for (uv = 0; uv < 2; uv++) { + if (aom->chroma_scaling_from_luma || aom->num_uv_points[uv]) { + int ar_bits = get_bits(gb, 2) + 5; + for (i = 0; i < num_y_coeffs + !!aom->num_y_points; i++) + aom->ar_coeffs_uv[uv][i] = get_bits(gb, ar_bits) - (1 << (ar_bits - 1)); + } + } + aom->ar_coeff_shift = get_bits(gb, 2) + 6; + aom->grain_scale_shift = get_bits(gb, 2); + for (uv = 0; uv < 2; uv++) { + if (aom->num_uv_points[uv] && !predict_uv_scaling[uv]) { + aom->uv_mult[uv] = get_bits(gb, 8) - 128; + aom->uv_mult_luma[uv] = get_bits(gb, 8) - 128; + aom->uv_offset[uv] = get_bits(gb, 9) - 256; + } + } + aom->overlap_flag = get_bits1(gb); + aom->limit_output_range = get_bits1(gb); + + // use first set as reference only if it was fully transmitted + if (n == 0) + ref = fgps; + + payload_bits = get_bits_count(gb) - start_position; + if (payload_bits > payload_size * 8) + goto error; + skip_bits(gb, payload_size * 8 - payload_bits); + } + return 0; + +error: + s->enable = 0; + return AVERROR_INVALIDDATA; +} + +const AVFilmGrainAOMParamSet *ff_aom_select_film_grain_set(const AVFilmGrainAOMParamSets *s, + const AVFrame *frame) +{ + const AVFilmGrainAOMParamSet *fgps, *best; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + if (!s->enable || !desc) + return NULL; + + best = NULL; + for (int i = 0; i < 8; i++) { + fgps = &s->sets[i]; + if (!fgps->apply_width || + !fgps->apply_height || + fgps->apply_width > frame->width || + fgps->apply_height > frame->height || + fgps->subx != desc->log2_chroma_w || + fgps->suby != desc->log2_chroma_h) + continue; + + if (!best || + fgps->apply_width > best->apply_width || + fgps->apply_height > best->apply_height) + best = fgps; + } + + return best; +} + // Taken from the AV1 spec. Range is [-2048, 2047], mean is 0 and stddev is 512 static const int16_t gaussian_sequence[2048] = { 56, 568, -180, 172, 124, -84, 172, -64, -900, 24, 820, diff --git a/libavcodec/aom_film_grain.h b/libavcodec/aom_film_grain.h index 5d772bd7d17..b985451dbc3 100644 --- a/libavcodec/aom_film_grain.h +++ b/libavcodec/aom_film_grain.h @@ -30,9 +30,34 @@ #include "libavutil/film_grain_params.h" +// Stand-alone AFGS1 metadata parameter set +typedef struct AVFilmGrainAOMParamSet { + int apply_grain; + int apply_width; + int apply_height; + int subx, suby; + uint16_t grain_seed; + AVFilmGrainAOMParams params; +} AVFilmGrainAOMParamSet; + +typedef struct AVFilmGrainAOMParamSets { + int enable; + AVFilmGrainAOMParamSet sets[8]; +} AVFilmGrainAOMParamSets; + // Synthesizes film grain on top of `in` and stores the result to `out`. `out` // must already have been allocated and set to the same size and format as `in`. int ff_aom_apply_film_grain(AVFrame *out, const AVFrame *in, const AVFilmGrainParams *params); +// Parse AFGS1 parameter sets from an ITU-T T.35 payload. Returns 0 on success, +// or a negative error code. +int ff_aom_parse_film_grain_sets(AVFilmGrainAOMParamSets *s, + const uint8_t *payload, int payload_size); + +// Select the most appropriate film grain parameter set for a given +// frame. Returns the parameter set, or NULL if none was selected. +const AVFilmGrainAOMParamSet *ff_aom_select_film_grain_set(const AVFilmGrainAOMParamSets *s, + const AVFrame *frame); + #endif /* AVCODEC_AOM_FILM_GRAIN_H */