Browse Source

core: add chroma_probe API

The module API allows all "video converter" modules to list their chroma
conversions.

The user API allows to get a full list of all possibles chromas
conversion from an input chroma.
pull/177/head
Thomas Guillem 1 year ago
parent
commit
2b898bb5b1
  1. 1
      include/meson.build
  2. 259
      include/vlc_chroma_probe.h
  3. 2
      src/Makefile.am
  4. 2
      src/libvlccore.sym
  5. 1
      src/meson.build
  6. 475
      src/misc/chroma_probe.c
  7. 4
      test/Makefile.am
  8. 379
      test/src/misc/chroma_probe.c

1
include/meson.build

@ -31,6 +31,7 @@ install_headers(
'vlc_block_helper.h',
'vlc_boxes.h',
'vlc_charset.h',
'vlc_chroma_probe.h',
'vlc_codec.h',
'vlc_codecs.h',
'vlc_common.h',

259
include/vlc_chroma_probe.h

@ -0,0 +1,259 @@
/*****************************************************************************
* vlc_chroma_probe.h: 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.
*****************************************************************************/
#ifndef VLC_CHROMA_PROBE_H
#define VLC_CHROMA_PROBE_H 1
#include <vlc_common.h>
#include <vlc_vector.h>
/**
* \defgroup chroma_probe Chroma conversion probing
* \ingroup filter
* @{
* \file
* Chroma conversion probing
*
* \defgroup chroma_probe_api Chroma probing API
* \ingroup chroma_probe
*
* @{
*/
#define VLC_CHROMA_CONV_MAX_INDIRECT_STEPS 1
#define VLC_CHROMA_CONV_CHAIN_COUNT_MAX (2 /* in + out */ + VLC_CHROMA_CONV_MAX_INDIRECT_STEPS)
/**
* Chroma conversion result structure
*/
struct vlc_chroma_conv_result
{
/**
* Array of chromas used to achieve the conversion
*
* 'chain[0]' is always equals to the 'in' argument of the
* vlc_chroma_conv_Probe() function.
*
* if the out argument of the vlc_chroma_conv_Probe() is valid,
* chain[chain_count - 1] is equals to 'out'
*/
vlc_fourcc_t chain[VLC_CHROMA_CONV_CHAIN_COUNT_MAX];
/** Number of chromas in the chain */
size_t chain_count;
/**
* Cost of the full conversion, lower is better.
*/
unsigned cost;
/**
* Quality of the conversion, higher is better.
*
* A quality of 100 means there are no quality loss: same color size and
* same vlc_chroma_subtype (or same YUV subsampling for video).
*/
unsigned quality;
};
/** Only accept YUV output chromas (the input chroma can be RGB) */
#define VLC_CHROMA_CONV_FLAG_ONLY_YUV 0x1
/** Only accept RGB output chromas (the input chroma can be YUV) */
#define VLC_CHROMA_CONV_FLAG_ONLY_RGB 0x2
/** Sort results by cost instead of quality */
#define VLC_CHROMA_CONV_FLAG_SORT_COST 0x4
/**
* Probe possible chroma conversions
* Results are sorted by quality, unless VLC_CHROMA_CONV_FLAG_SORT_COST is
* specified in flags.
* @param in the input chroma to convert from, must be valid
* @param out the output chroma to convert to, if 0, the function will find all
* possible conversion from in to x
* @param width video width, used for finer cost calculation, can be 0
* @param height video height, used for finer cost calculation, can be 0
* @param max_indirect_steps maximum number of indirect conversion steps, must
* be lower or equal to @ref VLC_CHROMA_CONV_MAX_INDIRECT_STEPS, if in and out
* chromas are CPU chromas, the steps will be automatically lowered to 0
* @param flags bitwise flags, cf. VLC_CHROMA_CONV_FLAG_*
* @param count pointer to the number of results, must be valid
* @return a pointer to an array of results, must be released with free(), can
* be NULL
*/
VLC_API struct vlc_chroma_conv_result *
vlc_chroma_conv_Probe(vlc_fourcc_t in, vlc_fourcc_t out,
unsigned width, unsigned height,
unsigned max_indirect_steps, int flags, size_t *count);
/**
* Get a string representing the result
*
* @param res pointer to a valid result
* @return a string or NULL, must be released with free()
*/
VLC_API char *
vlc_chroma_conv_result_ToString(const struct vlc_chroma_conv_result *res);
/**
* @}
*
* \defgroup chroma_probe_module Chroma probing module implementation
* \ingroup chroma_probe
*
* @{
*/
/**
* Chroma conversion entry structure
*/
struct vlc_chroma_conv_entry
{
/** Cost factor, 0.25 for GPU<->GPU conversions, 0.75 for SIMD, 1 for CPU */
float cost_factor;
/** input chroma */
vlc_fourcc_t in;
/** output chroma */
vlc_fourcc_t out;
};
typedef struct VLC_VECTOR(struct vlc_chroma_conv_entry) vlc_chroma_conv_vec;
/**
* Module probe function signature
*
* @param vec pointer to an allocated vector
* @return a VLC error code
*/
typedef void (*vlc_chroma_conv_probe)(vlc_chroma_conv_vec *vec);
#define set_callback_chroma_conv_probe(activate) \
{ \
vlc_chroma_conv_probe activate__ = activate; \
(void) activate__; \
set_callback(activate) \
} \
set_capability("chroma probe", 100)
/**
* Helper that add a chroma conversion
*
* Must be called inside vlc_chroma_conv_probe()
*
* @param vec pointer to the vector of chromas
* @param cost_factor cf. vlc_chroma_conv_entry.cost_factor
* @param in cf. vlc_chroma_conv_entry.in
* @param out cf. vlc_chroma_conv_entry.out
* @param twoway if true, 'out' can also be converted to 'in'
*/
static inline void
vlc_chroma_conv_add(vlc_chroma_conv_vec *vec, float cost_factor,
vlc_fourcc_t in, vlc_fourcc_t out, bool twoway)
{
{
const struct vlc_chroma_conv_entry entry = {
cost_factor, in, out
};
vlc_vector_push(vec, entry);
}
if (twoway)
{
const struct vlc_chroma_conv_entry entry = {
cost_factor, out, in
};
vlc_vector_push(vec, entry);
}
}
/**
* Helper that add an array of out chroma conversions
*
* Must be called inside vlc_chroma_conv_probe()
*
* @param vec pointer to the vector of chromas
* @param cost_factor cf. vlc_chroma_conv_entry.cost_factor
* @param in cf. vlc_chroma_conv_entry.in
* @param out_array a list of out chromas
* @param out_count number of elements in the out_array
*/
static inline void
vlc_chroma_conv_add_in_outarray(vlc_chroma_conv_vec *vec, float cost_factor,
vlc_fourcc_t in,
const vlc_fourcc_t *out_array, size_t out_count)
{
for (size_t i = 0; i < out_count; i++)
{
const struct vlc_chroma_conv_entry entry = {
cost_factor, in, out_array[i],
};
vlc_vector_push(vec, entry);
}
}
/**
* Helper that add a list of out chroma conversions
*/
#define vlc_chroma_conv_add_in_outlist(vec, cost_factor, in, ...) do { \
static const vlc_fourcc_t out_array[] = { __VA_ARGS__ }; \
size_t count = ARRAY_SIZE(out_array); \
vlc_chroma_conv_add_in_outarray(vec, cost_factor, in, out_array, count); \
} while(0)
/**
* Helper that add an array of in chroma conversions
*
* Must be called inside vlc_chroma_conv_probe()
*
* @param vec pointer to the vector of chromas
* @param cost_factor cf. vlc_chroma_conv_entry.cost_factor
* @param out cf. vlc_chroma_conv_entry.out
* @param in_array a list of out chromas
* @param in_count number of elements in the in_array
*/
static inline void
vlc_chroma_conv_add_out_inarray(vlc_chroma_conv_vec *vec, float cost_factor,
vlc_fourcc_t out,
const vlc_fourcc_t *in_array, size_t in_count)
{
for (size_t i = 0; i < in_count; i++)
{
const struct vlc_chroma_conv_entry entry = {
cost_factor, in_array[i], out,
};
vlc_vector_push(vec, entry);
}
}
/**
* Helper that add a list of in chroma conversions
*/
#define vlc_chroma_conv_add_out_inlist(vec, cost_factor, out, ...) do { \
static const vlc_fourcc_t in_array[] = { __VA_ARGS__ }; \
size_t count = ARRAY_SIZE(in_array); \
vlc_chroma_conv_add_out_inarray(vec, cost_factor, out, in_array, count); \
} while(0)
/**
* @}
* @}
*/
#endif /* VLC_CHROMA_PROBE_H */

2
src/Makefile.am

@ -43,6 +43,7 @@ pluginsinclude_HEADERS.h = \
../include/vlc_configuration.h \
../include/vlc_cpu.h \
../include/vlc_clock.h \
../include/vlc_chroma_probe.h \
../include/vlc_decoder.h \
../include/vlc_demux.h \
../include/vlc_dialog.h \
@ -380,6 +381,7 @@ libvlccore_la_SOURCES = \
misc/actions.c \
misc/ancillary.h \
misc/ancillary.c \
misc/chroma_probe.c \
misc/executor.c \
misc/md5.c \
misc/probe.c \

2
src/libvlccore.sym

@ -58,6 +58,8 @@ vlc_frame_shm_Alloc
vlc_frame_Realloc
vlc_frame_Release
vlc_frame_TryRealloc
vlc_chroma_conv_Probe
vlc_chroma_conv_result_ToString
config_AddIntf
config_ChainCreate
config_ChainDestroy

1
src/meson.build

@ -231,6 +231,7 @@ libvlccore_sources_base = files(
'text/iso-639_def.h',
'misc/actions.c',
'misc/ancillary.c',
'misc/chroma_probe.c',
'misc/executor.c',
'misc/md5.c',
'misc/probe.c',

475
src/misc/chroma_probe.c

@ -0,0 +1,475 @@
/*****************************************************************************
* 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);
}
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;
}

4
test/Makefile.am

@ -37,6 +37,7 @@ check_PROGRAMS = \
test_src_interface_dialog \
test_src_media_source \
test_src_misc_bits \
test_src_misc_chroma_probe \
test_src_misc_epg \
test_src_misc_keystore \
test_src_misc_image \
@ -219,6 +220,9 @@ test_src_input_decoder_SOURCES = \
src/input/decoder/input_decoder_scenarios.c
test_src_input_decoder_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_src_misc_chroma_probe_SOURCES = src/misc/chroma_probe.c
test_src_misc_chroma_probe_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_src_misc_image_SOURCES = src/misc/image.c
test_src_misc_image_LDADD = $(LIBVLCCORE) $(LIBVLC)

379
test/src/misc/chroma_probe.c

@ -0,0 +1,379 @@
/*****************************************************************************
* chroma_probe.c: test for chroma_probe
*****************************************************************************
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU 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
/* Define a builtin module for mocked parts */
#define MODULE_NAME test_chroma_probe
#undef VLC_DYNAMIC_PLUGIN
#include "../../libvlc/test.h"
#include <vlc/vlc.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_chroma_probe.h>
#include <vlc_fourcc.h>
#include <assert.h>
const char vlc_module_name[] = MODULE_STRING;
#define RESULT_MAX 6
struct scenario_result
{
unsigned cost;
unsigned quality;
vlc_fourcc_t chain[VLC_CHROMA_CONV_CHAIN_COUNT_MAX - 1 /* exclude 'from' */];
};
struct scenario
{
unsigned max_indirect_steps;
int flags;
vlc_fourcc_t in;
vlc_fourcc_t out;
struct scenario_result results[RESULT_MAX];
size_t result_count;
};
static const struct scenario scenario_array[] =
{
#define COST VLC_CHROMA_CONV_FLAG_SORT_COST
#define ONLY_YUV VLC_CHROMA_CONV_FLAG_ONLY_YUV
#define ONLY_RGB VLC_CHROMA_CONV_FLAG_ONLY_RGB
#define RESULT(cost, quality, chain0, chain1 ) \
{ cost, quality, { chain0, chain1 } }
#define SCENARIO0(max_indirect_steps_, from, to) { \
.max_indirect_steps = max_indirect_steps_, \
.flags = 0, \
.in = from, .out = to, \
.result_count = 0, \
}
#define SCENARIO1(max_indirect_steps_, sort_, from, to, cost, quality, chain0) { \
.max_indirect_steps = max_indirect_steps_, \
.flags = sort_, \
.in = from, .out = to, \
.results = { RESULT(cost, quality, chain0, 0) }, \
.result_count = 1, \
}
#define SCENARIO2(max_indirect_steps_, sort_, from, to, \
result0_cost, result0_quality, result0_chain0, \
result1_cost, result1_quality, result1_chain0) { \
.max_indirect_steps = max_indirect_steps_, \
.flags = sort_, \
.in = from, .out = to, \
.results = { RESULT(result0_cost, result0_quality, result0_chain0, 0), \
RESULT(result1_cost, result1_quality, result1_chain0, 0), }, \
.result_count = 2, \
}
#define SCENARIOX(max_indirect_steps_, sort_, from, to, count, ...) { \
.max_indirect_steps = max_indirect_steps_, \
.flags = sort_, \
.in = from, .out = to, \
.results = { __VA_ARGS__ }, \
.result_count = count, \
}
/* Success with a depth of 0 (Direct conversion) */
SCENARIO1(0, 0, VLC_CODEC_VAAPI_420, VLC_CODEC_I420, 26, 100, 0),
/* Success with a depth of 1 */
SCENARIO1(1, 0, VLC_CODEC_VAAPI_420_10BPP, VLC_CODEC_I420,
47, 80, VLC_CODEC_P010),
/* Fail because it require a depth of 1 */
SCENARIO0(0, VLC_CODEC_VAAPI_420_10BPP, VLC_CODEC_I420),
/* Check duplicated entries are removed and that we keep the lowest cost */
SCENARIO1(1, 0, VLC_CODEC_NV12, VLC_CODEC_I420,
18, 100, 0),
/* Check two_way is doing as expected */
SCENARIO1(1, 0, VLC_CODEC_I420, VLC_CODEC_VAAPI_420_10BPP,
47, 100, VLC_CODEC_P010),
/* Fail because it requires a depth of 2 */
SCENARIO0(1, VLC_CODEC_CVPX_P010, VLC_CODEC_P010),
/* Fail because conversion is not two-way */
SCENARIO0(1, VLC_CODEC_P010, VLC_CODEC_CVPX_P010),
/* Check low cost of GPU <-> GPU */
SCENARIO1(1, 0, VLC_CODEC_CVPX_P010, VLC_CODEC_CVPX_BGRA, 11, 80, 0),
/* Check cost and quality of direct conversion */
SCENARIO1(0, 0, VLC_CODEC_YUVA_444_12L, VLC_CODEC_I420, 60, 33, 0),
/* Check 1 depth conversions are correctly sorted */
SCENARIOX(1, 0, VLC_CODEC_VAAPI_420_10BPP, 0, 6,
RESULT(33, 100, VLC_CODEC_P010, 0),
RESULT(33, 100, VLC_CODEC_I420_10L, 0),
RESULT(66, 100, VLC_CODEC_P010, VLC_CODEC_I420_10L),
RESULT(66, 100, VLC_CODEC_I420_10L, VLC_CODEC_P010),
RESULT(47, 80, VLC_CODEC_P010, VLC_CODEC_I420),
RESULT(84, 72, VLC_CODEC_P010, VLC_CODEC_RGBA)),
/* Check default QUALITY order */
SCENARIOX(0, 0, VLC_CODEC_YUVA_444_12L, 0, 4,
RESULT(112, 90, VLC_CODEC_RGBA64, 0),
RESULT(88, 83, VLC_CODEC_YUVA_444_10L, 0),
RESULT(80, 60, VLC_CODEC_RGBA, 0),
RESULT(60, 33, VLC_CODEC_I420, 0)),
/* Check ONLY_YUV */
SCENARIOX(0, ONLY_YUV, VLC_CODEC_YUVA_444_12L, 0, 2,
RESULT(88, 83, VLC_CODEC_YUVA_444_10L, 0),
RESULT(60, 33, VLC_CODEC_I420, 0)),
/* Check ONLY_RGB */
SCENARIOX(0, ONLY_RGB, VLC_CODEC_YUVA_444_12L, 0, 2,
RESULT(112, 90, VLC_CODEC_RGBA64, 0),
RESULT(80, 60, VLC_CODEC_RGBA, 0)),
/* Check COST order */
SCENARIOX(0, COST, VLC_CODEC_YUVA_444_12L, 0, 4,
RESULT(60, 33, VLC_CODEC_I420, 0),
RESULT(80, 60, VLC_CODEC_RGBA, 0),
RESULT(88, 83, VLC_CODEC_YUVA_444_10L, 0),
RESULT(112, 90, VLC_CODEC_RGBA64, 0)),
/* Check VLC_CHROMA_CONV_ADD_IN_OUTTLIST, and quality order with smaller
* RGB chromas */
SCENARIOX(0, 0, VLC_CODEC_YV12, 0, 5,
RESULT(36, 90, VLC_CODEC_XRGB, 0),
RESULT(28, 59, VLC_CODEC_RGB565, 0),
RESULT(28, 59, VLC_CODEC_BGR565, 0),
RESULT(27, 56, VLC_CODEC_RGB555, 0),
RESULT(27, 56, VLC_CODEC_BGR555, 0)),
/* Check VLC_CHROMA_CONV_ADD_OUT_INLIST */
SCENARIO1(0, 0, VLC_CODEC_NV16, VLC_CODEC_I422, 32, 100, 0),
SCENARIO1(0, 0, VLC_CODEC_YUYV, VLC_CODEC_I422, 32, 100, 0),
SCENARIO1(0, 0, VLC_CODEC_UYVY, VLC_CODEC_I422, 32, 100, 0),
/* Check VLC_CHROMA_CONV_ADD_ALL */
SCENARIO1(1, 0, VLC_CODEC_I444_12L, VLC_CODEC_I444, 60, 66, 0),
SCENARIO1(1, 0, VLC_CODEC_I444, VLC_CODEC_I444_12L, 60, 100, 0),
SCENARIO1(1, 0, VLC_CODEC_I444_12L, VLC_CODEC_I444_10L, 66, 83, 0),
SCENARIO1(1, 0, VLC_CODEC_I444_10L, VLC_CODEC_I444_12L, 66, 100, 0),
};
static void ProbeChroma(vlc_chroma_conv_vec *vec)
{
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_VAAPI_420, VLC_CODEC_I420, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_VAAPI_420_10BPP, VLC_CODEC_P010, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_VAAPI_420_10BPP, VLC_CODEC_I420_10L, true);
vlc_chroma_conv_add(vec, 0.75, VLC_CODEC_I420, VLC_CODEC_NV12, true);
vlc_chroma_conv_add(vec, 0.75, VLC_CODEC_I420, VLC_CODEC_P010, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_I420_10L, VLC_CODEC_P010, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_RGBA, VLC_CODEC_P010, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_RGBA, VLC_CODEC_NV12, true);
/* Test duplicated entries are removed */
vlc_chroma_conv_add(vec, 1.0, VLC_CODEC_I420, VLC_CODEC_NV12, true);
/* Don't change this order as this is used to test to cost sort
* (we don't want the result to be naturally sorted) */
vlc_chroma_conv_add(vec, 1, VLC_CODEC_YUVA_444_12L, VLC_CODEC_RGBA, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_YUVA_444_12L, VLC_CODEC_I420, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_YUVA_444_12L, VLC_CODEC_YUVA_444_10L, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_YUVA_444_12L, VLC_CODEC_RGBA64, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_NV12, VLC_CODEC_CVPX_BGRA, false);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_NV12, VLC_CODEC_NV12, false);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_P010, VLC_CODEC_CVPX_BGRA, false);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_BGRA, VLC_CODEC_RGBA, false);
vlc_chroma_conv_add_in_outlist(vec, 1, VLC_CODEC_YV12, VLC_CODEC_XRGB,
VLC_CODEC_RGB565, VLC_CODEC_BGR565,
VLC_CODEC_RGB555, VLC_CODEC_BGR555);
vlc_chroma_conv_add_out_inlist(vec, 1, VLC_CODEC_I422, VLC_CODEC_NV16,
VLC_CODEC_YUYV, VLC_CODEC_UYVY);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_I444, VLC_CODEC_I444_10L, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_I444, VLC_CODEC_I444_12L, true);
vlc_chroma_conv_add(vec, 1, VLC_CODEC_I444_10L, VLC_CODEC_I444_12L, true);
/* Test duplicated entries are removed */
vlc_chroma_conv_add(vec, 1.0, VLC_CODEC_I420, VLC_CODEC_NV12, true);
}
vlc_module_begin()
set_callback_chroma_conv_probe(ProbeChroma)
vlc_module_end()
VLC_EXPORT vlc_plugin_cb vlc_static_modules[] = {
VLC_SYMBOL(vlc_entry),
NULL
};
static void
print_results(const struct vlc_chroma_conv_result *array, size_t count)
{
for (size_t i = 0; i < count; ++i)
{
const struct vlc_chroma_conv_result *res = &array[i];
char *res_str = vlc_chroma_conv_result_ToString(res);
assert(res_str != NULL);
fprintf(stderr, "\tres[%zu]: %s\n", i, res_str);
free(res_str);
}
}
static void
check_results(const struct scenario *scr,
const struct vlc_chroma_conv_result *results)
{
for (size_t i = 0; i < scr->result_count; ++i)
{
const struct vlc_chroma_conv_result *result = &results[i];
const struct scenario_result *scr_result = &scr->results[i];
assert(result->chain_count > 1);
assert(result->chain_count <= VLC_CHROMA_CONV_CHAIN_COUNT_MAX);
/* Reconstruct the expected fourcc array from the scenario */
vlc_fourcc_t scr_chain[VLC_CHROMA_CONV_CHAIN_COUNT_MAX];
bool end_reached = false;
size_t scr_count = 1;
scr_chain[0] = scr->in;
for (size_t j = 1; j < VLC_CHROMA_CONV_CHAIN_COUNT_MAX; ++j)
{
if (end_reached)
{
scr_chain[j] = 0;
continue;
}
if (scr_result->chain[j - 1] != 0)
{
scr_chain[j] = scr_result->chain[j - 1];
scr_count++;
}
else
{
if (scr->out != 0)
{
scr_chain[j] = scr->out;
scr_count++;
}
end_reached = true;
}
}
assert(scr_count == result->chain_count);
size_t j;
for (j = 0; j < result->chain_count; ++j)
assert(result->chain[j] == scr_chain[j]);
for (; j < VLC_CHROMA_CONV_CHAIN_COUNT_MAX - 1; ++j)
assert(scr_result->chain[j - 1] == 0);
assert(result->cost == scr_result->cost);
assert(result->quality == scr_result->quality);
}
}
int main(int argc, const char *argv[])
{
test_init();
if (argc > 1 && strlen(argv[1]) >= 4)
{
/* Disable test module (use all VLC modules) */
vlc_static_modules[0] = NULL;
unsigned max_indirect_steps = 1;
if (argc > 2)
max_indirect_steps = atoi(argv[2]);
int flags = 0;
if (argc > 3)
flags = atoi(argv[3]);
const char *f = argv[1];
vlc_fourcc_t from_fourcc = VLC_FOURCC(f[0], f[1], f[2], f[3]);
vlc_fourcc_t to_fourcc = 0;
if (f[4] == '-' && strlen(f) >= 9)
to_fourcc = VLC_FOURCC(f[5], f[6], f[7], f[8]);
libvlc_instance_t *vlc = libvlc_new(0, NULL);
assert(vlc != NULL);
size_t count;
struct vlc_chroma_conv_result *results =
vlc_chroma_conv_Probe(from_fourcc, to_fourcc, 0, 0,
max_indirect_steps, flags, &count);
assert(results != NULL);
print_results(results, count);
free(results);
libvlc_release(vlc);
return 0;
}
/* Disable all modules except the one from this test */
const char *libvlc_argv[] = {
"--no-plugins-cache",
"--no-plugins-scan",
};
int libvlc_argc = ARRAY_SIZE(libvlc_argv);
libvlc_instance_t *vlc = libvlc_new(libvlc_argc, libvlc_argv);
assert(vlc != NULL);
size_t scenario_count = ARRAY_SIZE(scenario_array);
for (size_t i = 0; i < scenario_count; i++)
{
const struct scenario *scr = &scenario_array[i];
fprintf(stderr, "scenario: %4.4s -> %4.4s flags: 0x%x, "
"max_indirect_steps: %u result_count: %zu\n",
(const char *)&scr->in, (const char *)&scr->out,
scr->flags,
scr->max_indirect_steps, scr->result_count);
size_t count;
struct vlc_chroma_conv_result *results =
vlc_chroma_conv_Probe(scr->in, scr->out, 0, 0,
scr->max_indirect_steps, scr->flags, &count);
if (results == NULL)
{
assert(scr->result_count == 0);
continue;
}
print_results(results, count);
assert(count == scr->result_count);
check_results(scr, results);
free(results);
}
libvlc_release(vlc);
return 0;
}
Loading…
Cancel
Save