Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
f9d0c115 by Thomas Guillem at 2023-10-28T14:40:34+00:00
tracer: add vlc_tracer_vaTraceWithTs()
- - - - -
08950258 by Thomas Guillem at 2023-10-28T14:40:34+00:00
test: add clock test
Possible to run all tests (by default from the test suite):
vlc/build-linux/test $ ./test_src_clock_clock
[normal]: checking that normal update has a coeff of 1.0f
[lowprecision]: checking that low precision update has a coeff near 1.0f
[drift_72]: checking that a drift of 72ms in 2h is handled
[drift_-72]: checking that a drift of -72ms in 2h is handled
[drift_864]: checking that a drift of 864ms in 24h is handled
[drift_-864]: checking that a drift of -864ms in 24h is handled
[drift_sudden]: checking that a sudden drift is handled
Or run one test with traces:
./test_src_clock_clock lowprecision --tracer=json --json-tracer-file=foo.trace
-vv
The test fake the timestamps that are sent to the default tracer to
behave like a live playback.
One possible future scenario would be to feed the clock with real points
taken from a real 24h live-streaming playback.
- - - - -
5 changed files:
- include/vlc_tracer.h
- src/libvlccore.sym
- src/misc/tracer.c
- test/Makefile.am
- + test/src/clock/clock.c
Changes:
=====================================
include/vlc_tracer.h
=====================================
@@ -136,10 +136,21 @@ VLC_API void vlc_tracer_Destroy(struct vlc_tracer
*tracer);
/**
* Emit traces
*
- * va-args are a list of key / value parameters.
+ * \param tracer tracer emitting the traces
+ * \param ts timestamp of the current trace
+ * \param entries list of key / value parameters.
* Key must be a not NULL string.
* Value has to be defined with one of the type defined
* in the \ref vlc_tracer_entry union.
+ */
+VLC_API void vlc_tracer_vaTraceWithTs(struct vlc_tracer *tracer, vlc_tick_t ts,
+ va_list entries);
+
+/**
+ * Emit traces
+ *
+ * cf. vlc_tracer_vaTraceWithTs()
+ *
* \param tracer tracer emitting the traces
* \param ts timestamp of the current trace
*/
=====================================
src/libvlccore.sym
=====================================
@@ -293,6 +293,7 @@ vlc_vaLog
vlc_tracer_Create
vlc_tracer_Destroy
vlc_tracer_TraceWithTs
+vlc_tracer_vaTraceWithTs
vlc_LogHeaderCreate
vlc_LogDestroy
vlc_strerror
=====================================
src/misc/tracer.c
=====================================
@@ -46,6 +46,19 @@ struct vlc_tracer_module {
void *opaque;
};
+void vlc_tracer_vaTraceWithTs(struct vlc_tracer *tracer, vlc_tick_t ts,
+ va_list entries)
+{
+ assert(tracer->ops->trace != NULL);
+ struct vlc_tracer_module *module =
+ container_of(tracer, struct vlc_tracer_module, tracer);
+
+ va_list copy;
+ va_copy(copy, entries);
+ tracer->ops->trace(module->opaque, ts, copy);
+ va_end(copy);
+}
+
void vlc_tracer_TraceWithTs(struct vlc_tracer *tracer, vlc_tick_t ts, ...)
{
assert(tracer->ops->trace != NULL);
=====================================
test/Makefile.am
=====================================
@@ -24,6 +24,7 @@ check_PROGRAMS = \
test_libvlc_renderer_discoverer \
test_libvlc_slaves \
test_src_config_chain \
+ test_src_clock_clock \
test_src_misc_ancillary \
test_src_misc_variables \
test_src_input_stream \
@@ -155,6 +156,10 @@ test_libvlc_slaves_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_libvlc_meta_SOURCES = libvlc/meta.c
test_libvlc_meta_LDADD = $(LIBVLCCORE) $(LIBVLC)
+test_src_clock_clock_SOURCES = src/clock/clock.c \
+ ../src/clock/clock.c \
+ ../src/clock/clock_internal.c
+test_src_clock_clock_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_src_misc_ancillary_SOURCES = src/misc/ancillary.c
test_src_misc_ancillary_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_src_misc_variables_SOURCES = src/misc/variables.c
=====================================
test/src/clock/clock.c
=====================================
@@ -0,0 +1,608 @@
+/*****************************************************************************
+ * clock/clock.c: test for the vlc clock
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors, VideoLAN and Videolabs SAS
+ *
+ * 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_common.h>
+#include <vlc_tick.h>
+#include <vlc_es.h>
+#include <vlc_tracer.h>
+
+#include "../../../src/clock/clock.h"
+
+#include <vlc/vlc.h>
+#include "../../libvlc/test.h"
+#include "../../../lib/libvlc_internal.h"
+
+#define MODULE_NAME test_clock_clock
+#undef VLC_DYNAMIC_PLUGIN
+#include <vlc_plugin.h>
+#include <vlc_vector.h>
+
+/* Define a builtin module for mocked parts */
+const char vlc_module_name[] = MODULE_STRING;
+
+struct clock_ctx;
+
+struct clock_scenario
+{
+ const char *name;
+ const char *desc;
+ vlc_tick_t stream_start;
+ vlc_tick_t system_start; /* VLC_TICK_INVALID for vlc_tick_now() */
+ vlc_tick_t duration;
+ vlc_tick_t stream_increment; /* VLC_TICK_INVALID for manual increment */
+ vlc_tick_t total_drift_duration; /* VLC_TICK_INVALID for non-drift test */
+
+ void (*update)(const struct clock_ctx *ctx, size_t index,
+ vlc_tick_t *system, vlc_tick_t stream);
+ void (*check)(const struct clock_ctx *ctx, size_t update_count,
+ vlc_tick_t expected_system_end, vlc_tick_t stream_end);
+};
+
+struct clock_ctx
+{
+ vlc_clock_main_t *mainclk;
+ vlc_clock_t *master;
+ vlc_clock_t *slave;
+
+ const struct clock_scenario *scenario;
+};
+
+enum tracer_event_type {
+ TRACER_EVENT_TYPE_UPDATE,
+ TRACER_EVENT_TYPE_STATUS,
+};
+
+enum tracer_event_status {
+ TRACER_EVENT_STATUS_RESET_USER,
+ TRACER_EVENT_STATUS_RESET_BADSOURCE,
+};
+
+struct tracer_event
+{
+ enum tracer_event_type type;
+
+ union {
+ struct {
+ double coeff;
+ vlc_tick_t offset;
+ } update;
+
+ enum tracer_event_status status;
+ };
+};
+
+struct tracer_ctx
+{
+ vlc_tick_t forced_ts;
+
+ struct VLC_VECTOR(struct tracer_event) events;
+};
+
+static struct tracer_ctx tracer_ctx;
+
+static void tracer_ctx_Reset(struct tracer_ctx *ctx)
+{
+ ctx->events.size = 0;
+ ctx->forced_ts = VLC_TICK_INVALID;
+}
+
+static void tracer_ctx_Init(struct tracer_ctx *ctx)
+{
+ tracer_ctx_Reset(ctx);
+ vlc_vector_init(&ctx->events);
+};
+
+static void tracer_ctx_Destroy(struct tracer_ctx *ctx)
+{
+ vlc_vector_destroy(&ctx->events);
+}
+
+static void tracer_ctx_PushUpdate(struct tracer_ctx *ctx,
+ double coeff, vlc_tick_t offset)
+{
+ struct tracer_event event = {
+ .type = TRACER_EVENT_TYPE_UPDATE,
+ .update = {
+ .coeff = coeff,
+ .offset = offset,
+ },
+ };
+ bool ret = vlc_vector_push(&ctx->events, event);
+ assert(ret);
+}
+
+static void tracer_ctx_PushStatus(struct tracer_ctx *ctx,
+ enum tracer_event_status status)
+{
+ struct tracer_event event = {
+ .type = TRACER_EVENT_TYPE_STATUS,
+ .status = status,
+ };
+ bool ret = vlc_vector_push(&ctx->events, event);
+ assert(ret);
+}
+
+static void TracerTrace(void *opaque, vlc_tick_t ts, va_list entries)
+{
+ (void) ts;
+ struct vlc_tracer *libvlc_tracer = opaque;
+ if (libvlc_tracer != NULL)
+ {
+ /* If the user specified a tracer, forward directly to it after faking
+ * the system ts */
+ assert(tracer_ctx.forced_ts != VLC_TICK_INVALID);
+ vlc_tracer_vaTraceWithTs(libvlc_tracer, tracer_ctx.forced_ts, entries);
+ }
+
+ struct vlc_tracer_entry entry = va_arg(entries, struct vlc_tracer_entry);
+
+ bool is_render = false, is_status = false;
+ unsigned nb_update = 0;
+ double coeff = 0.0f;
+ vlc_tick_t offset = VLC_TICK_INVALID;
+ enum tracer_event_status status = 0;
+
+ while (entry.key != NULL)
+ {
+ switch (entry.type)
+ {
+ case VLC_TRACER_INT:
+ if (!is_render)
+ continue;
+ assert(!is_status);
+
+ if (strcmp(entry.key, "offset") == 0)
+ {
+ nb_update++;
+ offset = VLC_TICK_FROM_NS(entry.value.integer);
+ }
+ break;
+ case VLC_TRACER_DOUBLE:
+ if (!is_render)
+ continue;
+ assert(!is_status);
+
+ if (strcmp(entry.key, "coeff") == 0)
+ {
+ nb_update++;
+ coeff = entry.value.double_;
+ }
+ break;
+ case VLC_TRACER_STRING:
+ if (strcmp(entry.key, "type") == 0)
+ {
+ if (strcmp(entry.value.string, "RENDER") == 0)
+ is_render = true;
+ }
+ if (!is_render)
+ continue;
+ /* Assert that there is no "reset_bad_source" */
+ if (strcmp(entry.key, "event") == 0)
+ {
+ is_status = true;
+ if (strcmp(entry.value.string, "reset_bad_source") == 0)
+ status = TRACER_EVENT_STATUS_RESET_BADSOURCE;
+ else if (strcmp(entry.value.string, "reset_user") == 0)
+ status = TRACER_EVENT_STATUS_RESET_USER;
+ else
+ vlc_assert_unreachable();
+ }
+
+ break;
+ default: vlc_assert_unreachable();
+ }
+ entry = va_arg(entries, struct vlc_tracer_entry);
+ }
+
+ if (!is_render)
+ return;
+
+ if (is_status)
+ tracer_ctx_PushStatus(&tracer_ctx, status);
+ else if (nb_update > 0)
+ {
+ assert(nb_update == 2);
+ tracer_ctx_PushUpdate(&tracer_ctx, coeff, offset);
+ }
+}
+
+/* Used to check for some trace value and hack the ts to the user tracer */
+static const struct vlc_tracer_operations *
+OpenTracer(vlc_object_t *obj, void **restrict sysp)
+{
+ static const struct vlc_tracer_operations ops =
+ {
+ .trace = TracerTrace,
+ };
+
+ *sysp = vlc_object_get_tracer(obj);
+
+ return &ops;
+}
+
+vlc_module_begin()
+ set_callback(OpenTracer)
+ set_capability("tracer", 0)
+vlc_module_end()
+
+VLC_EXPORT const vlc_plugin_cb vlc_static_modules[] = {
+ VLC_SYMBOL(vlc_entry),
+ NULL
+};
+
+static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer,
+ struct clock_scenario *scenario)
+{
+ fprintf(stderr, "[%s]: checking that %s\n", scenario->name,
scenario->desc);
+
+ assert(scenario->update != NULL);
+
+ tracer_ctx_Reset(&tracer_ctx);
+
+ struct vlc_logger *logger = vlc->obj.logger;
+
+ vlc_clock_main_t *mainclk = vlc_clock_main_New(logger, tracer);
+ assert(mainclk != NULL);
+
+ vlc_clock_t *master = vlc_clock_main_CreateMaster(mainclk, scenario->name,
+ NULL, NULL);
+ assert(master != NULL);
+
+ vlc_clock_t *slave = vlc_clock_main_CreateSlave(mainclk, NULL, VIDEO_ES,
+ NULL, NULL);
+ assert(slave != NULL);
+
+ const struct clock_ctx ctx = {
+ .mainclk = mainclk,
+ .master = master,
+ .slave = slave,
+ .scenario = scenario,
+ };
+
+ vlc_tick_t stream_end = scenario->stream_start + scenario->duration;
+ vlc_tick_t stream = scenario->stream_start;
+ if (scenario->system_start == VLC_TICK_INVALID)
+ scenario->system_start = vlc_tick_now();
+ vlc_tick_t system = scenario->system_start;
+ vlc_tick_t expected_system = scenario->system_start;
+
+ tracer_ctx.forced_ts = expected_system;
+ size_t index = 0;
+
+ for(; stream < stream_end;
+ stream += scenario->stream_increment, ++index)
+ {
+ scenario->update(&ctx, index, &system, stream);
+ expected_system += scenario->stream_increment;
+
+ tracer_ctx.forced_ts = expected_system;
+ }
+
+ if (scenario->check != NULL)
+ {
+ assert(expected_system == scenario->system_start + scenario->duration);
+
+ scenario->check(&ctx, index, expected_system, stream_end);
+ }
+
+ if (master != NULL)
+ vlc_clock_Delete(master);
+ vlc_clock_Delete(slave);
+ vlc_clock_main_Delete(mainclk);
+}
+
+static void run_scenarios(int main_argc, const char *main_argv[],
+ struct clock_scenario *scenarios, size_t count)
+{
+
+ int argc;
+ const char * const *argv;
+
+ const char *scenario_name = NULL;
+ if (main_argc > 1)
+ {
+ /* specific test run from the user with custom options */
+ scenario_name = main_argv[1];
+ argc = main_argc - 1;
+ argv = &main_argv[1];
+ }
+ else
+ {
+ argc = main_argc;
+ argv = main_argv;
+ }
+
+ libvlc_instance_t *vlc = libvlc_new(argc, argv);
+ assert(vlc != NULL);
+
+ tracer_ctx_Init(&tracer_ctx);
+
+ struct vlc_tracer *tracer =
vlc_tracer_Create(VLC_OBJECT(vlc->p_libvlc_int),
+ MODULE_STRING);
+ assert(tracer != NULL);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ if (scenario_name == NULL
+ || strcmp(scenario_name, scenarios[i].name) == 0)
+ play_scenario(vlc->p_libvlc_int, tracer, &scenarios[i]);
+ }
+
+ vlc_tracer_Destroy(tracer);
+
+ tracer_ctx_Destroy(&tracer_ctx);
+
+ libvlc_release(vlc);
+}
+
+static void normal_update(const struct clock_ctx *ctx, size_t index,
+ vlc_tick_t *system, vlc_tick_t stream)
+{
+ (void) index;
+ const struct clock_scenario *scenario = ctx->scenario;
+
+ vlc_tick_t drift =
+ vlc_clock_Update(ctx->master, *system, stream, 1.0f);
+ /* The master can't drift */
+ assert(drift == VLC_TICK_INVALID);
+
+ /* Check the slave is drifting (only system is moving) */
+ drift = vlc_clock_Update(ctx->slave, *system,
+ VLC_TICK_0, 1.0f);
+ assert(drift == - (stream - VLC_TICK_0));
+
+ *system += scenario->stream_increment;
+}
+
+static void check_no_event_error(size_t expected_update_count)
+{
+ /* assert that there is no error/status */
+ assert(tracer_ctx.events.size > 0);
+
+ size_t update_count = 0;
+ for (size_t i = 0; i < tracer_ctx.events.size; ++i)
+ {
+ struct tracer_event event = tracer_ctx.events.data[i];
+ switch (event.type)
+ {
+ case TRACER_EVENT_TYPE_UPDATE:
+ update_count++;
+ break;
+ case TRACER_EVENT_TYPE_STATUS:
+ switch (event.status)
+ {
+ case TRACER_EVENT_STATUS_RESET_USER:
+ case TRACER_EVENT_STATUS_RESET_BADSOURCE:
+ assert("clock reset not expected" == NULL);
+ break;
+ }
+ break;
+ }
+ }
+
+ assert(update_count == expected_update_count);
+}
+
+static void normal_check(const struct clock_ctx *ctx, size_t update_count,
+ vlc_tick_t expected_system_end,
+ vlc_tick_t stream_end)
+{
+ (void) expected_system_end; (void) stream_end;
+ const struct clock_scenario *scenario = ctx->scenario;
+
+ check_no_event_error(update_count);
+
+ for (size_t i = 0; i < tracer_ctx.events.size; ++i)
+ {
+ struct tracer_event event = tracer_ctx.events.data[i];
+ if (event.type == TRACER_EVENT_TYPE_UPDATE)
+ {
+ assert(event.update.coeff == 1.0f);
+ assert(event.update.offset ==
+ scenario->system_start - scenario->stream_start);
+ }
+ }
+
+ vlc_tick_t converted =
+ vlc_clock_ConvertToSystem(ctx->slave, expected_system_end,
+ stream_end, 1.0f);
+ assert(converted == expected_system_end);
+}
+
+static void lowprecision_update(const struct clock_ctx *ctx, size_t index,
+ vlc_tick_t *system, vlc_tick_t stream)
+{
+ (void) index;
+ const struct clock_scenario *scenario = ctx->scenario;
+
+ vlc_tick_t base_system = stream - scenario->stream_start
+ + scenario->system_start;
+ /* random imprecision (seed based on stream) */
+ srand(stream);
+ vlc_tick_t imprecision = rand() % VLC_TICK_FROM_MS(5);
+ *system = base_system + imprecision;
+
+ vlc_tick_t drift =
+ vlc_clock_Update(ctx->master, *system, stream, 1.0f);
+ /* The master can't drift */
+ assert(drift == VLC_TICK_INVALID);
+}
+
+static void lowprecision_check(const struct clock_ctx *ctx, size_t
update_count,
+ vlc_tick_t expected_system_end,
+ vlc_tick_t stream_end)
+{
+ (void) ctx; (void) expected_system_end; (void) stream_end;
+
+ check_no_event_error(update_count);
+
+ static const double epsilon = 0.005;
+
+ for (size_t i = 0; i < tracer_ctx.events.size; ++i)
+ {
+ struct tracer_event event = tracer_ctx.events.data[i];
+ if (event.type == TRACER_EVENT_TYPE_UPDATE)
+ assert(fabs(event.update.coeff - 1.0f) <= epsilon);
+
+ }
+}
+
+static void drift_update(const struct clock_ctx *ctx, size_t index,
+ vlc_tick_t *system, vlc_tick_t stream)
+{
+ (void) index;
+ const struct clock_scenario *scenario = ctx->scenario;
+
+ vlc_tick_t drift =
+ vlc_clock_Update(ctx->master, *system, stream, 1.0f);
+ /* The master can't drift */
+ assert(drift == VLC_TICK_INVALID);
+
+ *system += scenario->stream_increment;
+
+ /* Simulate 1us drift every stream_increment */
+ if (scenario->total_drift_duration > 0)
+ *system += VLC_TICK_FROM_US(1);
+ else
+ *system -= VLC_TICK_FROM_US(1);
+}
+
+static void drift_check(const struct clock_ctx *ctx, size_t update_count,
+ vlc_tick_t expected_system_end,
+ vlc_tick_t stream_end)
+{
+ const struct clock_scenario *scenario = ctx->scenario;
+
+ check_no_event_error(update_count);
+
+ vlc_tick_t converted =
+ vlc_clock_ConvertToSystem(ctx->slave, expected_system_end,
+ stream_end, 1.0f);
+
+ assert(converted - expected_system_end == scenario->total_drift_duration);
+}
+
+static void drift_sudden_update(const struct clock_ctx *ctx, size_t index,
+ vlc_tick_t *system, vlc_tick_t stream)
+{
+ (void) index;
+ const struct clock_scenario *scenario = ctx->scenario;
+
+ vlc_tick_t drift =
+ vlc_clock_Update(ctx->master, *system, stream, 1.0f);
+ /* The master can't drift */
+ assert(drift == VLC_TICK_INVALID);
+
+ *system += scenario->stream_increment;
+
+ if (stream - scenario->stream_start >= scenario->duration * 3 / 4)
+ {
+ /* Simulate a sudden high drift */
+ *system += VLC_TICK_FROM_US(4);
+ }
+}
+
+#define VLC_TICK_24H VLC_TICK_FROM_SEC(24 * 60 * 60)
+#define VLC_TICK_2H VLC_TICK_FROM_SEC(2 * 60 * 60)
+#define DEFAULT_STREAM_INCREMENT VLC_TICK_FROM_MS(100)
+
+#define INIT_SYSTEM_STREAM_TIMING(duration_, increment_) \
+ .stream_start = VLC_TICK_0 + VLC_TICK_FROM_MS(31000000), \
+ .system_start = VLC_TICK_INVALID, \
+ .duration = duration_, \
+ .stream_increment = increment_
+
+#define DRIFT_SCENARIO(name_, desc_, duration_, increment_, drift_) \
+ .name = name_, \
+ .desc = desc_, \
+ INIT_SYSTEM_STREAM_TIMING(duration_, increment_), \
+ .total_drift_duration = drift_, \
+ .update = drift_update, \
+ .check = drift_check,
+
+static struct clock_scenario clock_scenarios[] = {
+{
+ .name = "normal",
+ .desc = "normal update has a coeff of 1.0f",
+ INIT_SYSTEM_STREAM_TIMING(VLC_TICK_2H, DEFAULT_STREAM_INCREMENT),
+ .update = normal_update,
+ .check = normal_check,
+},
+{
+ .name = "lowprecision",
+ .desc = "low precision update has a coeff near 1.0f",
+ INIT_SYSTEM_STREAM_TIMING(VLC_TICK_2H, DEFAULT_STREAM_INCREMENT),
+ .update = lowprecision_update,
+ .check = lowprecision_check,
+},
+{
+ DRIFT_SCENARIO(
+ "drift_72",
+ "a drift of 72ms in 2h is handled",
+ VLC_TICK_2H,
+ DEFAULT_STREAM_INCREMENT,
+ VLC_TICK_FROM_MS(72)
+)},
+{
+ DRIFT_SCENARIO(
+ "drift_-72",
+ "a drift of -72ms in 2h is handled",
+ VLC_TICK_2H,
+ DEFAULT_STREAM_INCREMENT,
+ -VLC_TICK_FROM_MS(72)
+ )
+},
+{
+ DRIFT_SCENARIO(
+ "drift_864",
+ "a drift of 864ms in 24h is handled",
+ VLC_TICK_24H,
+ DEFAULT_STREAM_INCREMENT,
+ VLC_TICK_FROM_MS(864)
+ )
+},
+{
+ DRIFT_SCENARIO(
+ "drift_-864",
+ "a drift of -864ms in 24h is handled",
+ VLC_TICK_24H,
+ DEFAULT_STREAM_INCREMENT,
+ -VLC_TICK_FROM_MS(864)
+ )
+},
+{
+ .name = "drift_sudden",
+ .desc = "a sudden drift is handled",
+ INIT_SYSTEM_STREAM_TIMING(VLC_TICK_24H, DEFAULT_STREAM_INCREMENT),
+ .total_drift_duration = VLC_TICK_FROM_MS(864),
+ .update = drift_sudden_update,
+ .check = drift_check,
+},
+};
+
+int main(int argc, const char *argv[])
+{
+ test_init();
+ run_scenarios(argc, argv, clock_scenarios, ARRAY_SIZE(clock_scenarios));
+}
View it on GitLab:
https://code.videolan.org/videolan/vlc/-/compare/d97fe9750e8eee3844327f075d9804a09ac5c106...08950258ef982204319ea48af93603ffef19bd5c
--
View it on GitLab:
https://code.videolan.org/videolan/vlc/-/compare/d97fe9750e8eee3844327f075d9804a09ac5c106...08950258ef982204319ea48af93603ffef19bd5c
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
_______________________________________________
vlc-commits mailing list
vlc-commits@videolan.org
https://mailman.videolan.org/listinfo/vlc-commits