|
|
|
|
Quelle chroma_probe.c
Sprache: C
|
|
/*****************************************************************************
* chroma_probe.c: chroma conversion probing
*****************************************************************************
* Copyright (C) 2025 VLC authors and VideoLAN
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_chroma_probe.h>
#include <vlc_fourcc.h>
#include <vlc_threads.h>
#include <vlc_modules.h>
#include <vlc_sort.h>
#include <vlc_memstream.h>
static int
modules_Probe(vlc_chroma_conv_vec *chroma_table)
{
module_t **mods;
ssize_t total = vlc_module_match("chroma probe", NULL, false, &mods, NULL);
if (total == -1)
return -ENOENT;
for (ssize_t i = 0; i < total; ++i)
{
vlc_chroma_conv_probe fn = vlc_module_map(NULL, mods[i]);
if (fn == NULL)
continue;
fn(chroma_table);
}
free(mods);
return 0;
}
/* Breadth First Search (BFS) node */
struct bfs_node
{
vlc_fourcc_t chain[VLC_CHROMA_CONV_CHAIN_COUNT_MAX];
unsigned depth; /* Max deep is VLC_CHROMA_CONV_CHAIN_COUNT_MAX -1 */
float cost_factor;
};
typedef struct VLC_VECTOR(struct bfs_node) bfs_queue_vec;
static int
bfs_Run(vlc_fourcc_t chroma_from, vlc_fourcc_t chroma_to, unsigned max_depth,
const vlc_chroma_conv_vec *chroma_table, int flags,
bfs_queue_vec *queue)
{
struct bfs_node start = {
.chain[0] = chroma_from,
.cost_factor = 1,
.depth = 0,
};
bool success = vlc_vector_push(queue, start);
if (!success)
return -ENOMEM;
for (size_t queue_idx = 0; queue_idx < queue->size; queue_idx++)
{
const struct bfs_node current = queue->data[queue_idx];
vlc_fourcc_t current_chroma = current.chain[current.depth];
if (chroma_to != 0 && current_chroma == chroma_to)
continue; /* Found a path to 'chroma_to' */
if (current.depth == max_depth)
continue;
/* Enqueue neighbors */
for (size_t chroma_idx = 0; chroma_idx < chroma_table->size; chroma_idx++)
{
struct vlc_chroma_conv_entry *entry = &chroma_table->data[chroma_idx];
vlc_fourcc_t from = entry->in;
vlc_fourcc_t to = entry->out;
float cost_factor = entry->cost_factor;
if (from == current_chroma)
{
vlc_fourcc_t next_chroma = to;
/* Apply filters from flags */
if (flags & VLC_CHROMA_CONV_FLAG_ONLY_YUV)
{
if (!vlc_fourcc_IsYUV(next_chroma))
continue;
}
else if (flags & VLC_CHROMA_CONV_FLAG_ONLY_RGB)
{
const vlc_chroma_description_t *desc =
vlc_fourcc_GetChromaDescription(next_chroma);
if (desc == NULL || desc->subtype != VLC_CHROMA_SUBTYPE_RGB)
continue;
}
/* If next_chroma is already in the chain at any previous step,
* we've encountered a cycle or a duplicate. */
bool already_visited = false;
for (size_t i = 0; i < current.depth; ++i)
if (current.chain[i] == next_chroma)
{
already_visited = true;
break;
}
if (already_visited)
continue;
struct bfs_node next = current;
next.depth = current.depth + 1;
next.cost_factor = current.cost_factor * cost_factor;
next.chain[next.depth] = next_chroma;
success = vlc_vector_push(queue, next);
if (!success)
return -ENOMEM;
}
}
}
return 0;
}
static uint64_t
GetChromaBits(const vlc_chroma_description_t *desc,
unsigned width, unsigned height)
{
if (desc->plane_count == 0)
{
/* Fallback to the size of the subtype */
switch (desc->subtype)
{
case VLC_CHROMA_SUBTYPE_OTHER:
return 0;
case VLC_CHROMA_SUBTYPE_YUV444:
return width * height * 3 * desc->color_bits;
case VLC_CHROMA_SUBTYPE_YUV440:
case VLC_CHROMA_SUBTYPE_YUV422:
return width * height * 2 * desc->color_bits;
case VLC_CHROMA_SUBTYPE_YUV420:
case VLC_CHROMA_SUBTYPE_YUV411:
return width * height * 1.5 * desc->color_bits;
case VLC_CHROMA_SUBTYPE_YUV410:
return width * height * 1.125 * desc->color_bits;
case VLC_CHROMA_SUBTYPE_YUV211:
case VLC_CHROMA_SUBTYPE_GREY:
return width * height * desc->color_bits;
case VLC_CHROMA_SUBTYPE_RGB:
return width * height * 4 * desc->color_bits;
default:
vlc_assert_unreachable();
}
}
uint64_t total_bits = 0;
for (unsigned i = 0; i < desc->plane_count; i++)
{
const vlc_rational_t rw = desc->p[i].w;
const vlc_rational_t rh = desc->p[i].h;
unsigned plane_width = width * rw.num / rw.den;
unsigned plane_height = height * rh.num / rh.den;
uint64_t plane_pixels = plane_width * plane_height;
uint64_t plane_bits = plane_pixels * desc->pixel_bits;
total_bits += plane_bits;
}
return total_bits;
}
static float
GetColorRatio(enum vlc_chroma_subtype subtype)
{
switch (subtype)
{
case VLC_CHROMA_SUBTYPE_YUV444:
case VLC_CHROMA_SUBTYPE_RGB:
return 1.f;
case VLC_CHROMA_SUBTYPE_YUV422:
return 0.67;
case VLC_CHROMA_SUBTYPE_YUV440:
return 0.5; /* should be like YUV422, but it is less common */
case VLC_CHROMA_SUBTYPE_YUV420:
return 0.5;
case VLC_CHROMA_SUBTYPE_YUV411:
return 0.33;
case VLC_CHROMA_SUBTYPE_YUV410:
return 0.25;
case VLC_CHROMA_SUBTYPE_YUV211:
case VLC_CHROMA_SUBTYPE_OTHER:
return 0.2;
case VLC_CHROMA_SUBTYPE_GREY:
return 0.1;
default:
vlc_assert_unreachable();
}
}
static float
CompareDescs(const vlc_chroma_description_t *in_desc,
const vlc_chroma_description_t *out_desc)
{
/* Compare color bits */
float bits_ratio;
if (in_desc->color_bits == 0 || out_desc->color_bits == 0)
bits_ratio = 1.f;
else
{
bits_ratio = out_desc->color_bits / in_desc->color_bits;
if (bits_ratio > 1.f)
bits_ratio = 1.f;
}
/* Compare color ratios, favor same or near subtype */
if (in_desc->subtype == out_desc->subtype)
return bits_ratio;
float color_ratio = GetColorRatio(out_desc->subtype)
/ GetColorRatio(in_desc->subtype);
if (color_ratio > 1.f)
color_ratio = 1.f;
/* Malus for CPU YUV <-> Other. Favor staying in the same color model. */
bool in_is_yuv = vlc_chroma_description_IsYUV(in_desc);
bool out_is_yuv = vlc_chroma_description_IsYUV(out_desc);
if ((in_desc->plane_count != 0 && out_desc->plane_count != 0)
&& (in_is_yuv || out_is_yuv) && (in_is_yuv != out_is_yuv))
color_ratio *= 0.9;
return color_ratio * bits_ratio;
}
static void
vlc_chroma_conv_result_FromNode(struct vlc_chroma_conv_result *res,
const struct bfs_node *node,
unsigned width, unsigned height)
{
res->chain_count = node->depth + 1;
res->cost = 0;
uint64_t total_cost = 0;
float total_quality = 1.f;
for (size_t i = 0; i < res->chain_count; ++i)
{
res->chain[i] = node->chain[i];
if (i > 0)
{
const vlc_chroma_description_t *from_desc =
vlc_fourcc_GetChromaDescription(res->chain[i - 1]);
const vlc_chroma_description_t *to_desc =
vlc_fourcc_GetChromaDescription(res->chain[i]);
if (from_desc == NULL || to_desc == NULL)
{
/* Unlikely, fallback for a big cost */
total_cost += width * height * 4 * 8 * node->cost_factor;
continue;
}
uint64_t from_bits = GetChromaBits(from_desc, width, height);
uint64_t to_bits = GetChromaBits(to_desc, width, height);
/* Unlikely case */
if (from_bits == 0) /* OTHER -> ANY */
from_bits = to_bits;
else if (to_bits == 0) /* ANY -> OTHER */
to_bits = from_bits;
total_cost += (from_bits + to_bits) * node->cost_factor;
float quality = CompareDescs(from_desc, to_desc);
assert(quality > 0.f && quality <= 1.f);
total_quality *= quality;
}
}
res->cost = total_cost / width / height;
res->quality = 100 * total_quality;
}
static int
SortResults(const void *a, const void *b, void *arg)
{
const struct vlc_chroma_conv_result *ra = a;
const struct vlc_chroma_conv_result *rb = b;
bool *sort_by_quality = arg;
int cost_score = 0, quality_score = 0;
/* Lower cost is better */
if (ra->cost < rb->cost)
cost_score = -1;
else if (ra->cost > rb->cost)
cost_score = 1;
/* Higher Quality is better */
if (ra->quality > rb->quality)
quality_score = -1;
else if (ra->quality < rb->quality)
quality_score = 1;
/* Fallback to secondary score in same score */
if (*sort_by_quality)
return quality_score != 0 ? quality_score : cost_score;
else
return cost_score != 0 ? cost_score : quality_score;
}
static bool
bfs_node_IsResult(const struct bfs_node *node, vlc_fourcc_t to)
{
vlc_fourcc_t current_chroma = node->chain[node->depth];
return to == 0 || current_chroma == to;
}
static bool
vlc_chroma_conv_result_Equals(struct vlc_chroma_conv_result *a,
struct vlc_chroma_conv_result *b)
{
if (a->chain_count != b->chain_count)
return false;
if (a->quality != b->quality)
return false;
/* Don't check cost since we want to merge results with different costs */
for (size_t i = 0; i < a->chain_count; ++i)
if (a->chain[i] != b->chain[i])
return false;
return true;
}
struct vlc_chroma_conv_result *
vlc_chroma_conv_Probe(vlc_fourcc_t from, vlc_fourcc_t to,
unsigned width, unsigned height,
unsigned max_indirect_steps, int flags, size_t *count)
{
assert(from != 0);
assert(max_indirect_steps <= VLC_CHROMA_CONV_MAX_INDIRECT_STEPS);
vlc_chroma_conv_vec chroma_table;
vlc_vector_init(&chroma_table);
if (width == 0 || height == 0)
{
width = 3840;
height = 2160;
}
if (max_indirect_steps > 0)
{
/* Allow indirect steps only when converting from/to a GPU chroma */
bool from_cpu = vlc_fourcc_GetChromaBPP(from) != 0;
bool to_cpu = to == 0 ? true : vlc_fourcc_GetChromaBPP(to) != 0;
if (from_cpu && to_cpu)
max_indirect_steps--;
}
/* Probe modules */
int ret = modules_Probe(&chroma_table);
if (ret != 0 || chroma_table.size == 0)
{
vlc_vector_destroy(&chroma_table);
return NULL;
}
/* Run tree search */
bfs_queue_vec bfs_queue;
vlc_vector_init(&bfs_queue);
ret = bfs_Run(from, to, max_indirect_steps + 1 , &chroma_table, flags,
&bfs_queue);
vlc_vector_destroy(&chroma_table);
size_t result_count = 0;
for (size_t i = 1 /* skip start node */; i < bfs_queue.size; ++i)
if (bfs_node_IsResult(&bfs_queue.data[i], to))
result_count++;
if (unlikely(ret != 0) || result_count == 0)
{
vlc_vector_destroy(&bfs_queue);
return NULL;
}
/* Allocate the result array */
struct VLC_VECTOR(struct vlc_chroma_conv_result) result_vec =
VLC_VECTOR_INITIALIZER;
bool success = vlc_vector_push_hole(&result_vec, result_count);
if (!success)
{
vlc_vector_destroy(&bfs_queue);
return NULL;
}
/* Fill the result from the tree search */
size_t res_idx = 0;
for (size_t i = 1 /* skip start node */; i < bfs_queue.size; ++i)
{
const struct bfs_node *node = &bfs_queue.data[i];
if (!bfs_node_IsResult(node, to))
continue;
assert(res_idx < result_count);
struct vlc_chroma_conv_result *res = &result_vec.data[res_idx++];
vlc_chroma_conv_result_FromNode(res, node, width, height);
}
assert(res_idx == result_count);
vlc_vector_destroy(&bfs_queue);
/* Sort */
bool sort_by_quality = (flags & VLC_CHROMA_CONV_FLAG_SORT_COST) == 0;
vlc_qsort(result_vec.data, result_count,
sizeof(struct vlc_chroma_conv_result), SortResults,
&sort_by_quality);
/* Remove duplicate entries, it can happen when more the 2 modules probe
* the same conversion. They are not necessarily one after the other as
* they might have different quality. */
for (size_t i = 0; i < result_vec.size - 1; ++ i)
{
struct vlc_chroma_conv_result *cur = &result_vec.data[i];
size_t j = i + 1;
while (j < result_vec.size)
{
struct vlc_chroma_conv_result *next = &result_vec.data[j];
if (vlc_chroma_conv_result_Equals(cur, next))
{
/* Keep the lowest cost */
if (next->cost < cur->cost)
cur->cost = next->cost;
vlc_vector_remove(&result_vec, j);
/* update pointer after possible realloc */
cur = &result_vec.data[i];
}
else
j++;
}
}
*count = result_vec.size;
return result_vec.data;
}
char *
vlc_chroma_conv_result_ToString(const struct vlc_chroma_conv_result *res)
{
struct vlc_memstream ms;
int ret = vlc_memstream_open(&ms);
if (ret != 0)
return NULL;
vlc_memstream_printf(&ms, "[c=%u|q=%u] ", res->cost, res->quality);
for (size_t i = 0; i < res->chain_count; ++i)
{
vlc_memstream_printf(&ms, "%4.4s", (const char *) &res->chain[i]);
if (i != res->chain_count - 1)
vlc_memstream_puts(&ms, " -> ");
}
ret = vlc_memstream_close(&ms);
return ret == 0 ? ms.ptr : NULL;
}
¤ Dauer der Verarbeitung: 0.5 Sekunden
¤
*© Formatika GbR, Deutschland
|
|
2026-11-01
|
|
|
|
|
Neuigkeiten |
| Aktuelles |
| Motto des Tages |
|
Open Source Software |
|
|
|
Jenseits des Üblichen ....
|
|
Besucherstatistik |
|
|
| Statistik |
| #Sources=1104842 |
| #Domains=499164 |
|
|