Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/src/clock/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 45 kB image not shown  

Quelle  clock.c   Sprache: C

 

/*****************************************************************************
 * clock.c: Output modules synchronisation clock
 *****************************************************************************
 * Copyright (C) 2018-2019 VLC authors, VideoLAN and Videolabs SAS
 * Copyright (C) 2024      Videolabs
 *
 * Authors: Thomas Guillem <thomas@gllm.fr>
 *          Alexandre Janniaux <ajanni@videolabs.io>
 *
 * 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_aout.h>
#include <assert.h>
#include <limits.h>
#include <vlc_tracer.h>
#include <vlc_vector.h>
#include "clock.h"
#include "clock_internal.h"

#define COEFF_THRESHOLD 0.2 /* between 0.8 and 1.2 */

#define MAX_PCR_DELAY VLC_TICK_FROM_SEC(60)

struct vlc_clock_listener_id
{
    vlc_clock_t *clock;
    const struct vlc_clock_event_cbs *cbs;
    void *data;
};

struct vlc_clock_context
{
    double rate;
    double coeff;
    vlc_tick_t offset;
    uint32_t clock_id;

    clock_point_t last;
    clock_point_t wait_sync_ref;

    /**
     * Start point emitted by the buffering to indicate when we supposedly
     * started the playback. It does not account for latency of the output
     * when one of them is driving the main_clock. */

    clock_point_t start_time;

    struct vlc_list node;
    struct vlc_list using_clocks;
};

struct vlc_clock_main_t
{
    struct vlc_logger *logger;
    struct vlc_tracer *tracer;
    vlc_mutex_t lock;
    vlc_cond_t cond;

    vlc_clock_t *master;
    vlc_clock_t *input_master;

    unsigned rc;

    /**
     * Linear function
     * system = ts * coeff / rate + offset
     */

    average_t coeff_avg; /* Moving average to smooth out the instant coeff */
    vlc_tick_t delay;
    struct vlc_clock_context *context;

    bool paused;
    vlc_tick_t pause_date;

    unsigned wait_sync_ref_priority;
    clock_point_t first_pcr;

    vlc_tick_t output_dejitter; /* Delay used to absorb the output clock jitter */
    vlc_tick_t input_dejitter; /* Delay used to absorb the input jitter */

    struct VLC_VECTOR(vlc_clock_listener_id *) listeners;
    struct vlc_list prev_contexts;
};

struct vlc_clock_ops
{
    vlc_tick_t (*update)(vlc_clock_t *clock, struct vlc_clock_context *ctx,
                         vlc_tick_t system_now, vlc_tick_t ts, double rate,
                         unsigned frame_rate, unsigned frame_rate_base);
    void (*reset)(vlc_clock_t *clock);
    vlc_tick_t (*set_delay)(vlc_clock_t *clock, vlc_tick_t delay);
    vlc_tick_t (*to_system)(vlc_clock_t *clock, struct vlc_clock_context *ctx,
                            vlc_tick_t system_now, vlc_tick_t ts, double rate);

    /**
     * Signal the clock bus that the given clock will start a new timeline.
     *
     * When starting an input clock, a whole new context is created to track
     * the parameters for the new timeline.
     *
     * When starting an output clock, the given clock will track the new context
     * to register the progress and convert the timestamps.
     *
     * \param clock         the clock which is ready to start
     * \param system_now    the system time for the start point
     * \param ts            the time for the start point
     **/

    void (*start)(vlc_clock_t *clock, vlc_tick_t system_now, vlc_tick_t ts);
};

struct vlc_clock_t
{
    const struct vlc_clock_ops *ops;
    vlc_clock_main_t *owner;
    vlc_tick_t delay;
    unsigned priority;
    const char *track_str_id;

    const struct vlc_clock_cbs *cbs;
    void *cbs_data;

    struct vlc_list node;

    struct vlc_clock_context *context;

    vlc_tick_t last_conversion;
};

vlc_clock_listener_id *
vlc_clock_AddListener(vlc_clock_t *clock,
                      const struct vlc_clock_event_cbs *cbs,
                      void *data)
{
    vlc_clock_main_t *main_clock = clock->owner;
    assert(cbs != NULL);
    vlc_mutex_assert(&main_clock->lock);

    vlc_clock_listener_id *listener_id = malloc(sizeof(*listener_id));
    if (listener_id == NULL)
        return NULL;

    listener_id->clock = clock;
    listener_id->cbs = cbs;
    listener_id->data = data;

    bool success = vlc_vector_push(&main_clock->listeners, listener_id);
    if (!success)
    {
        free(listener_id);
        return NULL;
    }
    return listener_id;
}

void
vlc_clock_RemoveListener(vlc_clock_t *clock, vlc_clock_listener_id *listener_id)
{
    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_assert(&main_clock->lock);

    const vlc_clock_listener_id *it;
    vlc_vector_foreach(it, &main_clock->listeners)
        if (it == listener_id)
        {
            vlc_vector_remove(&main_clock->listeners, vlc_vector_idx_it);
            free(listener_id);
            return;
        }

    vlc_assert_unreachable();
}

#define vlc_clock_SendEvent(main_clock, event) { \
    const struct vlc_clock_listener_id *listener_id; \
    vlc_vector_foreach(listener_id, &main_clock->listeners) \
        if (listener_id->cbs->on_##event != NULL) \
            listener_id->cbs->on_##event(listener_id->data); \
}

static inline void TraceRender(struct vlc_tracer *tracer, const char *type,
                               const char *id, vlc_tick_t now, vlc_tick_t pts,
                               vlc_tick_t drift, uint32_t clock_id)
{
    if (now != VLC_TICK_MAX && now != VLC_TICK_INVALID)
    {
        vlc_tracer_TraceWithTs(tracer, vlc_tick_now(),
                               VLC_TRACE("type", type),
                               VLC_TRACE("id", id),
                               VLC_TRACE("mode", "realtime"),
                               VLC_TRACE("clock_id", (int64_t)clock_id),
                               VLC_TRACE_TICK_NS("pts", pts),
                               VLC_TRACE_TICK_NS("render_ts", now),
                               VLC_TRACE_TICK_NS("drift", drift),
                               VLC_TRACE_END);
        vlc_tracer_TraceWithTs(tracer, now,
                               VLC_TRACE("type", type),
                               VLC_TRACE("id", id),
                               VLC_TRACE("mode", "representative"),
                               VLC_TRACE_TICK_NS("render_pts", pts),
                               VLC_TRACE_TICK_NS("drift", drift),
                               VLC_TRACE_END);

    }
    else
        vlc_tracer_Trace(tracer, VLC_TRACE("type", type),
                                 VLC_TRACE("id", id),
                                 VLC_TRACE_TICK_NS("pts", pts),
                                 VLC_TRACE_END);
}

static vlc_tick_t ComputeOffset(vlc_clock_main_t *main,
                          struct vlc_clock_context *ctx,
                          vlc_tick_t system_now,
                          vlc_tick_t ts,
                          double rate)
{
    vlc_tick_t start_stream = ctx->start_time.stream - VLC_TICK_0;
    vlc_tick_t start_system = ctx->start_time.system - VLC_TICK_0;
    if (main->first_pcr.system != VLC_TICK_INVALID)
        start_stream = start_system = 0;

    return system_now
        - ((vlc_tick_t) ((ts - start_stream) * ctx->coeff / rate))
        - start_system;
}

static void context_reset(struct vlc_clock_context *ctx)
{
    ctx->coeff = 1.0f;
    ctx->rate = 1.0f;
    ctx->offset = 0;
    ctx->wait_sync_ref = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
    ctx->last = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
    ctx->start_time = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
}

static void context_init(struct vlc_clock_context *ctx)
{
    vlc_list_init(&ctx->using_clocks);
    context_reset(ctx);
    ctx->clock_id = 0;
}

static struct vlc_clock_context *context_new(void)
{
    struct vlc_clock_context *ctx = malloc(sizeof(*ctx));
    if (unlikely(ctx == NULL))
        return NULL;
    context_init(ctx);
    return ctx;
}

static vlc_tick_t context_stream_to_system(const struct vlc_clock_context *ctx,
                                           vlc_tick_t ts)
{
    if (ctx->last.system == VLC_TICK_INVALID)
        return VLC_TICK_INVALID;

    return ((vlc_tick_t) ((ts - ctx->start_time.stream) * ctx->coeff / ctx->rate))
            + ctx->offset
            + ctx->start_time.system;
}

static void
vlc_clock_remove_current_context(vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;
    struct vlc_clock_context *ctx = clock->context;
    assert(ctx != NULL);

    /* Remove the clock from the context using_clocks */
    vlc_list_remove(&clock->node);
    uint32_t clock_id = ctx->clock_id;
    bool removed = false;

    /* Free the context if it is not used anymore. Don't free the main context
     * that should stay alive even when no clocks are referencing it.  */

    if (vlc_list_is_empty(&ctx->using_clocks) && ctx != main_clock->context)
    {
        /* Remove the context from the list of previous contexts */
        vlc_list_remove(&ctx->node);
        free(ctx);
        removed = true;
    }
    clock->context = NULL;

    if (main_clock->logger != NULL && clock->track_str_id != NULL
     && ctx != main_clock->context)
        vlc_warning(main_clock->logger, "clock(%s): unused clock context(%u)%s",
                    clock->track_str_id, clock_id, removed ? " (removed)" : "");
}

static void
vlc_clock_attach_context(vlc_clock_t *clock, struct vlc_clock_context *ctx)
{
    vlc_list_append(&clock->node, &ctx->using_clocks);
    clock->context = ctx;
}

static void
vlc_clock_switch_context(vlc_clock_t *clock, struct vlc_clock_context *ctx)
{
    vlc_clock_main_t *main_clock = clock->owner;

    vlc_clock_remove_current_context(clock);
    vlc_clock_attach_context(clock, ctx);

    if (main_clock->logger != NULL && clock->track_str_id != NULL)
        vlc_warning(main_clock->logger, "clock(%s): using clock context(%u)",
                    clock->track_str_id, ctx->clock_id);
}

static void
context_get_closest(vlc_clock_t *clock, struct vlc_clock_context *ctx,
                    vlc_tick_t system_now, vlc_tick_t ts,
                    struct vlc_clock_context **closest_ctx,
                    vlc_tick_t *closest_diff)
{
    /* Find the context which has the smallest gap with the last conversion. */
    vlc_tick_t converted =
        clock->ops->to_system(clock, ctx, system_now, ts, 1.0);

    vlc_tick_t diff = converted - clock->last_conversion;

    if (diff < 0)
        diff = -diff;

    if (diff < *closest_diff)
    {
        *closest_diff = diff;
        *closest_ctx = ctx;
    }
}

static struct vlc_clock_context *
vlc_clock_get_context(vlc_clock_t *clock, vlc_tick_t system_now, vlc_tick_t ts,
                      bool update)
{
    vlc_clock_main_t *main_clock = clock->owner;

    /* The input clock is always using the last context */
    if (clock->context == NULL)
        return main_clock->context;

    if (vlc_list_is_empty(&main_clock->prev_contexts)
     || clock->context == main_clock->context)
        return main_clock->context;

    vlc_tick_t closest_diff = VLC_TICK_MAX;
    struct vlc_clock_context *ctx = NULL;

    /* Iterate through newer contexts, and not past ones */
    for (struct vlc_clock_context *ctx_it = clock->context; ctx_it != NULL;
         ctx_it = vlc_list_next_entry_or_null(&main_clock->prev_contexts, ctx_it,
                                              struct vlc_clock_context, node))
        context_get_closest(clock, ctx_it, system_now, ts, &ctx, &closest_diff);

    context_get_closest(clock, main_clock->context, system_now, ts, &ctx,
                        &closest_diff);

    if (clock->context != ctx && update)
    {
        /* Switch the clock context to the newer one. This means that contexts
         * past the new one will never be selected again by this current clock.
         * Switch context only when updating, since outputs will hold frames
         * until there are displayed. */

        vlc_clock_switch_context(clock, ctx);
    }

    return ctx;
}

static void vlc_clock_main_reset(vlc_clock_main_t *main_clock)
{
    struct vlc_clock_context *ctx = main_clock->context;

    context_reset(ctx);
    AvgResetAndFill(&main_clock->coeff_avg, ctx->coeff);

    main_clock->wait_sync_ref_priority = UINT_MAX;
    main_clock->pause_date = VLC_TICK_INVALID;
    vlc_cond_broadcast(&main_clock->cond);
}

static inline void vlc_clock_on_update(vlc_clock_t *clock,
                                       vlc_tick_t system_now,
                                       vlc_tick_t ts,
                                       vlc_tick_t drift,
                                       double rate,
                                       unsigned frame_rate,
                                       unsigned frame_rate_base)
{
    vlc_clock_main_t *main_clock = clock->owner;
    if (clock->cbs)
        clock->cbs->on_update(system_now, ts, rate, frame_rate, frame_rate_base,
                              clock->cbs_data);

    if (main_clock->tracer != NULL && clock->track_str_id != NULL &&
        system_now != VLC_TICK_INVALID && system_now != VLC_TICK_MAX)
        TraceRender(main_clock->tracer, "RENDER", clock->track_str_id,
                    system_now, ts, drift, clock->context ? clock->context->clock_id : 0);
}

static void vlc_clock_master_update_coeff(
    vlc_clock_t *clock, struct vlc_clock_context *ctx,
    vlc_tick_t system_now, vlc_tick_t ts, double rate)
{
    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_assert(&main_clock->lock);

    if (ctx->last.system != VLC_TICK_INVALID
     && ts != ctx->last.stream)
    {
        if (rate == ctx->rate)
        {
            /* We have a reference so we can update coeff */
            vlc_tick_t system_diff = system_now - ctx->last.system;
            vlc_tick_t stream_diff = ts - ctx->last.stream;

            double instant_coeff = system_diff / (double) stream_diff * rate;

            /* System and stream ts should be incrementing */
            bool decreasing_ts = system_diff < 0 || stream_diff < 0;
            /* The instant coeff should always be around 1.0 */
            bool coefficient_unstable = instant_coeff > 1.0 + COEFF_THRESHOLD
                || instant_coeff < 1.0 - COEFF_THRESHOLD;

            if (decreasing_ts || coefficient_unstable)
            {
                if (main_clock->logger != NULL)
                {
                    if (decreasing_ts)
                        vlc_warning(main_clock->logger, "resetting master clock: "
                                    "decreasing ts: system: %"PRId64 ", stream: %" PRId64,
                                    system_diff, stream_diff);
                    else
                        vlc_warning(main_clock->logger, "resetting master clock: "
                                    "coefficient too unstable: %f", instant_coeff);
                }

                if (main_clock->tracer != NULL && clock->track_str_id != NULL)
                    vlc_tracer_TraceEvent(main_clock->tracer, "RENDER",
                                          clock->track_str_id,
                                          "reset_bad_source");

                vlc_clock_SendEvent(main_clock, discontinuity);

                /* Reset and continue (calculate the offset from the
                 * current point) */

                ctx->coeff = 1.f;
                AvgResetAndFill(&main_clock->coeff_avg, 1.);
                ctx->offset = ComputeOffset(main_clock, ctx, system_now, ts, rate);
            }
            else
            {
                AvgUpdate(&main_clock->coeff_avg, instant_coeff);
                ctx->coeff = AvgGet(&main_clock->coeff_avg);
            }
        }
    }
    else
    {
        main_clock->wait_sync_ref_priority = UINT_MAX;
        ctx->wait_sync_ref =
            clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);

        vlc_clock_SendEvent(main_clock, discontinuity);
    }

    ctx->offset = ComputeOffset(main_clock, ctx, system_now, ts, rate);

    if (main_clock->tracer != NULL && clock->track_str_id != NULL)
        vlc_tracer_Trace(main_clock->tracer,
                         VLC_TRACE("type", "RENDER"),
                         VLC_TRACE("id", clock->track_str_id),
                         VLC_TRACE_TICK_NS("offset", ctx->offset),
                         VLC_TRACE("coeff", ctx->coeff),
                         VLC_TRACE_END);

    ctx->rate = rate;
    vlc_cond_broadcast(&main_clock->cond);
}

static vlc_tick_t vlc_clock_master_update(vlc_clock_t *clock,
                                          struct vlc_clock_context *ctx,
                                          vlc_tick_t system_now,
                                          vlc_tick_t ts, double rate,
                                          unsigned frame_rate,
                                          unsigned frame_rate_base)
{
    vlc_clock_main_t *main_clock = clock->owner;

    if (unlikely(ts == VLC_TICK_INVALID || system_now == VLC_TICK_INVALID))
        return VLC_TICK_INVALID;

    if (ctx != main_clock->context)
        return VLC_TICK_INVALID; /* Don't update previous contexts */

    /* If system_now is VLC_TICK_MAX, the update is forced, don't modify
     * anything but only notify the new clock point. */

    if (system_now != VLC_TICK_MAX)
    {
        vlc_clock_master_update_coeff(clock, ctx, system_now, ts, rate);
        ctx->last = clock_point_Create(system_now, ts);
    }

    /* Fix the reported ts if both master and slaves source are delayed. This
     * happens if we apply a positive delay to the master, and then lower it. */

    if (clock->delay > 0 && main_clock->delay < 0 && ts > -main_clock->delay)
        ts += main_clock->delay;

    vlc_tick_t drift = 0;
    vlc_clock_on_update(clock, system_now, ts, drift,
                        rate, frame_rate, frame_rate_base);
    return drift;
}

static void vlc_clock_master_reset(vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;

    if (main_clock->tracer != NULL && clock->track_str_id != NULL)
        vlc_tracer_TraceEvent(main_clock->tracer, "RENDER", clock->track_str_id,
                              "reset_user");

    /* Attach to the last and main context in case of reset */
    struct vlc_clock_context *ctx = main_clock->context;
    if (clock->context != NULL && clock->context != ctx)
        vlc_clock_switch_context(clock, ctx);


    assert(main_clock->delay <= 0);
    assert(clock->delay >= 0);
    /* Move the delay from the slaves to the master */
    if (clock->delay != 0 && main_clock->delay != 0)
    {
        vlc_tick_t delta = clock->delay + main_clock->delay;
        if (delta > 0)
        {
            clock->delay = delta;
            main_clock->delay = 0;
        }
        else
        {
            clock->delay = 0;
            main_clock->delay = delta;
        }
    }

    vlc_clock_on_update(clock, VLC_TICK_INVALID, VLC_TICK_INVALID, VLC_TICK_INVALID, 1.f, 0, 0);
}

static vlc_tick_t vlc_clock_master_set_delay(vlc_clock_t *clock, vlc_tick_t delay)
{
    vlc_clock_main_t *main_clock = clock->owner;

    vlc_tick_t delta = delay - clock->delay;

    if (delta > 0)
    {
        /* The master clock is delayed */
        main_clock->delay = 0;
        clock->delay = delay;
    }
    else
    {
        /* Delay all slaves clock instead of advancing the master one */
        main_clock->delay = delta;
    }

    assert(main_clock->delay <= 0);
    assert(clock->delay >= 0);

    vlc_cond_broadcast(&main_clock->cond);
    return delta;
}

static void
vlc_clock_input_start(vlc_clock_t *clock,
                      vlc_tick_t start_date,
                      vlc_tick_t first_ts)
{
    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_assert(&main_clock->lock);
    assert(main_clock->context != NULL);

    if (main_clock->first_pcr.system != VLC_TICK_INVALID)
        return;

    struct vlc_clock_context *context
        = main_clock->context;

    struct vlc_clock_context *last_context =
        vlc_list_last_entry_or_null(&main_clock->prev_contexts, struct vlc_clock_context, node);

    if (last_context != NULL)
        context->clock_id = last_context->clock_id + 1;

    // Note: should not trigger when SetFirstPCR is called
    context->start_time = clock_point_Create(start_date, first_ts);
    context->offset = ComputeOffset(main_clock, context, start_date, first_ts, 1.f);
    main_clock->wait_sync_ref_priority = UINT_MAX;

    vlc_clock_switch_context(clock, context);
}

static void vlc_clock_slave_reset(vlc_clock_t *clock);
static void vlc_clock_input_reset(vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;

    if (main_clock->tracer != NULL && clock->track_str_id != NULL)
        vlc_tracer_TraceEvent(main_clock->tracer, "RENDER", clock->track_str_id,
                              "reset_user");

    if (main_clock->first_pcr.system != VLC_TICK_INVALID)
    {
        (main_clock->master == clock ? vlc_clock_master_reset : vlc_clock_slave_reset)(clock);
        return;
    }

    if (clock->context == NULL || clock->context->start_time.system == VLC_TICK_INVALID)
        return;

    struct vlc_clock_context *context = main_clock->context;

    bool has_other_clock = false;
    vlc_clock_t *clock_iterator;
    vlc_list_foreach(clock_iterator, &context->using_clocks, node)
    {
        if (clock_iterator == clock)
            continue;

        has_other_clock = true;
    }

    if (!has_other_clock)
    {
        context_reset(context);
        return;
    }

    assert(context->start_time.stream != VLC_TICK_INVALID);
    vlc_list_append(&context->node, &main_clock->prev_contexts);
    main_clock->context = context_new();

    if (main_clock->context == NULL)
        main_clock->context = context; /* TODO: It fallbacks to previous context */
}

static vlc_tick_t
vlc_clock_monotonic_to_system(vlc_clock_t *clock, struct vlc_clock_context *ctx,
                              vlc_tick_t now, vlc_tick_t ts, double rate)
{
    vlc_clock_main_t *main_clock = clock->owner;

    if (clock->priority < main_clock->wait_sync_ref_priority
     && ctx == main_clock->context
     && main_clock->first_pcr.system != VLC_TICK_INVALID)
    {
        /* XXX: This input_delay calculation is needed until we (finally) get
         * ride of the input clock. This code is adapted from input_clock.c and
         * is used to introduce the same delay than the input clock (first PTS
         * - first PCR). */

        vlc_tick_t pcr_delay = 0;
        if (ctx->start_time.system != VLC_TICK_INVALID)
            pcr_delay = (ts - ctx->start_time.stream) / rate +
                ctx->start_time.system - now;
        else if (main_clock->first_pcr.system != VLC_TICK_INVALID)
            pcr_delay = (ts - main_clock->first_pcr.stream) / rate +
                main_clock->first_pcr.system - now;

        if (pcr_delay > MAX_PCR_DELAY)
        {
            if (main_clock->logger != NULL)
                vlc_error(main_clock->logger, "Invalid PCR delay ! Ignoring it...");
            pcr_delay = 0;
        }

        const vlc_tick_t input_delay = main_clock->input_dejitter + pcr_delay;

        const vlc_tick_t delay =
            __MAX(input_delay, main_clock->output_dejitter);

        main_clock->wait_sync_ref_priority = clock->priority;
        ctx->wait_sync_ref = clock_point_Create(now + delay, ts);
    }


    return (ts - ctx->wait_sync_ref.stream) / rate + ctx->wait_sync_ref.system;
}

static vlc_tick_t vlc_clock_slave_to_system(vlc_clock_t *clock,
                                            struct vlc_clock_context *ctx,
                                            vlc_tick_t now, vlc_tick_t ts,
                                            double rate)
{
    vlc_clock_main_t *main_clock = clock->owner;

    vlc_tick_t system = context_stream_to_system(ctx, ts);
    if (system == VLC_TICK_INVALID)
    {
        /* We don't have a master sync point, let's fallback to a monotonic ref
         * point */

        system = vlc_clock_monotonic_to_system(clock, ctx, now, ts, rate);
    }

    return system + (clock->delay - main_clock->delay) * rate;
}

static vlc_tick_t vlc_clock_master_to_system(vlc_clock_t *clock,
                                             struct vlc_clock_context *ctx,
                                             vlc_tick_t now, vlc_tick_t ts,
                                             double rate)
{
    vlc_tick_t system = context_stream_to_system(ctx, ts);
    if (system == VLC_TICK_INVALID)
    {
        /* We don't have a master sync point, let's fallback to a monotonic ref
         * point */

        system = vlc_clock_monotonic_to_system(clock, ctx, now, ts, rate);
    }

    return system;
}

static vlc_tick_t vlc_clock_slave_update(vlc_clock_t *clock,
                                         struct vlc_clock_context *ctx,
                                         vlc_tick_t system_now,
                                         vlc_tick_t ts, double rate,
                                         unsigned frame_rate,
                                         unsigned frame_rate_base)
{
    vlc_clock_main_t *main_clock = clock->owner;

    if (system_now == VLC_TICK_MAX)
    {
        /* If system_now is VLC_TICK_MAX, the update is forced, don't modify
         * anything but only notify the new clock point. */

        vlc_clock_on_update(clock, VLC_TICK_MAX, ts, VLC_TICK_INVALID,
                            rate, frame_rate, frame_rate_base);
        return VLC_TICK_MAX;
    }

    vlc_tick_t drift;
    vlc_tick_t computed;
    if (main_clock->first_pcr.system != VLC_TICK_INVALID || ctx->wait_sync_ref.stream != VLC_TICK_INVALID)
    {
        computed = clock->ops->to_system(clock, ctx, system_now, ts, rate);
        drift = computed - system_now;
    }
    else
    {
        computed = system_now;
        drift = 0;
    }

    vlc_clock_on_update(clock, computed, ts, drift, rate,
                        frame_rate, frame_rate_base);
    return drift;
}

static void vlc_clock_slave_reset(vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;

    /* Attach to the last and main context in case of reset */
    struct vlc_clock_context *ctx = main_clock->context;
    if (clock->context != NULL && clock->context != ctx)
        vlc_clock_switch_context(clock, ctx);

    vlc_clock_on_update(clock, VLC_TICK_INVALID, VLC_TICK_INVALID,
                        VLC_TICK_INVALID, 1.0f, 0, 0);
}

static vlc_tick_t vlc_clock_slave_set_delay(vlc_clock_t *clock, vlc_tick_t delay)
{
    vlc_clock_main_t *main_clock = clock->owner;

    clock->delay = delay;

    vlc_cond_broadcast(&main_clock->cond);
    return 0;
}

static void
vlc_clock_output_start(vlc_clock_t *clock,
                       vlc_tick_t start_date,
                       vlc_tick_t first_ts)
{
    (void)start_date; (void)first_ts;

    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_assert(&main_clock->lock);

    if (clock->last_conversion == VLC_TICK_INVALID)
        clock->last_conversion = start_date;

    /* Attach to the correct context in case of reset */
    struct vlc_clock_context *context
        = vlc_clock_get_context(clock, start_date, first_ts, true);

#if 0
    /* Disabled for now, the handling will be done later. */
    /* vlc_clock_Start must have already been called. */
    assert(context->start_time.system != VLC_TICK_INVALID);
    assert(context->start_time.stream != VLC_TICK_INVALID);
#endif
    if (context->start_time.system == VLC_TICK_INVALID)
        return;

    /* Attach to the correct context in case of reset */
    clock->context = context;

    if (clock->priority >= main_clock->wait_sync_ref_priority)
        return;

    /**
     * The clock should have already been started, so we have a valid
     * start_time which links a PCR value to a time where the PCR is being
     * "used". Any PTS from the output will necessarily be converted after
     * this start_time, and will necessarily have bigger PTS than the
     * registered PCR, so we can use the difference to find how much delta
     * there is between the first PTS and the first PCR.
     *
     * When setting up the outputs, some amount of wait will already be
     * consumed, for instance when playing live stream, we consume some time
     * between the first decoded packet and the end of the buffering. The
     * core currently handles this by substracting the buffering duration to
     * the start time, and we re-inject this duration through the pts-delay,
     * called input_dejitter in the clock.
     *
     *    |  Start                   End of      Decoders ready
     *    |  buffering               buffering   (DecoderWaitUnblock)
     *    |     v                       v           v
     *    |     x-----------------------x-----------x---> system time
     *    |
     *    |                               Preroll and
     *    |             pts delay         decoder delay
     *    |     |----------------------->|---------->
     *    |                 <-----------------------|
     *    |                     Buffering duration
     *    |                       (media time)
     *    |
     *    | Fig.1: System time representation of the clock startup
     *
     * The defined start_time from the input is set according to the PCR
     * but the first PTS received by an output is likely later than this
     * clock time, so the real output start_time must be shifted to keep
     * intra-media synchronization in acceptable levels.
     *
     *    |    Start     First audio    First video
     *    |    time      packet PTS     packet PTS
     *    |      v            v            v
     *    |      x------------x------------x----------> media time
     *    |
     *    |      |------------>
     *    |      Audio PCR delay
     *    |
     *    |      |------------------------->
     *    |      Video PCR delay
     *    |
     *    | Fig.2: Media time representation of the clock startup
     *
     * If the pts-delay is very low, and if the PCR delay is very small,
     * an additional output_dejitter is used to offset the beginning of
     * the playback in a uniform manner, to let some time for the outputs
     * to start. It only makes sense when one of the output will drive
     * the bus clock.
     **/


    vlc_tick_t pcr_delay = (first_ts - context->start_time.stream) / context->rate
                         + context->start_time.system - start_date;

    if (pcr_delay > MAX_PCR_DELAY)
    {
        if (main_clock->logger != NULL)
            vlc_error(main_clock->logger, "Invalid PCR delay ! Ignoring it...");
        pcr_delay = 0;
    }

    const vlc_tick_t input_delay = main_clock->input_dejitter + pcr_delay;

    const vlc_tick_t delay =
        __MAX(input_delay, main_clock->output_dejitter);

    main_clock->wait_sync_ref_priority = clock->priority;
    context->wait_sync_ref = clock_point_Create(start_date + delay, first_ts);
}

void vlc_clock_Lock(vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_lock(&main_clock->lock);
}

void vlc_clock_Unlock(vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_unlock(&main_clock->lock);
}

static inline void AssertLocked(const vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_assert(&main_clock->lock);
}

bool vlc_clock_IsPaused(const vlc_clock_t *clock)
{
    AssertLocked(clock);

    vlc_clock_main_t *main_clock = clock->owner;
    return main_clock->paused;
}

int vlc_clock_Wait(vlc_clock_t *clock, vlc_tick_t deadline)
{
    AssertLocked(clock);

    vlc_clock_main_t *main_clock = clock->owner;
    return vlc_cond_timedwait(&main_clock->cond, &main_clock->lock, deadline);
}

void vlc_clock_Wake(vlc_clock_t *clock)
{
    AssertLocked(clock);

    vlc_clock_main_t *main_clock = clock->owner;
    vlc_cond_broadcast(&main_clock->cond);
}

vlc_clock_main_t *vlc_clock_main_New(struct vlc_logger *parent_logger, struct vlc_tracer *parent_tracer)
{
    vlc_clock_main_t *main_clock = malloc(sizeof(vlc_clock_main_t));

    if (main_clock == NULL)
        return NULL;

    struct vlc_clock_context *ctx = main_clock->context = context_new();
    if (unlikely(ctx == NULL))
    {
        free(main_clock);
        return NULL;
    }

    main_clock->logger = vlc_LogHeaderCreate(parent_logger, "clock");
    main_clock->tracer = parent_tracer;

    vlc_mutex_init(&main_clock->lock);
    vlc_cond_init(&main_clock->cond);
    main_clock->input_master = main_clock->master = NULL;
    main_clock->rc = 1;

    main_clock->delay = 0;

    main_clock->first_pcr =
        clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
    main_clock->wait_sync_ref_priority = UINT_MAX;

    ctx->start_time =
        clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);

    main_clock->paused = false;
    main_clock->pause_date = VLC_TICK_INVALID;
    main_clock->input_dejitter = DEFAULT_PTS_DELAY;
    main_clock->output_dejitter = AOUT_MAX_PTS_ADVANCE * 2;

    AvgInit(&main_clock->coeff_avg, 10);
    AvgResetAndFill(&main_clock->coeff_avg, ctx->coeff);

    vlc_vector_init(&main_clock->listeners);
    vlc_list_init(&main_clock->prev_contexts);

    return main_clock;
}

void vlc_clock_main_Reset(vlc_clock_main_t *main_clock)
{
    vlc_mutex_assert(&main_clock->lock);

    vlc_clock_main_reset(main_clock);

    main_clock->first_pcr =
        clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
}

void vlc_clock_main_SetFirstPcr(vlc_clock_main_t *main_clock,
                                vlc_tick_t system_now, vlc_tick_t ts)
{
    vlc_mutex_assert(&main_clock->lock);
    struct vlc_clock_context *ctx = main_clock->context;

    /* Keep the old context if it allows to convert via master or monotonic */
    if (main_clock->first_pcr.system != VLC_TICK_INVALID
     && (ctx->last.system != VLC_TICK_INVALID ||
         ctx->wait_sync_ref.stream != VLC_TICK_INVALID)
     && !vlc_list_is_empty(&ctx->using_clocks))
    {
        struct vlc_clock_context *newctx = context_new();
        if (newctx != NULL)
        {
            newctx->clock_id = ctx->clock_id + 1;
            vlc_list_append(&ctx->node, &main_clock->prev_contexts);
            main_clock->context = ctx = newctx;
        }
        vlc_warning(main_clock->logger, "new clock context(%u) @%"PRId64,
                    ctx->clock_id, ts);
    }
    vlc_clock_main_reset(main_clock);

    main_clock->first_pcr = clock_point_Create(system_now, ts);
    main_clock->wait_sync_ref_priority = UINT_MAX;
    ctx->wait_sync_ref =
        clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
}

void vlc_clock_main_SetInputDejitter(vlc_clock_main_t *main_clock,
                                     vlc_tick_t delay)
{
    vlc_mutex_assert(&main_clock->lock);

    main_clock->input_dejitter = delay;
}

void vlc_clock_main_SetDejitter(vlc_clock_main_t *main_clock,
                                vlc_tick_t dejitter)
{
    vlc_mutex_assert(&main_clock->lock);

    main_clock->output_dejitter = dejitter;
}

void vlc_clock_main_ChangePause(vlc_clock_main_t *main_clock, vlc_tick_t now,
                                bool paused)
{
    vlc_mutex_assert(&main_clock->lock);

    assert(paused != main_clock->paused);
    main_clock->paused = paused;

    if (paused)
    {
        main_clock->pause_date = now;
        return;
    }

    /* Reset was called before resume from Pause */
    if (main_clock->pause_date == VLC_TICK_INVALID)
        return;

    struct vlc_clock_context *ctx = main_clock->context;
    /**
     * Only apply a delay if the clock has a reference point to avoid
     * messing up the timings if the stream was paused then seeked
     */

    const vlc_tick_t delay = now - main_clock->pause_date;
    if (ctx->last.system != VLC_TICK_INVALID)
    {
        ctx->last.system += delay;
        if (ctx->start_time.system == VLC_TICK_INVALID)
            ctx->offset += delay;
    }
    if (main_clock->first_pcr.system != VLC_TICK_INVALID)
        main_clock->first_pcr.system += delay;
    if (ctx->start_time.system != VLC_TICK_INVALID)
        ctx->start_time.system += delay;
    if (ctx->wait_sync_ref.system != VLC_TICK_INVALID)
        ctx->wait_sync_ref.system += delay;
    main_clock->pause_date = VLC_TICK_INVALID;
    vlc_cond_broadcast(&main_clock->cond);
}

void vlc_clock_main_Delete(vlc_clock_main_t *main_clock)
{
    assert(main_clock->rc == 1);
    if (main_clock->logger != NULL)
        vlc_LogDestroy(main_clock->logger);

    assert(main_clock->listeners.size == 0);
    vlc_vector_destroy(&main_clock->listeners);

    assert(vlc_list_is_empty(&main_clock->prev_contexts));

    assert(vlc_list_is_empty(&main_clock->context->using_clocks));
    free(main_clock->context);

    free(main_clock);
}

void vlc_clock_main_Lock(vlc_clock_main_t *main_clock)
{
    vlc_mutex_lock(&main_clock->lock);
}

void vlc_clock_main_Unlock(vlc_clock_main_t *main_clock)
{
    vlc_mutex_assert(&main_clock->lock);
    vlc_mutex_unlock(&main_clock->lock);
}

vlc_tick_t vlc_clock_Update(vlc_clock_t *clock, vlc_tick_t system_now,
                            vlc_tick_t ts, double rate)
{
    AssertLocked(clock);

    vlc_clock_main_t *main_clock = clock->owner;
    struct vlc_clock_context *ctx;

    if (main_clock->first_pcr.system != VLC_TICK_INVALID)
        ctx = vlc_clock_get_context(clock, system_now, ts, true);
    else
        ctx = clock->context;

    return clock->ops->update(clock, ctx, system_now, ts, rate, 0, 0);
}

vlc_tick_t vlc_clock_UpdateVideo(vlc_clock_t *clock, vlc_tick_t system_now,
                                 vlc_tick_t ts, double rate,
                                 unsigned frame_rate, unsigned frame_rate_base)
{
    AssertLocked(clock);
    vlc_clock_main_t *main_clock = clock->owner;
    struct vlc_clock_context *ctx;

    if (main_clock->first_pcr.system != VLC_TICK_INVALID)
        ctx = vlc_clock_get_context(clock, system_now, ts, true);
    else
        ctx = clock->context;

    return clock->ops->update(clock, ctx, system_now, ts, rate,
                              frame_rate, frame_rate_base);
}

void vlc_clock_Reset(vlc_clock_t *clock)
{
    AssertLocked(clock);
    clock->ops->reset(clock);
}

vlc_tick_t vlc_clock_SetDelay(vlc_clock_t *clock, vlc_tick_t delay)
{
    AssertLocked(clock);
    return clock->ops->set_delay(clock, delay);
}

vlc_tick_t vlc_clock_ConvertToSystem(vlc_clock_t *clock,
                                     vlc_tick_t system_now, vlc_tick_t ts,
                                     double rate, uint32_t *clock_id)
{
    AssertLocked(clock);
    struct vlc_clock_context *ctx =
        vlc_clock_get_context(clock, system_now, ts, false);
    if (clock_id != NULL)
        *clock_id = ctx->clock_id;

    clock->last_conversion =
        clock->ops->to_system(clock, ctx, system_now, ts, rate);
    return clock->last_conversion;
}

static const struct vlc_clock_ops master_ops = {
    .update = vlc_clock_master_update,
    .reset = vlc_clock_master_reset,
    .set_delay = vlc_clock_master_set_delay,
    .to_system = vlc_clock_master_to_system,
    .start = vlc_clock_output_start,
};

static const struct vlc_clock_ops slave_ops = {
    .update = vlc_clock_slave_update,
    .reset = vlc_clock_slave_reset,
    .set_delay = vlc_clock_slave_set_delay,
    .to_system = vlc_clock_slave_to_system,
    .start = vlc_clock_output_start,
};

static const struct vlc_clock_ops input_master_ops = {
    .update = vlc_clock_master_update,
    .reset = vlc_clock_input_reset,
    .set_delay = vlc_clock_master_set_delay,
    .to_system = vlc_clock_master_to_system,
    .start = vlc_clock_input_start,
};

static const struct vlc_clock_ops input_slave_ops = {
    .update = vlc_clock_slave_update,
    .reset = vlc_clock_input_reset,
    .set_delay = vlc_clock_slave_set_delay,
    .to_system = vlc_clock_slave_to_system,
    .start = vlc_clock_input_start,
};

static vlc_clock_t *vlc_clock_main_Create(vlc_clock_main_t *main_clock,
                                          const char* track_str_id,
                                          bool input, unsigned priority,
                                          const struct vlc_clock_cbs *cbs,
                                          void *cbs_data)
{
    vlc_clock_t *clock = malloc(sizeof(vlc_clock_t));
    if (clock == NULL)
        return NULL;

    clock->owner = main_clock;
    clock->track_str_id = track_str_id;
    clock->delay = 0;
    clock->cbs = cbs;
    clock->cbs_data = cbs_data;
    clock->priority = priority;
    assert(!cbs || cbs->on_update);
    clock->last_conversion = VLC_TICK_INVALID;

    if (input)
    {
        /* Always use the main one */
        vlc_clock_attach_context(clock, main_clock->context);
        return clock;
    }

    /* Attach the clock to the first context or the main one */
    struct vlc_clock_context *ctx =
        vlc_list_first_entry_or_null(&main_clock->prev_contexts,
                                        struct vlc_clock_context, node);
    if (likely(ctx == NULL))
        ctx = main_clock->context;
    vlc_clock_attach_context(clock, ctx);
    return clock;
}

vlc_clock_t *vlc_clock_main_CreateMaster(vlc_clock_main_t *main_clock,
                                         const char *track_str_id,
                                         const struct vlc_clock_cbs *cbs,
                                         void *cbs_data)
{
    vlc_mutex_assert(&main_clock->lock);

    /* The master has always the 0 priority */
    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, track_str_id, false,
                                               0, cbs, cbs_data);
    if (!clock)
        return NULL;

    assert(main_clock->master == NULL);

    if (main_clock->input_master == NULL)
        clock->ops = &master_ops;
    else
        clock->ops = &slave_ops;

    main_clock->master = clock;
    main_clock->rc++;

    return clock;
}

vlc_clock_t *vlc_clock_main_CreateInputMaster(vlc_clock_main_t *main_clock)
{
    vlc_mutex_assert(&main_clock->lock);
    struct vlc_clock_context *ctx = main_clock->context;

    /* The master has always the 0 priority */
    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, "input", true,
                                               0, NULL, NULL);
    if (!clock)
        return NULL;

    assert(main_clock->input_master == NULL);

    /* Even if the master ES clock has already been created, it should not
     * have updated any points */

    assert(ctx->offset == 0); (void) ctx;

    /* Override the master ES clock if it exists */
    if (main_clock->master != NULL)
        main_clock->master->ops = &slave_ops;

    clock->ops = &input_master_ops;
    main_clock->input_master = clock;
    main_clock->rc++;

    return clock;
}

vlc_clock_t *vlc_clock_main_CreateInputSlave(vlc_clock_main_t *main_clock)
{
    vlc_mutex_assert(&main_clock->lock);

    /* The input has always the 0 priority */
    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, "input", true,
                                               0, NULL, NULL);
    if (!clock)
        return NULL;

    clock->ops = &input_slave_ops;
    main_clock->rc++;

    return clock;
}

vlc_clock_t *vlc_clock_main_CreateSlave(vlc_clock_main_t *main_clock,
                                        const char* track_str_id,
                                        enum es_format_category_e cat,
                                        const struct vlc_clock_cbs *cbs,
                                        void *cbs_data)
{
    vlc_mutex_assert(&main_clock->lock);

    /* SPU outputs should have lower priority than VIDEO outputs since they
     * necessarily depend on a VIDEO output. This mean that a SPU reference
     * point will always be overridden by AUDIO or VIDEO outputs. Cf.
     * vlc_clock_monotonic_to_system */

    unsigned priority;
    switch (cat)
    {
        case VIDEO_ES:
        case AUDIO_ES:
            priority = 1;
            break;
        case SPU_ES:
        default:
            priority = 2;
            break;
    }

    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, track_str_id, false,
                                               priority, cbs, cbs_data);
    if (!clock)
        return NULL;

    clock->ops = &slave_ops;
    main_clock->rc++;

    return clock;
}

vlc_clock_t *vlc_clock_CreateSlave(const vlc_clock_t *clock,
                                   enum es_format_category_e cat)
{
    return vlc_clock_main_CreateSlave(clock->owner, clock->track_str_id, cat, NULL, NULL);
}

void vlc_clock_Delete(vlc_clock_t *clock)
{
    vlc_clock_main_t *main_clock = clock->owner;
    vlc_mutex_lock(&main_clock->lock);

    if (clock->context != NULL)
        vlc_clock_remove_current_context(clock);

    if (clock == main_clock->input_master)
    {
        /* The input master must be the last clock to be deleted */
        assert(main_clock->rc == 2);
    }
    else if (clock == main_clock->master)
    {
        /* Don't reset the main clock if the master has been overridden by the
         * input master */

        if (main_clock->input_master != NULL)
            vlc_clock_main_reset(main_clock);
        main_clock->master = NULL;
    }
    main_clock->rc--;
    vlc_mutex_unlock(&main_clock->lock);
    free(clock);
}

void vlc_clock_Start(vlc_clock_t *clock,
                     vlc_tick_t start_date,
                     vlc_tick_t first_ts)
{
    clock->ops->start(clock, start_date, first_ts);
}

Messung
CHZG
849510093

¤ Dauer der Verarbeitung: 0.10 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.