Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TV range color scaling (again) #479

Merged
merged 7 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions debian/patches/0004-add-cuda-tonemap-impl.patch
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ Index: FFmpeg/libavfilter/colorspace.h
===================================================================
--- FFmpeg.orig/libavfilter/colorspace.h
+++ FFmpeg/libavfilter/colorspace.h
@@ -23,10 +23,42 @@
@@ -23,10 +23,69 @@
#include "libavutil/csp.h"
#include "libavutil/frame.h"
#include "libavutil/pixfmt.h"
Expand All @@ -276,6 +276,33 @@ Index: FFmpeg/libavfilter/colorspace.h
+#define ARIB_B67_C 0.55991073f
+#define FLOAT_EPS 1e-6f
+
+/*
+ * Pre-calculated constants used for YCbCr narrow to full range scaling
+ * The base formula is the quantization formula derived from BT.2100 Table 9:
+ * Where Y' = Round [(219 * E′ + 16) * 2^(n−8)],
+ * Cb',Cr' = Round [(224 * E′ + 128) * 2^(n−8)]
+ * where E' is the signal value in [0,1] range and n is the bit depth. Round is rounding towards 0.
+ * For inputs, the inverse is used where we are solving for E' for a given Y'Cb'Cr' normalized by GPU
+ * in [0,1] range. The GPU will interpret color as a 16bit int value, and solving for E' becomes:
+ * E' = (Y' - 2^(n-4)) / (219 * 2^(n-8))
+ * E' = (Cb'Cr' - 2^(n-1)) / (7 * 2^(n-3))
+ * Y' and Cb'Cr' is in the range of [0, 2^n - 1] in original formula, we need to scale the value normalized to [0,1]:
+ * C = Y'Cb'Cr' * (2^n - 1)
+ * Which means the input scale = (2^n - 1) / (219 * 2^(n-8)) and input offset = 2^(n-4)) / (219 * 2^(n-8)) for Y' and
+ * 2^(n-1)) / (7 * 2^(n-3)) for Cb'Cr'
+ */
+#define INPUT_Y_SCALE_10B 1.1678082192f
+#define INPUT_UV_SCALE_10B 1.1417410714f
+#define INPUT_Y_SCALE_12B 1.1689497717f
+#define INPUT_UV_SCALE_12B 1.1428571429f
+
+/*
+ * GPU will interpret 10bit and 12bit color as 16bit int
+ * but that will introduce a slight (2^(16-n))/2^16 quantization offset which we want to compensate for
+*/
+#define QUANTIZATION_OFFSET_10B 0.0009765774014f
+#define QUANTIZATION_OFFSET_12B 0.0002441443503f
+
nyanmisaka marked this conversation as resolved.
Show resolved Hide resolved
+// Parsed metadata from the Dolby Vision RPU
+struct DoviMetadata {
+ float nonlinear_offset[3]; // input offset ("ycc_to_rgb_offset")
Expand All @@ -300,7 +327,7 @@ Index: FFmpeg/libavfilter/colorspace.h
void ff_matrix_mul_3x3(double dst[3][3],
const double src1[3][3], const double src2[3][3]);
void ff_matrix_mul_3x3_vec(double dst[3], const double vec[3], const double mat[3][3]);
@@ -38,4 +70,19 @@ void ff_fill_rgb2yuv_table(const AVLumaC
@@ -38,4 +97,19 @@ void ff_fill_rgb2yuv_table(const AVLumaC
double ff_determine_signal_peak(AVFrame *in);
void ff_update_hdr_metadata(AVFrame *in, double peak);

Expand Down Expand Up @@ -1785,7 +1812,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_cuda.c
===================================================================
--- /dev/null
+++ FFmpeg/libavfilter/vf_tonemap_cuda.c
@@ -0,0 +1,1162 @@
@@ -0,0 +1,1165 @@
+/*
+ * This file is part of FFmpeg.
+ *
Expand Down Expand Up @@ -2413,22 +2440,25 @@ Index: FFmpeg/libavfilter/vf_tonemap_cuda.c
+ return ret;
+
+ if (s->in_desc->comp[0].depth == 10) {
+ input_quantization_offset = 0.0009765774014f;
+ input_y_scale = 1.1678082192f;
+ input_uv_scale = 1.1417410714f;
+ input_quantization_offset = QUANTIZATION_OFFSET_10B;
+ input_y_scale = INPUT_Y_SCALE_10B;
+ input_uv_scale = INPUT_UV_SCALE_10B;
+ } else if (s->in_desc->comp[0].depth == 16) {
+ input_quantization_offset = 0.0002441443503f;
+ input_y_scale = 1.1689497717f;
+ input_uv_scale = 1.1428571429f;
+ // Assume 16bit is actually 12bit for now as that is what the hardware decoders producing
+ // and what videos are actually encoded in
+ input_quantization_offset = QUANTIZATION_OFFSET_12B;
+ input_y_scale = INPUT_Y_SCALE_12B;
+ input_uv_scale = INPUT_UV_SCALE_12B;
+ }
+
+ if (s->out_desc->comp[0].depth == 10) {
+ output_quantization_offset = 0.0009765774014f;
+ // Don't handle 12b offset for now and assume 16b output is real 16b out to make it consistent with other filters
+ output_quantization_offset = QUANTIZATION_OFFSET_10B;
+ }
+
+ if (s->out_desc->comp[0].depth > 8) {
+ output_quantization_factor = 256.0f;
+ output_quantization_scale = 65535.0f;
+ output_quantization_factor = 256.0f; // 2^(16-8)
+ output_quantization_scale = 65535.0f; // 2^16 - 1
+ }
gnattu marked this conversation as resolved.
Show resolved Hide resolved
+
+ av_bprint_init(&constants, 2048, AV_BPRINT_SIZE_UNLIMITED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c

switch(ctx->tonemap) {
case TONEMAP_GAMMA:
@@ -139,59 +353,207 @@ static int tonemap_opencl_init(AVFilterC
@@ -139,59 +353,210 @@ static int tonemap_opencl_init(AVFilterC
if (isnan(ctx->param))
ctx->param = 0.3f;
break;
Expand Down Expand Up @@ -1656,16 +1656,18 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c
av_bprintf(&header, "#define FULL_RANGE_OUT\n");

+ if (ctx->in_desc->comp[0].depth == 10) {
+ av_bprintf(&header, "#define INPUT_QUANTIZATION_OFFSET 0.0009765774014f\n");
+ av_bprintf(&header, "#define INPUT_Y_SCALE 1.1678082192f\n");
+ av_bprintf(&header, "#define INPUT_UV_SCALE 1.1417410714f\n");
+ av_bprintf(&header, "#define INPUT_QUANTIZATION_OFFSET %ff\n", QUANTIZATION_OFFSET_10B);
+ av_bprintf(&header, "#define INPUT_Y_SCALE %ff\n", INPUT_Y_SCALE_10B);
+ av_bprintf(&header, "#define INPUT_UV_SCALE %ff\n", INPUT_UV_SCALE_10B);
+ } else if (ctx->in_desc->comp[0].depth == 16) {
+ // 16bit texture is actually 12bit
+ av_bprintf(&header, "#define INPUT_QUANTIZATION_OFFSET 0.0002441443503f\n");
+ av_bprintf(&header, "#define INPUT_Y_SCALE 1.1689497717f\n");
+ av_bprintf(&header, "#define INPUT_UV_SCALE 1.1428571429f\n");
+ // Assume 16bit is actually 12bit for now as that is what the hardware decoders producing
+ // and what videos are actually encoded in
+ av_bprintf(&header, "#define INPUT_QUANTIZATION_OFFSET %ff\n", QUANTIZATION_OFFSET_12B);
+ av_bprintf(&header, "#define INPUT_Y_SCALE %ff\n", INPUT_Y_SCALE_12B);
+ av_bprintf(&header, "#define INPUT_UV_SCALE %ff\n", INPUT_UV_SCALE_12B);
+ } else {
+ // should not happen, but for completeness
+ // once we can tell if the input is real 16bit we can use this branch
+ av_bprintf(&header, "#define INPUT_QUANTIZATION_OFFSET 0.0f\n");
+ av_bprintf(&header, "#define INPUT_Y_SCALE 1.0f\n");
+ av_bprintf(&header, "#define INPUT_UV_SCALE 1.0f\n");
Expand All @@ -1675,13 +1677,14 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c
+ av_bprintf(&header, "#define RESCALE_LIMITED_RANGE_OUTPUT\n");
+ }
+
+ if (ctx->in_desc->comp[0].depth == 10)
+ av_bprintf(&header, "#define OUTPUT_QUANTIZATION_OFFSET 0.0009765774014f\n");
+ if (ctx->out_desc->comp[0].depth == 10)
+ av_bprintf(&header, "#define OUTPUT_QUANTIZATION_OFFSET %ff\n", QUANTIZATION_OFFSET_10B);
+ // Don't handle 12b offset for now and assume 16b output is real 16b out to make it consistent with other filters
+
av_bprintf(&header, "#define chroma_loc %d\n", (int)ctx->chroma_loc);

if (rgb2rgb_passthrough)
@@ -199,19 +561,41 @@ static int tonemap_opencl_init(AVFilterC
@@ -199,19 +564,41 @@ static int tonemap_opencl_init(AVFilterC
else
ff_opencl_print_const_matrix_3x3(&header, "rgb2rgb", rgb2rgb);

Expand Down Expand Up @@ -1730,7 +1733,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c
ctx->colorspace_out, av_color_space_name(ctx->colorspace_out));
goto fail;
}
@@ -219,24 +603,23 @@ static int tonemap_opencl_init(AVFilterC
@@ -219,24 +606,23 @@ static int tonemap_opencl_init(AVFilterC
ff_fill_rgb2yuv_table(luma_dst, rgb2yuv);
ff_opencl_print_const_matrix_3x3(&header, "yuv_matrix", rgb2yuv);

Expand Down Expand Up @@ -1770,7 +1773,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c

av_log(avctx, AV_LOG_DEBUG, "Generated OpenCL header:\n%s\n", header.str);
opencl_sources[0] = header.str;
@@ -254,46 +637,171 @@ static int tonemap_opencl_init(AVFilterC
@@ -254,46 +640,171 @@ static int tonemap_opencl_init(AVFilterC
CL_FAIL_ON_ERROR(AVERROR(EIO), "Failed to create OpenCL "
"command queue %d.\n", cle);

Expand Down Expand Up @@ -1960,7 +1963,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c
ret = ff_opencl_filter_config_output(outlink);
if (ret < 0)
return ret;
@@ -308,13 +816,46 @@ static int launch_kernel(AVFilterContext
@@ -308,13 +819,46 @@ static int launch_kernel(AVFilterContext
size_t global_work[2];
size_t local_work[2];
cl_int cle;
Expand Down Expand Up @@ -2009,7 +2012,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c

local_work[0] = 16;
local_work[1] = 16;
@@ -338,13 +879,10 @@ static int tonemap_opencl_filter_frame(A
@@ -338,13 +882,10 @@ static int tonemap_opencl_filter_frame(A
AVFilterContext *avctx = inlink->dst;
AVFilterLink *outlink = avctx->outputs[0];
TonemapOpenCLContext *ctx = avctx->priv;
Expand All @@ -2024,7 +2027,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c

av_log(ctx, AV_LOG_DEBUG, "Filter input: %s, %ux%u (%"PRId64").\n",
av_get_pix_fmt_name(input->format),
@@ -363,8 +901,49 @@ static int tonemap_opencl_filter_frame(A
@@ -363,8 +904,49 @@ static int tonemap_opencl_filter_frame(A
if (err < 0)
goto fail;

Expand Down Expand Up @@ -2076,7 +2079,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c

if (ctx->trc != -1)
output->color_trc = ctx->trc;
@@ -385,72 +964,50 @@ static int tonemap_opencl_filter_frame(A
@@ -385,72 +967,50 @@ static int tonemap_opencl_filter_frame(A
ctx->range_out = output->color_range;
ctx->chroma_loc = output->chroma_location;

Expand Down Expand Up @@ -2172,7 +2175,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c
av_frame_free(&input);
av_frame_free(&output);
return err;
@@ -458,24 +1015,9 @@ fail:
@@ -458,24 +1018,9 @@ fail:

static av_cold void tonemap_opencl_uninit(AVFilterContext *avctx)
{
Expand All @@ -2199,7 +2202,7 @@ Index: FFmpeg/libavfilter/vf_tonemap_opencl.c

ff_opencl_filter_uninit(avctx);
}
@@ -483,37 +1025,50 @@ static av_cold void tonemap_opencl_unini
@@ -483,37 +1028,50 @@ static av_cold void tonemap_opencl_unini
#define OFFSET(x) offsetof(TonemapOpenCLContext, x)
#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
static const AVOption tonemap_opencl_options[] = {
Expand Down
6 changes: 4 additions & 2 deletions debian/patches/0052-add-vf-tonemap-videotoolbox-filter.patch
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Index: FFmpeg/libavfilter/metal/vf_tonemap_videotoolbox.metal
===================================================================
--- /dev/null
+++ FFmpeg/libavfilter/metal/vf_tonemap_videotoolbox.metal
@@ -0,0 +1,914 @@
@@ -0,0 +1,916 @@
+/*
+ * Copyright (c) 2024 Gnattu OC <gnattuoc@me.com>
+ *
Expand Down Expand Up @@ -269,6 +269,7 @@ Index: FFmpeg/libavfilter/metal/vf_tonemap_videotoolbox.metal
+
+// ------------
+// Color conversion
+// See libavfilter/colorspace.h for derivation of these constants
+float3 yuv2rgb(float y, float u, float v) {
+ y += mix(0.0f, 0.0009765774014f, y > 0.0f);
+ u += mix(0.0f, 0.0009765774014f, u > 0.0f);
Expand Down Expand Up @@ -317,7 +318,8 @@ Index: FFmpeg/libavfilter/metal/vf_tonemap_videotoolbox.metal
+ v = floor(((224.0f * v + 128.0f) * 256.0f) + 0.5f) / 65535.0f;
+ }
+ }
+ // in rgb2yuv conversion, enable_dither means output is 8bit
+ // in rgb2yuv conversion, enable_dither means output is 8bit in metal pipeline
+ // use this to check if we need the 10bit offset
+ if (!enable_dither) {
+ y -= mix(0.0f, 0.0009765774014f, y > 0.0f);
nyanmisaka marked this conversation as resolved.
Show resolved Hide resolved
+ u -= mix(0.0f, 0.0009765774014f, u > 0.0f);
Expand Down
Loading