Coverage Report

Created: 2026-02-05 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/src/misc/chroma_probe.c
Line
Count
Source
1
/*****************************************************************************
2
 * chroma_probe.c: chroma conversion probing
3
 *****************************************************************************
4
 * Copyright (C) 2025 VLC authors and VideoLAN
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 2.1 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program 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
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program; if not, write to the Free Software Foundation,
18
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19
 *****************************************************************************/
20
21
#ifdef HAVE_CONFIG_H
22
# include "config.h"
23
#endif
24
25
#include <vlc_chroma_probe.h>
26
#include <vlc_fourcc.h>
27
#include <vlc_threads.h>
28
#include <vlc_modules.h>
29
#include <vlc_sort.h>
30
#include <vlc_memstream.h>
31
32
static int
33
modules_Probe(vlc_chroma_conv_vec *chroma_table)
34
0
{
35
0
    module_t **mods;
36
0
    ssize_t total = vlc_module_match("chroma probe", NULL, false, &mods, NULL);
37
0
    if (total == -1)
38
0
        return -ENOENT;
39
40
0
    for (ssize_t i = 0; i < total; ++i)
41
0
    {
42
0
        vlc_chroma_conv_probe fn = vlc_module_map(NULL, mods[i]);
43
0
        if (fn == NULL)
44
0
            continue;
45
0
        fn(chroma_table);
46
0
    }
47
0
    free(mods);
48
0
    return 0;
49
0
}
50
51
/* Breadth First Search (BFS) node */
52
struct bfs_node
53
{
54
    vlc_fourcc_t chain[VLC_CHROMA_CONV_CHAIN_COUNT_MAX];
55
    unsigned depth; /* Max deep is VLC_CHROMA_CONV_CHAIN_COUNT_MAX -1 */
56
    float cost_factor;
57
};
58
typedef struct VLC_VECTOR(struct bfs_node) bfs_queue_vec;
59
60
static int
61
bfs_Run(vlc_fourcc_t chroma_from, vlc_fourcc_t chroma_to, unsigned max_depth,
62
        const vlc_chroma_conv_vec *chroma_table, int flags,
63
        bfs_queue_vec *queue)
64
0
{
65
0
    struct bfs_node start = {
66
0
        .chain[0] = chroma_from,
67
0
        .cost_factor = 1,
68
0
        .depth = 0,
69
0
    };
70
0
    bool success = vlc_vector_push(queue, start);
71
0
    if (!success)
72
0
        return -ENOMEM;
73
74
0
    for (size_t queue_idx = 0; queue_idx < queue->size; queue_idx++)
75
0
    {
76
0
        const struct bfs_node current = queue->data[queue_idx];
77
0
        vlc_fourcc_t current_chroma = current.chain[current.depth];
78
79
0
        if (chroma_to != 0 && current_chroma == chroma_to)
80
0
            continue; /* Found a path to 'chroma_to' */
81
82
0
        if (current.depth == max_depth)
83
0
            continue;
84
85
        /* Enqueue neighbors */
86
0
        for (size_t chroma_idx = 0; chroma_idx < chroma_table->size; chroma_idx++)
87
0
        {
88
0
            struct vlc_chroma_conv_entry *entry = &chroma_table->data[chroma_idx];
89
0
            vlc_fourcc_t from = entry->in;
90
0
            vlc_fourcc_t to = entry->out;
91
0
            float cost_factor = entry->cost_factor;
92
93
0
            if (from == current_chroma)
94
0
            {
95
0
                vlc_fourcc_t next_chroma = to;
96
97
                /* Apply filters from flags */
98
0
                if (flags & VLC_CHROMA_CONV_FLAG_ONLY_YUV)
99
0
                {
100
0
                    if (!vlc_fourcc_IsYUV(next_chroma))
101
0
                        continue;
102
0
                }
103
0
                else if (flags & VLC_CHROMA_CONV_FLAG_ONLY_RGB)
104
0
                {
105
0
                    const vlc_chroma_description_t *desc =
106
0
                        vlc_fourcc_GetChromaDescription(next_chroma);
107
0
                    if (desc == NULL || desc->subtype != VLC_CHROMA_SUBTYPE_RGB)
108
0
                        continue;
109
0
                }
110
111
                /* If next_chroma is already in the chain at any previous step,
112
                 * we've encountered a cycle or a duplicate. */
113
0
                bool already_visited = false;
114
0
                for (size_t i = 0; i < current.depth; ++i)
115
0
                    if (current.chain[i] == next_chroma)
116
0
                    {
117
0
                        already_visited = true;
118
0
                        break;
119
0
                    }
120
0
                if (already_visited)
121
0
                    continue;
122
123
0
                struct bfs_node next = current;
124
0
                next.depth = current.depth + 1;
125
0
                next.cost_factor = current.cost_factor * cost_factor;
126
127
0
                next.chain[next.depth] = next_chroma;
128
0
                success = vlc_vector_push(queue, next);
129
0
                if (!success)
130
0
                    return -ENOMEM;
131
0
            }
132
0
        }
133
0
    }
134
0
    return 0;
135
0
}
136
137
static uint64_t
138
GetChromaBits(const vlc_chroma_description_t *desc,
139
              unsigned width, unsigned height)
140
0
{
141
0
    if (desc->plane_count == 0)
142
0
    {
143
        /* Fallback to the size of the subtype */
144
0
        switch (desc->subtype)
145
0
        {
146
0
            case VLC_CHROMA_SUBTYPE_OTHER:
147
0
                return 0;
148
0
            case VLC_CHROMA_SUBTYPE_YUV444:
149
0
                return width * height * 3 * desc->color_bits;
150
0
            case VLC_CHROMA_SUBTYPE_YUV440:
151
0
            case VLC_CHROMA_SUBTYPE_YUV422:
152
0
                return width * height * 2 * desc->color_bits;
153
0
            case VLC_CHROMA_SUBTYPE_YUV420:
154
0
            case VLC_CHROMA_SUBTYPE_YUV411:
155
0
                return width * height * 1.5 * desc->color_bits;
156
0
            case VLC_CHROMA_SUBTYPE_YUV410:
157
0
                return width * height * 1.125 * desc->color_bits;
158
0
            case VLC_CHROMA_SUBTYPE_YUV211:
159
0
            case VLC_CHROMA_SUBTYPE_GREY:
160
0
                return width * height * desc->color_bits;
161
0
            case VLC_CHROMA_SUBTYPE_RGB:
162
0
                return width * height * 4 * desc->color_bits;
163
0
            default:
164
0
                vlc_assert_unreachable();
165
0
        }
166
0
    }
167
168
0
    uint64_t total_bits = 0;
169
0
    for (unsigned i = 0; i < desc->plane_count; i++)
170
0
    {
171
0
        const vlc_rational_t rw = desc->p[i].w;
172
0
        const vlc_rational_t rh = desc->p[i].h;
173
174
0
        unsigned plane_width = width * rw.num / rw.den;
175
0
        unsigned plane_height = height * rh.num / rh.den;
176
177
0
        uint64_t plane_pixels = plane_width * plane_height;
178
0
        uint64_t plane_bits = plane_pixels * desc->pixel_bits;
179
180
0
        total_bits += plane_bits;
181
0
    }
182
183
0
    return total_bits;
184
0
}
185
186
static float
187
GetColorRatio(enum vlc_chroma_subtype subtype)
188
0
{
189
0
    switch (subtype)
190
0
    {
191
0
        case VLC_CHROMA_SUBTYPE_YUV444:
192
0
        case VLC_CHROMA_SUBTYPE_RGB:
193
0
            return 1.f;
194
0
        case VLC_CHROMA_SUBTYPE_YUV422:
195
0
            return 0.67;
196
0
        case VLC_CHROMA_SUBTYPE_YUV440:
197
0
            return 0.5; /* should be like YUV422, but it is less common */
198
0
        case VLC_CHROMA_SUBTYPE_YUV420:
199
0
            return 0.5;
200
0
        case VLC_CHROMA_SUBTYPE_YUV411:
201
0
            return 0.33;
202
0
        case VLC_CHROMA_SUBTYPE_YUV410:
203
0
            return 0.25;
204
0
        case VLC_CHROMA_SUBTYPE_YUV211:
205
0
        case VLC_CHROMA_SUBTYPE_OTHER:
206
0
            return 0.2;
207
0
        case VLC_CHROMA_SUBTYPE_GREY:
208
0
            return 0.1;
209
0
        default:
210
0
            vlc_assert_unreachable();
211
0
    }
212
0
}
213
214
static float
215
CompareDescs(const vlc_chroma_description_t *in_desc,
216
             const vlc_chroma_description_t *out_desc)
217
0
{
218
    /* Compare color bits */
219
0
    float bits_ratio;
220
0
    if (in_desc->color_bits == 0 || out_desc->color_bits == 0)
221
0
        bits_ratio = 1.f;
222
0
    else
223
0
    {
224
0
        bits_ratio = out_desc->color_bits / in_desc->color_bits;
225
0
        if (bits_ratio > 1.f)
226
0
            bits_ratio = 1.f;
227
0
    }
228
229
    /* Compare color ratios, favor same or near subtype */
230
0
    if (in_desc->subtype == out_desc->subtype)
231
0
        return bits_ratio;
232
233
0
    float color_ratio = GetColorRatio(out_desc->subtype)
234
0
                      / GetColorRatio(in_desc->subtype);
235
0
    if (color_ratio > 1.f)
236
0
        color_ratio = 1.f;
237
238
    /* Malus for CPU YUV <-> Other. Favor staying in the same color model. */
239
0
    bool in_is_yuv = vlc_chroma_description_IsYUV(in_desc);
240
0
    bool out_is_yuv = vlc_chroma_description_IsYUV(out_desc);
241
0
    if ((in_desc->plane_count != 0 && out_desc->plane_count != 0)
242
0
     && (in_is_yuv || out_is_yuv) && (in_is_yuv != out_is_yuv))
243
0
        color_ratio *= 0.9;
244
245
0
    return color_ratio * bits_ratio;
246
0
}
247
248
static void
249
vlc_chroma_conv_result_FromNode(struct vlc_chroma_conv_result *res,
250
                                const struct bfs_node *node,
251
                                unsigned width, unsigned height)
252
0
{
253
0
    res->chain_count = node->depth + 1;
254
0
    res->cost = 0;
255
256
0
    uint64_t total_cost = 0;
257
0
    float total_quality = 1.f;
258
0
    for (size_t i = 0; i < res->chain_count; ++i)
259
0
    {
260
0
        res->chain[i] = node->chain[i];
261
262
0
        if (i > 0)
263
0
        {
264
0
            const vlc_chroma_description_t *from_desc =
265
0
                vlc_fourcc_GetChromaDescription(res->chain[i - 1]);
266
0
            const vlc_chroma_description_t *to_desc =
267
0
                vlc_fourcc_GetChromaDescription(res->chain[i]);
268
269
0
            if (from_desc == NULL || to_desc == NULL)
270
0
            {
271
                /* Unlikely, fallback for a big cost */
272
0
                total_cost += width * height * 4 * 8 * node->cost_factor;
273
0
                continue;
274
0
            }
275
276
0
            uint64_t from_bits = GetChromaBits(from_desc, width, height);
277
0
            uint64_t to_bits = GetChromaBits(to_desc, width, height);
278
279
            /* Unlikely case */
280
0
            if (from_bits == 0) /* OTHER -> ANY */
281
0
                from_bits = to_bits;
282
0
            else if (to_bits == 0) /* ANY -> OTHER */
283
0
                to_bits = from_bits;
284
285
0
            total_cost += (from_bits + to_bits) * node->cost_factor;
286
287
0
            float quality = CompareDescs(from_desc, to_desc);
288
0
            assert(quality > 0.f && quality <= 1.f);
289
290
0
            total_quality *= quality;
291
0
        }
292
0
    }
293
0
    res->cost = total_cost / width / height;
294
0
    res->quality = 100 * total_quality;
295
0
}
296
297
static int
298
SortResults(const void *a, const void *b, void *arg)
299
0
{
300
0
    const struct vlc_chroma_conv_result *ra = a;
301
0
    const struct vlc_chroma_conv_result *rb = b;
302
0
    bool *sort_by_quality = arg;
303
304
0
    int cost_score = 0, quality_score = 0;
305
306
    /* Lower cost is better */
307
0
    if (ra->cost < rb->cost)
308
0
        cost_score = -1;
309
0
    else if (ra->cost > rb->cost)
310
0
        cost_score = 1;
311
312
    /* Higher Quality is better */
313
0
    if (ra->quality > rb->quality)
314
0
        quality_score = -1;
315
0
    else if (ra->quality < rb->quality)
316
0
        quality_score = 1;
317
318
    /* Fallback to secondary score in same score */
319
0
    if (*sort_by_quality)
320
0
        return quality_score != 0 ? quality_score : cost_score;
321
0
    else
322
0
        return cost_score != 0 ? cost_score : quality_score;
323
0
}
324
325
static bool
326
bfs_node_IsResult(const struct bfs_node *node, vlc_fourcc_t to)
327
0
{
328
0
    vlc_fourcc_t current_chroma = node->chain[node->depth];
329
0
    return to == 0 || current_chroma == to;
330
0
}
331
332
static bool
333
vlc_chroma_conv_result_Equals(struct vlc_chroma_conv_result *a,
334
                              struct vlc_chroma_conv_result *b)
335
0
{
336
0
    if (a->chain_count != b->chain_count)
337
0
        return false;
338
0
    if (a->quality != b->quality)
339
0
        return false;
340
    /* Don't check cost since we want to merge results with different costs */
341
0
    for (size_t i = 0; i < a->chain_count; ++i)
342
0
        if (a->chain[i] != b->chain[i])
343
0
            return false;
344
0
    return true;
345
0
}
346
347
struct vlc_chroma_conv_result *
348
vlc_chroma_conv_Probe(vlc_fourcc_t from, vlc_fourcc_t to,
349
                      unsigned width, unsigned height,
350
                      unsigned max_indirect_steps, int flags, size_t *count)
351
0
{
352
0
    assert(from != 0);
353
0
    assert(max_indirect_steps <= VLC_CHROMA_CONV_MAX_INDIRECT_STEPS);
354
0
    vlc_chroma_conv_vec chroma_table;
355
0
    vlc_vector_init(&chroma_table);
356
357
0
    if (width == 0 || height == 0)
358
0
    {
359
0
        width = 3840;
360
0
        height = 2160;
361
0
    }
362
363
0
    if (max_indirect_steps > 0)
364
0
    {
365
        /* Allow indirect steps only when converting from/to a GPU chroma */
366
0
        bool from_cpu = vlc_fourcc_GetChromaBPP(from) != 0;
367
0
        bool to_cpu = to == 0 ? true : vlc_fourcc_GetChromaBPP(to) != 0;
368
0
        if (from_cpu && to_cpu)
369
0
            max_indirect_steps--;
370
0
    }
371
372
    /* Probe modules */
373
0
    int ret = modules_Probe(&chroma_table);
374
0
    if (ret != 0 || chroma_table.size == 0)
375
0
    {
376
0
        vlc_vector_destroy(&chroma_table);
377
0
        return NULL;
378
0
    }
379
380
    /* Run tree search */
381
0
    bfs_queue_vec bfs_queue;
382
0
    vlc_vector_init(&bfs_queue);
383
0
    ret = bfs_Run(from, to, max_indirect_steps + 1 , &chroma_table, flags,
384
0
                  &bfs_queue);
385
386
0
    vlc_vector_destroy(&chroma_table);
387
388
0
    size_t result_count = 0;
389
0
    for (size_t i = 1 /* skip start node */; i < bfs_queue.size; ++i)
390
0
        if (bfs_node_IsResult(&bfs_queue.data[i], to))
391
0
            result_count++;
392
393
0
    if (unlikely(ret != 0) || result_count == 0)
394
0
    {
395
0
        vlc_vector_destroy(&bfs_queue);
396
0
        return NULL;
397
0
    }
398
399
    /* Allocate the result array */
400
0
    struct VLC_VECTOR(struct vlc_chroma_conv_result) result_vec =
401
0
        VLC_VECTOR_INITIALIZER;
402
0
    bool success = vlc_vector_push_hole(&result_vec, result_count);
403
0
    if (!success)
404
0
    {
405
0
        vlc_vector_destroy(&bfs_queue);
406
0
        return NULL;
407
0
    }
408
409
    /* Fill the result from the tree search */
410
0
    size_t res_idx = 0;
411
0
    for (size_t i = 1 /* skip start node */; i < bfs_queue.size; ++i)
412
0
    {
413
0
        const struct bfs_node *node = &bfs_queue.data[i];
414
0
        if (!bfs_node_IsResult(node, to))
415
0
            continue;
416
417
0
        assert(res_idx < result_count);
418
0
        struct vlc_chroma_conv_result *res = &result_vec.data[res_idx++];
419
0
        vlc_chroma_conv_result_FromNode(res, node, width, height);
420
0
    }
421
0
    assert(res_idx == result_count);
422
423
0
    vlc_vector_destroy(&bfs_queue);
424
425
    /* Sort */
426
0
    bool sort_by_quality = (flags & VLC_CHROMA_CONV_FLAG_SORT_COST) == 0;
427
0
    vlc_qsort(result_vec.data, result_count,
428
0
              sizeof(struct vlc_chroma_conv_result), SortResults,
429
0
              &sort_by_quality);
430
431
    /* Remove duplicate entries, it can happen when more the 2 modules probe
432
     * the same conversion. They are not necessarily one after the other as
433
     * they might have different quality. */
434
0
    for (size_t i = 0; i < result_vec.size - 1; ++ i)
435
0
    {
436
0
        struct vlc_chroma_conv_result *cur = &result_vec.data[i];
437
438
0
        size_t j = i + 1;
439
0
        while (j < result_vec.size)
440
0
        {
441
0
            struct vlc_chroma_conv_result *next = &result_vec.data[j];
442
0
            if (vlc_chroma_conv_result_Equals(cur, next))
443
0
            {
444
                /* Keep the lowest cost */
445
0
                if (next->cost < cur->cost)
446
0
                    cur->cost = next->cost;
447
0
                vlc_vector_remove(&result_vec, j);
448
                /* update pointer after possible realloc */
449
0
                cur = &result_vec.data[i];
450
0
            }
451
0
            else
452
0
                j++;
453
0
        }
454
0
    }
455
456
0
    *count = result_vec.size;
457
0
    return result_vec.data;
458
0
}
459
460
char *
461
vlc_chroma_conv_result_ToString(const struct vlc_chroma_conv_result *res)
462
0
{
463
0
    struct vlc_memstream ms;
464
0
    int ret = vlc_memstream_open(&ms);
465
0
    if (ret != 0)
466
0
        return NULL;
467
0
    vlc_memstream_printf(&ms, "[c=%u|q=%u] ", res->cost, res->quality);
468
469
0
    for (size_t i = 0; i < res->chain_count; ++i)
470
0
    {
471
0
        vlc_memstream_printf(&ms, "%4.4s", (const char *) &res->chain[i]);
472
0
        if (i != res->chain_count - 1)
473
0
            vlc_memstream_puts(&ms, " -> ");
474
0
    }
475
0
    ret = vlc_memstream_close(&ms);
476
0
    return ret == 0 ? ms.ptr : NULL;
477
0
}