| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2017 Richard Ling | ||
| 3 | * | ||
| 4 | * This file is part of FFmpeg. | ||
| 5 | * | ||
| 6 | * FFmpeg is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU Lesser General Public | ||
| 8 | * License as published by the Free Software Foundation; either | ||
| 9 | * version 2.1 of the License, or (at your option) any later version. | ||
| 10 | * | ||
| 11 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 14 | * Lesser General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU Lesser General Public | ||
| 17 | * License along with FFmpeg; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 19 | */ | ||
| 20 | |||
| 21 | /* | ||
| 22 | * Normalize RGB video (aka histogram stretching, contrast stretching). | ||
| 23 | * See: https://en.wikipedia.org/wiki/Normalization_(image_processing) | ||
| 24 | * | ||
| 25 | * For each channel of each frame, the filter computes the input range and maps | ||
| 26 | * it linearly to the user-specified output range. The output range defaults | ||
| 27 | * to the full dynamic range from pure black to pure white. | ||
| 28 | * | ||
| 29 | * Naively maximising the dynamic range of each frame of video in isolation | ||
| 30 | * may cause flickering (rapid changes in brightness of static objects in the | ||
| 31 | * scene) when small dark or bright objects enter or leave the scene. This | ||
| 32 | * filter can apply temporal smoothing to the input range to reduce flickering. | ||
| 33 | * Temporal smoothing is similar to the auto-exposure (automatic gain control) | ||
| 34 | * on a video camera, which performs the same function; and, like a video | ||
| 35 | * camera, it may cause a period of over- or under-exposure of the video. | ||
| 36 | * | ||
| 37 | * The filter can normalize the R,G,B channels independently, which may cause | ||
| 38 | * color shifting, or link them together as a single channel, which prevents | ||
| 39 | * color shifting. More precisely, linked normalization preserves hue (as it's | ||
| 40 | * defined in HSV/HSL color spaces) while independent normalization does not. | ||
| 41 | * Independent normalization can be used to remove color casts, such as the | ||
| 42 | * blue cast from underwater video, restoring more natural colors. The filter | ||
| 43 | * can also combine independent and linked normalization in any ratio. | ||
| 44 | * | ||
| 45 | * Finally the overall strength of the filter can be adjusted, from no effect | ||
| 46 | * to full normalization. | ||
| 47 | * | ||
| 48 | * The 5 AVOptions are: | ||
| 49 | * blackpt, Colors which define the output range. The minimum input value | ||
| 50 | * whitept is mapped to the blackpt. The maximum input value is mapped to | ||
| 51 | * the whitept. The defaults are black and white respectively. | ||
| 52 | * Specifying white for blackpt and black for whitept will give | ||
| 53 | * color-inverted, normalized video. Shades of grey can be used | ||
| 54 | * to reduce the dynamic range (contrast). Specifying saturated | ||
| 55 | * colors here can create some interesting effects. | ||
| 56 | * | ||
| 57 | * smoothing The amount of temporal smoothing, expressed in frames (>=0). | ||
| 58 | * the minimum and maximum input values of each channel are | ||
| 59 | * smoothed using a rolling average over the current frame and | ||
| 60 | * that many previous frames of video. Defaults to 0 (no temporal | ||
| 61 | * smoothing). | ||
| 62 | * | ||
| 63 | * independence | ||
| 64 | * Controls the ratio of independent (color shifting) channel | ||
| 65 | * normalization to linked (color preserving) normalization. 0.0 | ||
| 66 | * is fully linked, 1.0 is fully independent. Defaults to fully | ||
| 67 | * independent. | ||
| 68 | * | ||
| 69 | * strength Overall strength of the filter. 1.0 is full strength. 0.0 is | ||
| 70 | * a rather expensive no-op. Values in between can give a gentle | ||
| 71 | * boost to low-contrast video without creating an artificial | ||
| 72 | * over-processed look. The default is full strength. | ||
| 73 | */ | ||
| 74 | |||
| 75 | #include "libavutil/intreadwrite.h" | ||
| 76 | #include "libavutil/mem.h" | ||
| 77 | #include "libavutil/opt.h" | ||
| 78 | #include "libavutil/pixdesc.h" | ||
| 79 | #include "avfilter.h" | ||
| 80 | #include "drawutils.h" | ||
| 81 | #include "filters.h" | ||
| 82 | #include "video.h" | ||
| 83 | |||
| 84 | typedef struct NormalizeHistory { | ||
| 85 | uint16_t *history; // History entries. | ||
| 86 | uint64_t history_sum; // Sum of history entries. | ||
| 87 | } NormalizeHistory; | ||
| 88 | |||
| 89 | typedef struct NormalizeLocal { | ||
| 90 | uint16_t in; // Original input byte value for this frame. | ||
| 91 | float smoothed; // Smoothed input value [0,255]. | ||
| 92 | float out; // Output value [0,255] | ||
| 93 | } NormalizeLocal; | ||
| 94 | |||
| 95 | typedef struct NormalizeContext { | ||
| 96 | const AVClass *class; | ||
| 97 | |||
| 98 | // Storage for the corresponding AVOptions | ||
| 99 | uint8_t blackpt[4]; | ||
| 100 | uint8_t whitept[4]; | ||
| 101 | int smoothing; | ||
| 102 | float independence; | ||
| 103 | float strength; | ||
| 104 | |||
| 105 | uint8_t co[4]; // Offsets to R,G,B,A bytes respectively in each pixel | ||
| 106 | int depth; | ||
| 107 | int sblackpt[4]; | ||
| 108 | int swhitept[4]; | ||
| 109 | int num_components; // Number of components in the pixel format | ||
| 110 | int step; | ||
| 111 | int history_len; // Number of frames to average; based on smoothing factor | ||
| 112 | int frame_num; // Increments on each frame, starting from 0. | ||
| 113 | |||
| 114 | // Per-extremum, per-channel history, for temporal smoothing. | ||
| 115 | NormalizeHistory min[3], max[3]; // Min and max for each channel in {R,G,B}. | ||
| 116 | uint16_t *history_mem; // Single allocation for above history entries | ||
| 117 | |||
| 118 | uint16_t lut[3][65536]; // Lookup table | ||
| 119 | |||
| 120 | void (*find_min_max)(struct NormalizeContext *s, AVFrame *in, NormalizeLocal min[3], NormalizeLocal max[3]); | ||
| 121 | void (*process)(struct NormalizeContext *s, AVFrame *in, AVFrame *out); | ||
| 122 | } NormalizeContext; | ||
| 123 | |||
| 124 | #define OFFSET(x) offsetof(NormalizeContext, x) | ||
| 125 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM | ||
| 126 | #define FLAGSR AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM | ||
| 127 | |||
| 128 | static const AVOption normalize_options[] = { | ||
| 129 | { "blackpt", "output color to which darkest input color is mapped", OFFSET(blackpt), AV_OPT_TYPE_COLOR, { .str = "black" }, 0, 0, FLAGSR }, | ||
| 130 | { "whitept", "output color to which brightest input color is mapped", OFFSET(whitept), AV_OPT_TYPE_COLOR, { .str = "white" }, 0, 0, FLAGSR }, | ||
| 131 | { "smoothing", "amount of temporal smoothing of the input range, to reduce flicker", OFFSET(smoothing), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX/8, FLAGS }, | ||
| 132 | { "independence", "proportion of independent to linked channel normalization", OFFSET(independence), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, 0.0, 1.0, FLAGSR }, | ||
| 133 | { "strength", "strength of filter, from no effect to full normalization", OFFSET(strength), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, 0.0, 1.0, FLAGSR }, | ||
| 134 | { NULL } | ||
| 135 | }; | ||
| 136 | |||
| 137 | AVFILTER_DEFINE_CLASS(normalize); | ||
| 138 | |||
| 139 | ✗ | static void find_min_max(NormalizeContext *s, AVFrame *in, NormalizeLocal min[3], NormalizeLocal max[3]) | |
| 140 | { | ||
| 141 | ✗ | for (int c = 0; c < 3; c++) | |
| 142 | ✗ | min[c].in = max[c].in = in->data[0][s->co[c]]; | |
| 143 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 144 | ✗ | uint8_t *inp = in->data[0] + y * in->linesize[0]; | |
| 145 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 146 | ✗ | for (int c = 0; c < 3; c++) { | |
| 147 | ✗ | min[c].in = FFMIN(min[c].in, inp[s->co[c]]); | |
| 148 | ✗ | max[c].in = FFMAX(max[c].in, inp[s->co[c]]); | |
| 149 | } | ||
| 150 | ✗ | inp += s->step; | |
| 151 | } | ||
| 152 | } | ||
| 153 | ✗ | } | |
| 154 | |||
| 155 | ✗ | static void process(NormalizeContext *s, AVFrame *in, AVFrame *out) | |
| 156 | { | ||
| 157 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 158 | ✗ | uint8_t *inp = in->data[0] + y * in->linesize[0]; | |
| 159 | ✗ | uint8_t *outp = out->data[0] + y * out->linesize[0]; | |
| 160 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 161 | ✗ | for (int c = 0; c < 3; c++) | |
| 162 | ✗ | outp[s->co[c]] = s->lut[c][inp[s->co[c]]]; | |
| 163 | ✗ | if (s->num_components == 4) | |
| 164 | // Copy alpha as-is. | ||
| 165 | ✗ | outp[s->co[3]] = inp[s->co[3]]; | |
| 166 | ✗ | inp += s->step; | |
| 167 | ✗ | outp += s->step; | |
| 168 | } | ||
| 169 | } | ||
| 170 | ✗ | } | |
| 171 | |||
| 172 | ✗ | static void find_min_max_planar(NormalizeContext *s, AVFrame *in, NormalizeLocal min[3], NormalizeLocal max[3]) | |
| 173 | { | ||
| 174 | ✗ | min[0].in = max[0].in = in->data[2][0]; | |
| 175 | ✗ | min[1].in = max[1].in = in->data[0][0]; | |
| 176 | ✗ | min[2].in = max[2].in = in->data[1][0]; | |
| 177 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 178 | ✗ | uint8_t *inrp = in->data[2] + y * in->linesize[2]; | |
| 179 | ✗ | uint8_t *ingp = in->data[0] + y * in->linesize[0]; | |
| 180 | ✗ | uint8_t *inbp = in->data[1] + y * in->linesize[1]; | |
| 181 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 182 | ✗ | min[0].in = FFMIN(min[0].in, inrp[x]); | |
| 183 | ✗ | max[0].in = FFMAX(max[0].in, inrp[x]); | |
| 184 | ✗ | min[1].in = FFMIN(min[1].in, ingp[x]); | |
| 185 | ✗ | max[1].in = FFMAX(max[1].in, ingp[x]); | |
| 186 | ✗ | min[2].in = FFMIN(min[2].in, inbp[x]); | |
| 187 | ✗ | max[2].in = FFMAX(max[2].in, inbp[x]); | |
| 188 | } | ||
| 189 | } | ||
| 190 | ✗ | } | |
| 191 | |||
| 192 | ✗ | static void process_planar(NormalizeContext *s, AVFrame *in, AVFrame *out) | |
| 193 | { | ||
| 194 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 195 | ✗ | uint8_t *inrp = in->data[2] + y * in->linesize[2]; | |
| 196 | ✗ | uint8_t *ingp = in->data[0] + y * in->linesize[0]; | |
| 197 | ✗ | uint8_t *inbp = in->data[1] + y * in->linesize[1]; | |
| 198 | ✗ | uint8_t *inap = in->data[3] + y * in->linesize[3]; | |
| 199 | ✗ | uint8_t *outrp = out->data[2] + y * out->linesize[2]; | |
| 200 | ✗ | uint8_t *outgp = out->data[0] + y * out->linesize[0]; | |
| 201 | ✗ | uint8_t *outbp = out->data[1] + y * out->linesize[1]; | |
| 202 | ✗ | uint8_t *outap = out->data[3] + y * out->linesize[3]; | |
| 203 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 204 | ✗ | outrp[x] = s->lut[0][inrp[x]]; | |
| 205 | ✗ | outgp[x] = s->lut[1][ingp[x]]; | |
| 206 | ✗ | outbp[x] = s->lut[2][inbp[x]]; | |
| 207 | ✗ | if (s->num_components == 4) | |
| 208 | ✗ | outap[x] = inap[x]; | |
| 209 | } | ||
| 210 | } | ||
| 211 | ✗ | } | |
| 212 | |||
| 213 | ✗ | static void find_min_max_16(NormalizeContext *s, AVFrame *in, NormalizeLocal min[3], NormalizeLocal max[3]) | |
| 214 | { | ||
| 215 | ✗ | for (int c = 0; c < 3; c++) | |
| 216 | ✗ | min[c].in = max[c].in = AV_RN16(in->data[0] + 2 * s->co[c]); | |
| 217 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 218 | ✗ | uint16_t *inp = (uint16_t *)(in->data[0] + y * in->linesize[0]); | |
| 219 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 220 | ✗ | for (int c = 0; c < 3; c++) { | |
| 221 | ✗ | min[c].in = FFMIN(min[c].in, inp[s->co[c]]); | |
| 222 | ✗ | max[c].in = FFMAX(max[c].in, inp[s->co[c]]); | |
| 223 | } | ||
| 224 | ✗ | inp += s->step; | |
| 225 | } | ||
| 226 | } | ||
| 227 | ✗ | } | |
| 228 | |||
| 229 | ✗ | static void process_16(NormalizeContext *s, AVFrame *in, AVFrame *out) | |
| 230 | { | ||
| 231 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 232 | ✗ | uint16_t *inp = (uint16_t *)(in->data[0] + y * in->linesize[0]); | |
| 233 | ✗ | uint16_t *outp = (uint16_t *)(out->data[0] + y * out->linesize[0]); | |
| 234 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 235 | ✗ | for (int c = 0; c < 3; c++) | |
| 236 | ✗ | outp[s->co[c]] = s->lut[c][inp[s->co[c]]]; | |
| 237 | ✗ | if (s->num_components == 4) | |
| 238 | // Copy alpha as-is. | ||
| 239 | ✗ | outp[s->co[3]] = inp[s->co[3]]; | |
| 240 | ✗ | inp += s->step; | |
| 241 | ✗ | outp += s->step; | |
| 242 | } | ||
| 243 | } | ||
| 244 | ✗ | } | |
| 245 | |||
| 246 | ✗ | static void find_min_max_planar_16(NormalizeContext *s, AVFrame *in, NormalizeLocal min[3], NormalizeLocal max[3]) | |
| 247 | { | ||
| 248 | ✗ | min[0].in = max[0].in = AV_RN16(in->data[2]); | |
| 249 | ✗ | min[1].in = max[1].in = AV_RN16(in->data[0]); | |
| 250 | ✗ | min[2].in = max[2].in = AV_RN16(in->data[1]); | |
| 251 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 252 | ✗ | uint16_t *inrp = (uint16_t *)(in->data[2] + y * in->linesize[2]); | |
| 253 | ✗ | uint16_t *ingp = (uint16_t *)(in->data[0] + y * in->linesize[0]); | |
| 254 | ✗ | uint16_t *inbp = (uint16_t *)(in->data[1] + y * in->linesize[1]); | |
| 255 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 256 | ✗ | min[0].in = FFMIN(min[0].in, inrp[x]); | |
| 257 | ✗ | max[0].in = FFMAX(max[0].in, inrp[x]); | |
| 258 | ✗ | min[1].in = FFMIN(min[1].in, ingp[x]); | |
| 259 | ✗ | max[1].in = FFMAX(max[1].in, ingp[x]); | |
| 260 | ✗ | min[2].in = FFMIN(min[2].in, inbp[x]); | |
| 261 | ✗ | max[2].in = FFMAX(max[2].in, inbp[x]); | |
| 262 | } | ||
| 263 | } | ||
| 264 | ✗ | } | |
| 265 | |||
| 266 | ✗ | static void process_planar_16(NormalizeContext *s, AVFrame *in, AVFrame *out) | |
| 267 | { | ||
| 268 | ✗ | for (int y = 0; y < in->height; y++) { | |
| 269 | ✗ | uint16_t *inrp = (uint16_t *)(in->data[2] + y * in->linesize[2]); | |
| 270 | ✗ | uint16_t *ingp = (uint16_t *)(in->data[0] + y * in->linesize[0]); | |
| 271 | ✗ | uint16_t *inbp = (uint16_t *)(in->data[1] + y * in->linesize[1]); | |
| 272 | ✗ | uint16_t *inap = (uint16_t *)(in->data[3] + y * in->linesize[3]); | |
| 273 | ✗ | uint16_t *outrp = (uint16_t *)(out->data[2] + y * out->linesize[2]); | |
| 274 | ✗ | uint16_t *outgp = (uint16_t *)(out->data[0] + y * out->linesize[0]); | |
| 275 | ✗ | uint16_t *outbp = (uint16_t *)(out->data[1] + y * out->linesize[1]); | |
| 276 | ✗ | uint16_t *outap = (uint16_t *)(out->data[3] + y * out->linesize[3]); | |
| 277 | ✗ | for (int x = 0; x < in->width; x++) { | |
| 278 | ✗ | outrp[x] = s->lut[0][inrp[x]]; | |
| 279 | ✗ | outgp[x] = s->lut[1][ingp[x]]; | |
| 280 | ✗ | outbp[x] = s->lut[2][inbp[x]]; | |
| 281 | ✗ | if (s->num_components == 4) | |
| 282 | ✗ | outap[x] = inap[x]; | |
| 283 | } | ||
| 284 | } | ||
| 285 | ✗ | } | |
| 286 | |||
| 287 | // This function is the main guts of the filter. Normalizes the input frame | ||
| 288 | // into the output frame. The frames are known to have the same dimensions | ||
| 289 | // and pixel format. | ||
| 290 | ✗ | static void normalize(NormalizeContext *s, AVFrame *in, AVFrame *out) | |
| 291 | { | ||
| 292 | // Per-extremum, per-channel local variables. | ||
| 293 | NormalizeLocal min[3], max[3]; // Min and max for each channel in {R,G,B}. | ||
| 294 | |||
| 295 | float rgb_min_smoothed; // Min input range for linked normalization | ||
| 296 | float rgb_max_smoothed; // Max input range for linked normalization | ||
| 297 | int c; | ||
| 298 | |||
| 299 | // First, scan the input frame to find, for each channel, the minimum | ||
| 300 | // (min.in) and maximum (max.in) values present in the channel. | ||
| 301 | ✗ | s->find_min_max(s, in, min, max); | |
| 302 | |||
| 303 | // Next, for each channel, push min.in and max.in into their respective | ||
| 304 | // histories, to determine the min.smoothed and max.smoothed for this frame. | ||
| 305 | { | ||
| 306 | ✗ | int history_idx = s->frame_num % s->history_len; | |
| 307 | // Assume the history is not yet full; num_history_vals is the number | ||
| 308 | // of frames received so far including the current frame. | ||
| 309 | ✗ | int num_history_vals = s->frame_num + 1; | |
| 310 | ✗ | if (s->frame_num >= s->history_len) { | |
| 311 | //The history is full; drop oldest value and cap num_history_vals. | ||
| 312 | ✗ | for (c = 0; c < 3; c++) { | |
| 313 | ✗ | s->min[c].history_sum -= s->min[c].history[history_idx]; | |
| 314 | ✗ | s->max[c].history_sum -= s->max[c].history[history_idx]; | |
| 315 | } | ||
| 316 | ✗ | num_history_vals = s->history_len; | |
| 317 | } | ||
| 318 | // For each extremum, update history_sum and calculate smoothed value | ||
| 319 | // as the rolling average of the history entries. | ||
| 320 | ✗ | for (c = 0; c < 3; c++) { | |
| 321 | ✗ | s->min[c].history_sum += (s->min[c].history[history_idx] = min[c].in); | |
| 322 | ✗ | min[c].smoothed = s->min[c].history_sum / (float)num_history_vals; | |
| 323 | ✗ | s->max[c].history_sum += (s->max[c].history[history_idx] = max[c].in); | |
| 324 | ✗ | max[c].smoothed = s->max[c].history_sum / (float)num_history_vals; | |
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | // Determine the input range for linked normalization. This is simply the | ||
| 329 | // minimum of the per-channel minimums, and the maximum of the per-channel | ||
| 330 | // maximums. | ||
| 331 | ✗ | rgb_min_smoothed = FFMIN3(min[0].smoothed, min[1].smoothed, min[2].smoothed); | |
| 332 | ✗ | rgb_max_smoothed = FFMAX3(max[0].smoothed, max[1].smoothed, max[2].smoothed); | |
| 333 | |||
| 334 | // Now, process each channel to determine the input and output range and | ||
| 335 | // build the lookup tables. | ||
| 336 | ✗ | for (c = 0; c < 3; c++) { | |
| 337 | int in_val; | ||
| 338 | // Adjust the input range for this channel [min.smoothed,max.smoothed] | ||
| 339 | // by mixing in the correct proportion of the linked normalization | ||
| 340 | // input range [rgb_min_smoothed,rgb_max_smoothed]. | ||
| 341 | ✗ | min[c].smoothed = (min[c].smoothed * s->independence) | |
| 342 | ✗ | + (rgb_min_smoothed * (1.0f - s->independence)); | |
| 343 | ✗ | max[c].smoothed = (max[c].smoothed * s->independence) | |
| 344 | ✗ | + (rgb_max_smoothed * (1.0f - s->independence)); | |
| 345 | |||
| 346 | // Calculate the output range [min.out,max.out] as a ratio of the full- | ||
| 347 | // strength output range [blackpt,whitept] and the original input range | ||
| 348 | // [min.in,max.in], based on the user-specified filter strength. | ||
| 349 | ✗ | min[c].out = (s->sblackpt[c] * s->strength) | |
| 350 | ✗ | + (min[c].in * (1.0f - s->strength)); | |
| 351 | ✗ | max[c].out = (s->swhitept[c] * s->strength) | |
| 352 | ✗ | + (max[c].in * (1.0f - s->strength)); | |
| 353 | |||
| 354 | // Now, build a lookup table which linearly maps the adjusted input range | ||
| 355 | // [min.smoothed,max.smoothed] to the output range [min.out,max.out]. | ||
| 356 | // Perform the linear interpolation for each x: | ||
| 357 | // lut[x] = (int)(float(x - min.smoothed) * scale + max.out + 0.5) | ||
| 358 | // where scale = (max.out - min.out) / (max.smoothed - min.smoothed) | ||
| 359 | ✗ | if (min[c].smoothed == max[c].smoothed) { | |
| 360 | // There is no dynamic range to expand. No mapping for this channel. | ||
| 361 | ✗ | for (in_val = min[c].in; in_val <= max[c].in; in_val++) | |
| 362 | ✗ | s->lut[c][in_val] = min[c].out; | |
| 363 | } else { | ||
| 364 | // We must set lookup values for all values in the original input | ||
| 365 | // range [min.in,max.in]. Since the original input range may be | ||
| 366 | // larger than [min.smoothed,max.smoothed], some output values may | ||
| 367 | // fall outside the [0,255] dynamic range. We need to clamp them. | ||
| 368 | ✗ | float scale = (max[c].out - min[c].out) / (max[c].smoothed - min[c].smoothed); | |
| 369 | ✗ | for (in_val = min[c].in; in_val <= max[c].in; in_val++) { | |
| 370 | ✗ | int out_val = (in_val - min[c].smoothed) * scale + min[c].out + 0.5f; | |
| 371 | ✗ | out_val = av_clip_uintp2_c(out_val, s->depth); | |
| 372 | ✗ | s->lut[c][in_val] = out_val; | |
| 373 | } | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | // Finally, process the pixels of the input frame using the lookup tables. | ||
| 378 | ✗ | s->process(s, in, out); | |
| 379 | |||
| 380 | ✗ | s->frame_num++; | |
| 381 | ✗ | } | |
| 382 | |||
| 383 | // Now we define all the functions accessible from the ff_vf_normalize class, | ||
| 384 | // which is ffmpeg's interface to our filter. See doc/filter_design.txt and | ||
| 385 | // doc/writing_filters.txt for descriptions of what these interface functions | ||
| 386 | // are expected to do. | ||
| 387 | |||
| 388 | // The pixel formats that our filter supports. We should be able to process | ||
| 389 | // any 8-bit RGB formats. 16-bit support might be useful one day. | ||
| 390 | static const enum AVPixelFormat pixel_fmts[] = { | ||
| 391 | AV_PIX_FMT_RGB24, | ||
| 392 | AV_PIX_FMT_BGR24, | ||
| 393 | AV_PIX_FMT_ARGB, | ||
| 394 | AV_PIX_FMT_RGBA, | ||
| 395 | AV_PIX_FMT_ABGR, | ||
| 396 | AV_PIX_FMT_BGRA, | ||
| 397 | AV_PIX_FMT_0RGB, | ||
| 398 | AV_PIX_FMT_RGB0, | ||
| 399 | AV_PIX_FMT_0BGR, | ||
| 400 | AV_PIX_FMT_BGR0, | ||
| 401 | AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48, | ||
| 402 | AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64, | ||
| 403 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, | ||
| 404 | AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, | ||
| 405 | AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, | ||
| 406 | AV_PIX_FMT_NONE | ||
| 407 | }; | ||
| 408 | |||
| 409 | // At this point we know the pixel format used for both input and output. We | ||
| 410 | // can also access the frame rate of the input video and allocate some memory | ||
| 411 | // appropriately | ||
| 412 | ✗ | static int config_input(AVFilterLink *inlink) | |
| 413 | { | ||
| 414 | ✗ | NormalizeContext *s = inlink->dst->priv; | |
| 415 | // Store offsets to R,G,B,A bytes respectively in each pixel | ||
| 416 | ✗ | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
| 417 | int c, planar, scale; | ||
| 418 | |||
| 419 | ✗ | ff_fill_rgba_map(s->co, inlink->format); | |
| 420 | ✗ | s->depth = desc->comp[0].depth; | |
| 421 | ✗ | scale = 1 << (s->depth - 8); | |
| 422 | ✗ | s->num_components = desc->nb_components; | |
| 423 | ✗ | s->step = av_get_padded_bits_per_pixel(desc) >> (3 + (s->depth > 8)); | |
| 424 | // Convert smoothing value to history_len (a count of frames to average, | ||
| 425 | // must be at least 1). Currently this is a direct assignment, but the | ||
| 426 | // smoothing value was originally envisaged as a number of seconds. In | ||
| 427 | // future it would be nice to set history_len using a number of seconds, | ||
| 428 | // but VFR video is currently an obstacle to doing so. | ||
| 429 | ✗ | s->history_len = s->smoothing + 1; | |
| 430 | // Allocate the history buffers -- there are 6 -- one for each extrema. | ||
| 431 | // s->smoothing is limited to INT_MAX/8, so that (s->history_len * 6) | ||
| 432 | // can't overflow on 32bit causing a too-small allocation. | ||
| 433 | ✗ | s->history_mem = av_malloc(s->history_len * 6 * sizeof(*s->history_mem)); | |
| 434 | ✗ | if (s->history_mem == NULL) | |
| 435 | ✗ | return AVERROR(ENOMEM); | |
| 436 | |||
| 437 | ✗ | for (c = 0; c < 3; c++) { | |
| 438 | ✗ | s->min[c].history = s->history_mem + (c*2) * s->history_len; | |
| 439 | ✗ | s->max[c].history = s->history_mem + (c*2+1) * s->history_len; | |
| 440 | ✗ | s->sblackpt[c] = scale * s->blackpt[c] + (s->blackpt[c] & (1 << (s->depth - 8))); | |
| 441 | ✗ | s->swhitept[c] = scale * s->whitept[c] + (s->whitept[c] & (1 << (s->depth - 8))); | |
| 442 | } | ||
| 443 | |||
| 444 | ✗ | planar = desc->flags & AV_PIX_FMT_FLAG_PLANAR; | |
| 445 | |||
| 446 | ✗ | if (s->depth <= 8) { | |
| 447 | ✗ | s->find_min_max = planar ? find_min_max_planar : find_min_max; | |
| 448 | ✗ | s->process = planar? process_planar : process; | |
| 449 | } else { | ||
| 450 | ✗ | s->find_min_max = planar ? find_min_max_planar_16 : find_min_max_16; | |
| 451 | ✗ | s->process = planar? process_planar_16 : process_16; | |
| 452 | } | ||
| 453 | |||
| 454 | ✗ | return 0; | |
| 455 | } | ||
| 456 | |||
| 457 | // Free any memory allocations here | ||
| 458 | ✗ | static av_cold void uninit(AVFilterContext *ctx) | |
| 459 | { | ||
| 460 | ✗ | NormalizeContext *s = ctx->priv; | |
| 461 | |||
| 462 | ✗ | av_freep(&s->history_mem); | |
| 463 | ✗ | } | |
| 464 | |||
| 465 | // This function is pretty much standard from doc/writing_filters.txt. It | ||
| 466 | // tries to do in-place filtering where possible, only allocating a new output | ||
| 467 | // frame when absolutely necessary. | ||
| 468 | ✗ | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
| 469 | { | ||
| 470 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 471 | ✗ | AVFilterLink *outlink = ctx->outputs[0]; | |
| 472 | ✗ | NormalizeContext *s = ctx->priv; | |
| 473 | AVFrame *out; | ||
| 474 | // Set 'direct' if we can modify the input frame in-place. Otherwise we | ||
| 475 | // need to retrieve a new frame from the output link. | ||
| 476 | ✗ | int direct = av_frame_is_writable(in) && !ctx->is_disabled; | |
| 477 | |||
| 478 | ✗ | if (direct) { | |
| 479 | ✗ | out = in; | |
| 480 | } else { | ||
| 481 | ✗ | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
| 482 | ✗ | if (!out) { | |
| 483 | ✗ | av_frame_free(&in); | |
| 484 | ✗ | return AVERROR(ENOMEM); | |
| 485 | } | ||
| 486 | ✗ | av_frame_copy_props(out, in); | |
| 487 | } | ||
| 488 | |||
| 489 | // Now we've got the input and output frames (which may be the same frame) | ||
| 490 | // perform the filtering with our custom function. | ||
| 491 | ✗ | normalize(s, in, out); | |
| 492 | |||
| 493 | ✗ | if (ctx->is_disabled) { | |
| 494 | ✗ | av_frame_free(&out); | |
| 495 | ✗ | return ff_filter_frame(outlink, in); | |
| 496 | } | ||
| 497 | |||
| 498 | ✗ | if (!direct) | |
| 499 | ✗ | av_frame_free(&in); | |
| 500 | |||
| 501 | ✗ | return ff_filter_frame(outlink, out); | |
| 502 | } | ||
| 503 | |||
| 504 | static const AVFilterPad inputs[] = { | ||
| 505 | { | ||
| 506 | .name = "default", | ||
| 507 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 508 | .filter_frame = filter_frame, | ||
| 509 | .config_props = config_input, | ||
| 510 | }, | ||
| 511 | }; | ||
| 512 | |||
| 513 | const FFFilter ff_vf_normalize = { | ||
| 514 | .p.name = "normalize", | ||
| 515 | .p.description = NULL_IF_CONFIG_SMALL("Normalize RGB video."), | ||
| 516 | .p.priv_class = &normalize_class, | ||
| 517 | .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL, | ||
| 518 | .priv_size = sizeof(NormalizeContext), | ||
| 519 | .uninit = uninit, | ||
| 520 | FILTER_INPUTS(inputs), | ||
| 521 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
| 522 | FILTER_PIXFMTS_ARRAY(pixel_fmts), | ||
| 523 | .process_command = ff_filter_process_command, | ||
| 524 | }; | ||
| 525 |