diff --git a/src/clock/clock.c b/src/clock/clock.c index c1fdc02d5ae59eb8b36a3d743d02f8eb23383a89..891634401eb01347997377669081996f2eb6d778 100644 --- a/src/clock/clock.c +++ b/src/clock/clock.c @@ -55,6 +55,12 @@ struct vlc_clock_context 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; }; @@ -83,6 +89,7 @@ struct vlc_clock_main_t 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 */ @@ -99,6 +106,21 @@ struct vlc_clock_ops 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 @@ -201,13 +223,30 @@ static inline void TraceRender(struct vlc_tracer *tracer, const char *type, 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 = VLC_TICK_INVALID; + 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) @@ -231,7 +270,10 @@ static vlc_tick_t context_stream_to_system(const struct vlc_clock_context *ctx, { if (ctx->last.system == VLC_TICK_INVALID) return VLC_TICK_INVALID; - return ((vlc_tick_t) (ts * ctx->coeff / ctx->rate)) + ctx->offset; + + return ((vlc_tick_t) ((ts - ctx->start_time.stream) * ctx->coeff / ctx->rate)) + + ctx->offset + + ctx->start_time.system; } static void @@ -373,7 +415,6 @@ static inline void vlc_clock_on_update(vlc_clock_t *clock, 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) @@ -420,7 +461,9 @@ static void vlc_clock_master_update_coeff( /* Reset and continue (calculate the offset from the * current point) */ - vlc_clock_main_reset(main_clock); + ctx->coeff = 1.f; + AvgResetAndFill(&main_clock->coeff_avg, 1.); + ctx->offset = ComputeOffset(main_clock, ctx, system_now, ts, rate); } else { @@ -438,7 +481,7 @@ static void vlc_clock_master_update_coeff( vlc_clock_SendEvent(main_clock, discontinuity); } - ctx->offset = system_now - ((vlc_tick_t) (ts * ctx->coeff / rate)); + 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, @@ -480,7 +523,7 @@ static vlc_tick_t vlc_clock_master_update(vlc_clock_t *clock, if (clock->delay > 0 && main_clock->delay < 0 && ts > -main_clock->delay) ts += main_clock->delay; - vlc_tick_t drift = VLC_TICK_INVALID; + vlc_tick_t drift = 0; vlc_clock_on_update(clock, system_now, ts, drift, rate, frame_rate, frame_rate_base); return drift; @@ -499,7 +542,6 @@ static void vlc_clock_master_reset(vlc_clock_t *clock) if (clock->context != NULL && clock->context != ctx) vlc_clock_switch_context(clock, ctx); - vlc_clock_main_reset(main_clock); assert(main_clock->delay <= 0); assert(clock->delay >= 0); @@ -547,6 +589,79 @@ static vlc_tick_t vlc_clock_master_set_delay(vlc_clock_t *clock, vlc_tick_t dela 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) @@ -554,16 +669,20 @@ vlc_clock_monotonic_to_system(vlc_clock_t *clock, struct vlc_clock_context *ctx, vlc_clock_main_t *main_clock = clock->owner; if (clock->priority < main_clock->wait_sync_ref_priority - && ctx == main_clock->context) + && 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 = - main_clock->first_pcr.system == VLC_TICK_INVALID ? 0 : - (ts - main_clock->first_pcr.stream) / rate + - main_clock->first_pcr.system - now; + 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) { @@ -581,7 +700,6 @@ vlc_clock_monotonic_to_system(vlc_clock_t *clock, struct vlc_clock_context *ctx, ctx->wait_sync_ref = clock_point_Create(now + delay, ts); } - assert(ctx->wait_sync_ref.stream != VLC_TICK_INVALID); return (ts - ctx->wait_sync_ref.stream) / rate + ctx->wait_sync_ref.system; } @@ -627,6 +745,8 @@ static vlc_tick_t vlc_clock_slave_update(vlc_clock_t *clock, 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 @@ -636,9 +756,19 @@ static vlc_tick_t vlc_clock_slave_update(vlc_clock_t *clock, return VLC_TICK_MAX; } - vlc_tick_t computed = clock->ops->to_system(clock, ctx, system_now, ts, rate); + 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_tick_t drift = computed - system_now; vlc_clock_on_update(clock, computed, ts, drift, rate, frame_rate, frame_rate_base); return drift; @@ -653,9 +783,6 @@ static void vlc_clock_slave_reset(vlc_clock_t *clock) if (clock->context != NULL && clock->context != ctx) vlc_clock_switch_context(clock, ctx); - main_clock->wait_sync_ref_priority = UINT_MAX; - ctx->wait_sync_ref = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID); - vlc_clock_on_update(clock, VLC_TICK_INVALID, VLC_TICK_INVALID, VLC_TICK_INVALID, 1.0f, 0, 0); } @@ -670,6 +797,111 @@ static vlc_tick_t vlc_clock_slave_set_delay(vlc_clock_t *clock, vlc_tick_t delay 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; @@ -740,6 +972,9 @@ vlc_clock_main_t *vlc_clock_main_New(struct vlc_logger *parent_logger, struct vl 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->pause_date = VLC_TICK_INVALID; main_clock->input_dejitter = DEFAULT_PTS_DELAY; main_clock->output_dejitter = AOUT_MAX_PTS_ADVANCE * 2; @@ -831,10 +1066,13 @@ void vlc_clock_main_ChangePause(vlc_clock_main_t *main_clock, vlc_tick_t now, if (ctx->last.system != VLC_TICK_INVALID) { ctx->last.system += delay; - ctx->offset += 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; @@ -873,8 +1111,14 @@ vlc_tick_t vlc_clock_Update(vlc_clock_t *clock, vlc_tick_t system_now, vlc_tick_t ts, double rate) { AssertLocked(clock); - struct vlc_clock_context *ctx = - vlc_clock_get_context(clock, system_now, ts, true); + + 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); } @@ -884,8 +1128,13 @@ vlc_tick_t vlc_clock_UpdateVideo(vlc_clock_t *clock, vlc_tick_t system_now, unsigned frame_rate, unsigned frame_rate_base) { AssertLocked(clock); - struct vlc_clock_context *ctx = - vlc_clock_get_context(clock, system_now, ts, true); + 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); @@ -923,6 +1172,7 @@ static const struct vlc_clock_ops master_ops = { .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 = { @@ -930,6 +1180,23 @@ static const struct vlc_clock_ops slave_ops = { .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, @@ -949,21 +1216,22 @@ static vlc_clock_t *vlc_clock_main_Create(vlc_clock_main_t *main_clock, clock->cbs_data = cbs_data; clock->priority = priority; assert(!cbs || cbs->on_update); - clock->last_conversion = 0; + clock->last_conversion = VLC_TICK_INVALID; if (input) - clock->context = NULL; /* Always use the main one */ - else { - /* 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); + /* 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; } @@ -1008,13 +1276,13 @@ vlc_clock_t *vlc_clock_main_CreateInputMaster(vlc_clock_main_t *main_clock) /* Even if the master ES clock has already been created, it should not * have updated any points */ - assert(ctx->offset == VLC_TICK_INVALID); (void) ctx; + 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 = &master_ops; + clock->ops = &input_master_ops; main_clock->input_master = clock; main_clock->rc++; @@ -1031,7 +1299,7 @@ vlc_clock_t *vlc_clock_main_CreateInputSlave(vlc_clock_main_t *main_clock) if (!clock) return NULL; - clock->ops = &slave_ops; + clock->ops = &input_slave_ops; main_clock->rc++; return clock; @@ -1104,3 +1372,10 @@ void vlc_clock_Delete(vlc_clock_t *clock) 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); +} diff --git a/src/clock/clock.h b/src/clock/clock.h index ddc9c0e8b29f2217f18af8928de1cc48e17dd0fa..7b0557096fde9704e99aa6a611e15e00cc3cbb38 100644 --- a/src/clock/clock.h +++ b/src/clock/clock.h @@ -330,4 +330,16 @@ 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); +/** + * Starts a new clock based on the given clock point, accounting for + * previous updates. + * + * @param clock an input or output clock that is being started + * @param start_date the system time at which the first update starts + * @param first_ts the media time origin + **/ +void vlc_clock_Start(vlc_clock_t *clock, + vlc_tick_t start_date, + vlc_tick_t first_ts); + #endif /*CLOCK_H*/ diff --git a/src/input/decoder.c b/src/input/decoder.c index 9ba55bed3fa64d826efd59dc5a7be8ddea7fd050..beb714f5ae8f209ec60563d09eddb9f756eee445 100644 --- a/src/input/decoder.c +++ b/src/input/decoder.c @@ -209,6 +209,7 @@ struct vlc_input_decoder_t bool b_waiting; bool b_first; bool b_has_data; + bool out_started; /* Flushing */ bool flushing; @@ -1023,7 +1024,7 @@ static void RequestReload( vlc_input_decoder_t *p_owner ) atomic_compare_exchange_strong( &p_owner->reload, &expected, RELOAD_DECODER ); } -static int DecoderWaitUnblock( vlc_input_decoder_t *p_owner ) +static int DecoderWaitUnblock(vlc_input_decoder_t *p_owner, vlc_tick_t date) { struct vlc_tracer *tracer = vlc_object_get_tracer(VLC_OBJECT(&p_owner->dec)); vlc_fifo_Assert(p_owner->p_fifo); @@ -1036,16 +1037,19 @@ static int DecoderWaitUnblock( vlc_input_decoder_t *p_owner ) vlc_cond_signal( &p_owner->wait_acknowledge ); } - bool did_wait = false; while (p_owner->b_waiting && p_owner->b_has_data && !p_owner->flushing) - { vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_request); - /* We should not start the clock if we ended up flushing. */ - did_wait = !p_owner->flushing; - } - if (tracer != NULL && did_wait) - vlc_tracer_TraceEvent(tracer, "DEC", p_owner->psz_id, "stop wait"); + if (!p_owner->out_started) + { + p_owner->out_started = true; + if (tracer != NULL) + vlc_tracer_TraceEvent(tracer, "DEC", p_owner->psz_id, "stop wait"); + + vlc_clock_Lock(p_owner->p_clock); + vlc_clock_Start(p_owner->p_clock, vlc_tick_now(), date); + vlc_clock_Unlock(p_owner->p_clock); + } if (p_owner->flushing) { @@ -1364,12 +1368,13 @@ static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_pi } else { - int ret = DecoderWaitUnblock(p_owner); + int ret = DecoderWaitUnblock(p_owner, p_picture->date); if (ret != VLC_SUCCESS) { picture_Release(p_picture); return ret; } + } if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) ) @@ -1506,7 +1511,7 @@ static int ModuleThread_PlayAudio( vlc_input_decoder_t *p_owner, vlc_frame_t *p_ vlc_aout_stream_Flush( p_astream ); } - int ret = DecoderWaitUnblock(p_owner); + int ret = DecoderWaitUnblock(p_owner, p_audio->i_pts); if (ret != VLC_SUCCESS) { block_Release(p_audio); @@ -1573,7 +1578,7 @@ static void ModuleThread_PlaySpu( vlc_input_decoder_t *p_owner, subpicture_t *p_ } /* */ - int ret = DecoderWaitUnblock(p_owner); + int ret = DecoderWaitUnblock(p_owner, p_subpic->i_start); if (ret != VLC_SUCCESS || p_subpic->i_start == VLC_TICK_INVALID) { @@ -1813,6 +1818,7 @@ static void *DecoderThread( void *p_data ) * is called again. This will avoid a second useless flush (but * harmless). */ p_owner->flushing = false; + p_owner->out_started = false; p_owner->i_preroll_end = PREROLL_NONE; continue; } @@ -1976,6 +1982,7 @@ CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg ) p_owner->b_waiting = false; p_owner->b_first = true; p_owner->b_has_data = false; + p_owner->out_started = false; p_owner->error = false; diff --git a/src/input/es_out.c b/src/input/es_out.c index 6ab2433d1889fc1b4c89b68ee24db210995eace3..cdf56ce699aac755b68a2076c4cc6acd8ddb8b39 100644 --- a/src/input/es_out.c +++ b/src/input/es_out.c @@ -960,7 +960,10 @@ static void EsOutChangePosition(es_out_sys_t *p_sys, bool b_flush, vlc_list_foreach(pgrm, &p_sys->programs, node) { input_clock_Reset(pgrm->p_input_clock); + vlc_clock_main_Lock(pgrm->clocks.main); pgrm->i_last_pcr = VLC_TICK_INVALID; + vlc_clock_Reset(pgrm->clocks.input); + vlc_clock_main_Unlock(pgrm->clocks.main); } p_sys->b_buffering = true; @@ -1070,10 +1073,9 @@ static void EsOutDecodersStopBuffering(es_out_sys_t *p_sys, bool b_forced) EsOutStopFreeVout(p_sys); /* */ - const vlc_tick_t i_wakeup_delay = VLC_TICK_FROM_MS(10); /* FIXME CLEANUP thread wake up time*/ const vlc_tick_t i_current_date = p_sys->b_paused ? p_sys->i_pause_date : vlc_tick_now(); - const vlc_tick_t update = i_current_date + i_wakeup_delay - i_buffering_duration; + const vlc_tick_t update = i_current_date - i_buffering_duration; /* The call order of these 3 input_clock_t/vlc_clock_main_t functions is * important: diff --git a/test/Makefile.am b/test/Makefile.am index 0ae5a58814b12aa809ec67fb9bf408d0ad5f4f69..17fbb3a3fdbc02bcad7b2af1405074f514f5d8a8 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -25,6 +25,7 @@ check_PROGRAMS = \ test_libvlc_slaves \ test_src_config_chain \ test_src_clock_clock \ + test_src_clock_start \ test_src_misc_ancillary \ test_src_misc_variables \ test_src_input_stream \ @@ -166,6 +167,11 @@ 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_clock_start_SOURCES = src/clock/clock_start.c \ + ../src/clock/clock.c \ + ../src/clock/clock_internal.c +test_src_clock_start_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 diff --git a/test/libvlc/test.h b/test/libvlc/test.h index 7ffadf4ba867e14ac39afbb91fe1a968938c7560..aba7521773247d9538826f9a994f18a271007b94 100644 --- a/test/libvlc/test.h +++ b/test/libvlc/test.h @@ -61,7 +61,7 @@ static const char test_default_sample[] = "mock://"; * Some useful common functions */ -#define test_log( ... ) printf( "testapi: " __VA_ARGS__ ); +#define test_log( ... ) fprintf(stderr, "testapi: " __VA_ARGS__); static inline void test_setup(void) { diff --git a/test/src/clock/clock.c b/test/src/clock/clock.c index 349ff02f9a93115180f9de25a0c0527d71be1d2f..d1c9644bcd6bd1225e72f74a736ba3c4d2b90069 100644 --- a/test/src/clock/clock.c +++ b/test/src/clock/clock.c @@ -57,10 +57,11 @@ struct clock_scenario CLOCK_SCENARIO_RUN, } type; + vlc_tick_t stream_start; + vlc_tick_t system_start; /* VLC_TICK_INVALID for vlc_tick_now() */ + union { struct { - 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 */ unsigned video_fps; @@ -84,6 +85,7 @@ struct clock_ctx vlc_clock_main_t *mainclk; vlc_clock_t *master; vlc_clock_t *slave; + vlc_clock_t *input; vlc_tick_t system_start; vlc_tick_t stream_start; @@ -328,6 +330,9 @@ static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer, assert(mainclk != NULL); vlc_clock_main_Lock(mainclk); + vlc_clock_t *input = vlc_clock_main_CreateInputSlave(mainclk); + assert(input != NULL); + vlc_clock_t *master = vlc_clock_main_CreateMaster(mainclk, scenario->name, NULL, NULL); assert(master != NULL); @@ -352,10 +357,16 @@ static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer, system_start = scenario->system_start; stream_start = scenario->stream_start; } + + vlc_clock_Start(input, system_start, stream_start); + + if (scenario->type == CLOCK_SCENARIO_UPDATE) + vlc_clock_Start(master, system_start, stream_start); vlc_clock_main_Unlock(mainclk); const struct clock_ctx ctx = { .mainclk = mainclk, + .input = input, .master = master, .slave = slave, .scenario = scenario, @@ -420,6 +431,7 @@ static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer, end: vlc_clock_Delete(master); vlc_clock_Delete(slave); + vlc_clock_Delete(input); free(slave_name); vlc_clock_main_Delete(mainclk); } @@ -545,8 +557,7 @@ static void normal_check(const struct clock_ctx *ctx, size_t update_count, { case TRACER_EVENT_TYPE_UPDATE: assert(event.update.coeff == 1.0f); - assert(event.update.offset == - scenario->system_start - scenario->stream_start); + assert(event.update.offset == 0); break; case TRACER_EVENT_TYPE_RENDER_VIDEO: if (last_video_date != VLC_TICK_INVALID) @@ -563,6 +574,8 @@ static void normal_check(const struct clock_ctx *ctx, size_t update_count, vlc_clock_ConvertToSystem(ctx->slave, expected_system_end, stream_end, 1.0f, NULL); vlc_clock_Unlock(ctx->slave); + fprintf(stderr, "%s:%d: converted=%" PRId64 " == expected_system_end=%" PRId64 "\n", + __func__, __LINE__, converted, expected_system_end); assert(converted == expected_system_end); } @@ -678,6 +691,7 @@ static void pause_common(const struct clock_ctx *ctx, vlc_clock_t *updater) vlc_tick_t system = system_start; vlc_clock_Lock(updater); + vlc_clock_Start(updater, ctx->system_start, ctx->stream_start); vlc_clock_Update(updater, system, ctx->stream_start, 1.0f); vlc_clock_Unlock(updater); @@ -722,7 +736,12 @@ static void convert_paused_common(const struct clock_ctx *ctx, vlc_clock_t *upda const vlc_tick_t system_start = ctx->system_start; vlc_tick_t system = system_start; + vlc_clock_main_Lock(ctx->mainclk); + vlc_clock_main_SetFirstPcr(ctx->mainclk, ctx->system_start, ctx->stream_start); + vlc_clock_main_Unlock(ctx->mainclk); + vlc_clock_Lock(updater); + vlc_clock_Start(updater, ctx->system_start, ctx->stream_start); vlc_clock_Update(updater, ctx->system_start, ctx->stream_start, 1.0f); vlc_clock_Unlock(updater); @@ -731,11 +750,14 @@ static void convert_paused_common(const struct clock_ctx *ctx, vlc_clock_t *upda vlc_clock_main_Lock(ctx->mainclk); vlc_clock_main_ChangePause(ctx->mainclk, system, true); vlc_clock_main_Unlock(ctx->mainclk); - system += 1; + system += 100; vlc_clock_Lock(ctx->slave); vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f, NULL); vlc_clock_Unlock(ctx->slave); + + fprintf(stderr, "%s:%d converted=%" PRId64 " == system_start=%" PRId64 "\n", + __func__, __LINE__, converted, system_start); assert(converted == system_start); } @@ -764,7 +786,10 @@ static void contexts_run(const struct clock_ctx *ctx) /* Check that the converted point is valid */ converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context0, 1.0f, &clock_id); + fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 0\n", __func__, __LINE__, clock_id); assert(clock_id == 0); + fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n", + __func__, __LINE__, converted, system); assert(converted == system); vlc_clock_Update(ctx->slave, system, stream_context0, 1.0f); @@ -777,7 +802,9 @@ static void contexts_run(const struct clock_ctx *ctx) /* Check that we can use the new context (or new origin) */ converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context1, 1.0f, &clock_id); + fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 1\n", __func__, __LINE__, clock_id); assert(clock_id == 1); + fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n", __func__, __LINE__, converted, system); assert(converted == system); /* Check that we can still use the old context when converting a point @@ -785,7 +812,10 @@ static void contexts_run(const struct clock_ctx *ctx) converted = vlc_clock_ConvertToSystem(ctx->slave, system, VLC_TICK_FROM_MS(10) + stream_context0, 1.0f, &clock_id); + fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 0\n", __func__, __LINE__, clock_id); assert(clock_id == 0); + fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n", + __func__, __LINE__, converted, system_context0 + VLC_TICK_FROM_MS(10)); assert(converted == system_context0 + VLC_TICK_FROM_MS(10)); /* Update on the newest context will cause previous contexts to be removed */ @@ -799,7 +829,10 @@ static void contexts_run(const struct clock_ctx *ctx) /* Check that we can use the new context (or new origin) */ converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context2, 1.0f, &clock_id); + fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 2\n", __func__, __LINE__, clock_id); assert(clock_id == 2); + fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n", + __func__, __LINE__, converted, system); assert(converted == system); /* Update on the newest context will cause previous contexts to be removed */ vlc_clock_Update(ctx->slave, system, stream_context2, 1.0f); @@ -809,7 +842,10 @@ static void contexts_run(const struct clock_ctx *ctx) system += VLC_TICK_FROM_MS(100); converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context1, 1.0f, &clock_id); + fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 2\n", __func__, __LINE__, clock_id); assert(clock_id == 2); + fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n", + __func__, __LINE__, converted, system_context0); assert(converted != system_context0); vlc_clock_main_Unlock(ctx->mainclk); @@ -894,6 +930,8 @@ static struct clock_scenario clock_scenarios[] = { .desc = "pause + resume is delaying the next conversion", .type = CLOCK_SCENARIO_RUN, .run = master_pause_run, + .system_start = VLC_TICK_FROM_MS(100), + .stream_start = VLC_TICK_FROM_MS(1000), }, { .name = "monotonic_pause", @@ -901,12 +939,16 @@ static struct clock_scenario clock_scenarios[] = { .type = CLOCK_SCENARIO_RUN, .run = monotonic_pause_run, .disable_jitter = true, + .system_start = VLC_TICK_FROM_MS(100), + .stream_start = VLC_TICK_FROM_MS(1000), }, { .name = "master_convert_paused", .desc = "it is possible to convert ts while paused", .type = CLOCK_SCENARIO_RUN, .run = master_convert_paused_run, + .system_start = VLC_TICK_FROM_MS(100), + .stream_start = VLC_TICK_FROM_MS(1000), }, { .name = "monotonic_convert_paused", @@ -914,6 +956,8 @@ static struct clock_scenario clock_scenarios[] = { .type = CLOCK_SCENARIO_RUN, .run = monotonic_convert_paused_run, .disable_jitter = true, + .system_start = VLC_TICK_FROM_MS(100), + .stream_start = VLC_TICK_FROM_MS(1000), }, { .name = "contexts", @@ -921,6 +965,8 @@ static struct clock_scenario clock_scenarios[] = { .type = CLOCK_SCENARIO_RUN, .run = contexts_run, .disable_jitter = true, + .system_start = VLC_TICK_FROM_MS(100), + .stream_start = VLC_TICK_FROM_MS(1000), }, }; diff --git a/test/src/clock/clock_start.c b/test/src/clock/clock_start.c new file mode 100644 index 0000000000000000000000000000000000000000..efdd39c31366ec79d1fe6bbc2f8c17de49c7f677 --- /dev/null +++ b/test/src/clock/clock_start.c @@ -0,0 +1,462 @@ +/***************************************************************************** + * clock/clock_start.c: test for the vlc clock + ***************************************************************************** + * Copyright (C) 2024 Videolabs + * + * Authors: 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_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; + +/** + * Fixture structure containing the clocks used for each test. + */ +struct clock_fixture_simple { + vlc_clock_main_t *main; + vlc_clock_t *input; + vlc_clock_t *master; + vlc_clock_t *slave; +}; + +struct clock_point +{ + vlc_tick_t system; + vlc_tick_t stream; +}; + +/** + * Initialize the fixture. + * + * It must be released using \ref destroy_fixture_simple. + */ +static struct clock_fixture_simple +init_fixture_simple(struct vlc_logger *logger, + bool is_input_master, + vlc_tick_t input_dejitter, + vlc_tick_t output_dejitter) +{ + struct clock_fixture_simple f = { + .main = vlc_clock_main_New(logger, NULL), + }; + assert(f.main != NULL); + + vlc_clock_main_Lock(f.main); + vlc_clock_main_SetInputDejitter(f.main, input_dejitter); + vlc_clock_main_SetDejitter(f.main, output_dejitter); + + if (is_input_master) + { + f.input = vlc_clock_main_CreateInputMaster(f.main); + f.master = f.input; + } + else + { + f.input = vlc_clock_main_CreateInputSlave(f.main); + f.master = vlc_clock_main_CreateMaster(f.main, "Driving", NULL, NULL); + } + assert(f.input != NULL); + assert(f.master != NULL); + + f.slave = vlc_clock_main_CreateSlave(f.main, "Output", AUDIO_ES, + NULL, NULL); + assert(f.slave != NULL); + vlc_clock_main_Unlock(f.main); + + return f; +} + +static void destroy_fixture_simple(struct clock_fixture_simple *f) +{ + vlc_clock_Delete(f->slave); + if (f->master != f->input) + vlc_clock_Delete(f->master); + vlc_clock_Delete(f->input); + vlc_clock_main_Delete(f->main); +} + +/** + * Check that conversion doesn't work as long as clock has not + * been started. + **/ +#if 0 +static void test_clock_without_start(struct vlc_logger *logger) +{ + struct clock_fixture_simple f = init_fixture_simple(logger, true, 0, 0); + + vlc_clock_main_Lock(f.main); + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + { + vlc_clock_Update(f.master, now, now * 1000, 1.); + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL); + //assert(result == VLC_TICK_INVALID); + } + + vlc_clock_Start(f.slave, now, now * 1000); + + { + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL); + assert(result == VLC_TICK_INVALID); + } + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); +} +#endif + +/** + * Check that conversion works and is valid after the clock start. + **/ +static void test_clock_with_start(struct vlc_logger *logger) +{ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, true, 0, 0); + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + + vlc_clock_main_Lock(f.main); + vlc_clock_Update(f.master, now, now * 1000, 1.); + vlc_clock_Start(f.input, now, now * 1000); + vlc_clock_Start(f.slave, now, now * 1000); + + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL); + assert(result != VLC_TICK_INVALID); + assert(result == now); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); +} + +/** + * Check that master clock can start. + **/ +static void test_clock_with_start_master(struct vlc_logger *logger) +{ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0); + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + + vlc_clock_main_Lock(f.main); + vlc_clock_Start(f.input, now, now * 1000); + vlc_clock_Start(f.master, now, now * 1000); + vlc_clock_Start(f.slave, now, now * 1000); + vlc_clock_Update(f.master, now, now * 1000, 1.); + + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL); + assert(result != VLC_TICK_INVALID); + assert(result == now); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); +} + +/** + * Check that conversion works and is valid after the clock start, + * when start is done later. + **/ +static void test_clock_with_output_dejitter(struct vlc_logger *logger) +{ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, true, 0, 0); + vlc_clock_main_Lock(f.main); + + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + vlc_clock_Update(f.master, now, now * 1000, 1.); + vlc_clock_Start(f.input, 2 * now, now * 1000); + vlc_clock_Start(f.slave, 2 * now, now * 1000); + + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL); + fprintf(stderr, "%s:%d: now=%" PRId64 " converted=%" PRId64 "\n", + __func__, __LINE__, now, result); + assert(result != VLC_TICK_INVALID); + assert(result == 2 * now); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); +} + +/** + * Check that clock start is working correctly with an output driving + * the clock bus. + **/ +static void test_clock_with_output_clock_start(struct vlc_logger *logger) +{ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0); + vlc_clock_main_Lock(f.main); + + /* When the input is not driving the clock bus, we should be able to start + * without any reference point since the output will provide it later. */ + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + vlc_clock_Start(f.input, now, now * 1000); + vlc_clock_Start(f.slave, now, now * 1000); + + /* We provide the clock point to the clock bus. The system time should + * approximately match the start date. */ + vlc_clock_Update(f.master, now, now * 1000, 1.); + + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL); + assert(result != VLC_TICK_INVALID); + assert(result == now); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); +} + +/** + * Check that monotonic clock start is working correctly. + **/ +static void test_clock_with_monotonic_clock(struct vlc_logger *logger) +{ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0); + vlc_clock_main_Lock(f.main); + + /* When the input is not driving the clock bus, we should be able to start + * without any reference point since the output will provide it later. */ + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + vlc_clock_Start(f.input, now, now * 1000); + + /* The output wants to start before a reference point is found. */ + vlc_clock_Start(f.slave, now, now * 1000); + + // TODO + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL); + assert(result != VLC_TICK_INVALID); + assert(result == now); + vlc_clock_main_Unlock(f.main); + + + destroy_fixture_simple(&f); +} + +/** + * Check that monotonic clock start accounts for the start date. + **/ +static vlc_tick_t test_clock_with_monotonic_clock_start( + struct vlc_logger *logger, + vlc_tick_t input_dejitter, + vlc_tick_t output_dejitter, + struct clock_point start_point, + struct clock_point output_point, + struct clock_point convert_point +){ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, false, input_dejitter, output_dejitter); + vlc_clock_main_Lock(f.main); + + /* When the input is not driving the clock bus, we should be able to start + * without any reference point since the output will provide it later. */ + vlc_clock_Start(f.input, start_point.system, start_point.stream); + + /* The output wants to start before a reference point is found. */ + vlc_clock_Start(f.slave, output_point.system, output_point.stream); + + vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, convert_point.system, convert_point.stream, 1., NULL); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); + return result; +} + +static vlc_tick_t test_clock_slave_update( + struct vlc_logger *logger, + struct clock_point start_point, + struct clock_point output_point +){ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0); + vlc_clock_main_Lock(f.main); + + /* When the input is not driving the clock bus, we should be able to start + * without any reference point since the output will provide it later. */ + vlc_clock_Start(f.input, start_point.system, start_point.stream); + vlc_clock_Update(f.master, start_point.system, start_point.stream, 1.); + + /* The output wants to start before a reference point is found. */ + vlc_clock_Start(f.slave, output_point.system, output_point.stream); + + vlc_tick_t result = vlc_clock_Update(f.slave, output_point.system, output_point.stream, 1.); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); + return result; +} + +static void test_clock_start_reset( + struct vlc_logger *logger +){ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0); + vlc_clock_main_Lock(f.main); + + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + struct clock_point start_point; + start_point = (struct clock_point){ now, now * 1000 }; + + /* When the input is not driving the clock bus, we should be able to start + * without any reference point since the output will provide it later. */ + vlc_clock_Start(f.input, start_point.system, start_point.stream); + vlc_clock_Update(f.master, start_point.system, start_point.stream, 1.); + vlc_clock_main_Reset(f.main); + + vlc_tick_t result; + result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream, 1., NULL); + //assert(result == VLC_TICK_INVALID); + + start_point.system *= 2; + vlc_clock_Start(f.input, start_point.system, start_point.stream); + vlc_clock_Update(f.master, start_point.system, start_point.stream, 1.); + result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream, 1., NULL); + assert(result == start_point.system); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); +} + +static void test_clock_slave_only( + struct vlc_logger *logger +){ + fprintf(stderr, "%s:\n", __func__); + struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0); + vlc_clock_main_Lock(f.main); + + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + struct clock_point start_point = { now, now * 1000 }; + + /* When the input is not driving the clock bus, we should be able to start + * without any reference point since the output will provide it later. */ + vlc_clock_Start(f.input, start_point.system, start_point.stream); + + vlc_clock_Start(f.slave, start_point.system, start_point.stream); + vlc_tick_t result; + result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream, 1., NULL); + assert(result == start_point.system); + + result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream + 1000, 1., NULL); + assert(result == start_point.system + 1000); + vlc_clock_main_Unlock(f.main); + + destroy_fixture_simple(&f); +} +int main(void) +{ + libvlc_instance_t *libvlc = libvlc_new(0, NULL); + assert(libvlc != NULL); + + libvlc_int_t *vlc = libvlc->p_libvlc_int; + struct vlc_logger *logger = vlc->obj.logger; + + const vlc_tick_t now = VLC_TICK_FROM_MS(1000); + + //test_clock_without_start(logger); + test_clock_with_start(logger); + test_clock_with_start_master(logger); + test_clock_with_output_dejitter(logger); + test_clock_with_output_clock_start(logger); + test_clock_with_monotonic_clock(logger); + + struct clock_point start_point, output_point; + start_point = (struct clock_point){ now, now * 1000 }; + output_point = (struct clock_point){ now, now * 1000 }; + + vlc_tick_t result = test_clock_with_monotonic_clock_start( + logger, 0, 0, start_point, start_point, start_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system); + assert(result == start_point.system); + + /* Monotonic clock must start at the start point. */ + start_point = (struct clock_point){ 2 * now, now * 1000 }; + result = test_clock_with_monotonic_clock_start( + logger, 0, 0, start_point, output_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system); + assert(result == start_point.system); + + /* A point later in the future will be started later also. */ + start_point = (struct clock_point){ now, now * 1000 }; + output_point = (struct clock_point){ now, now * 1000 + 100}; + result = test_clock_with_monotonic_clock_start( + logger, 0, 0, start_point, output_point, output_point); + fprintf(stderr, "result(%" PRId64 ") = start_point.system(%" PRId64 ")\n", result, start_point.system + 100); + assert(result == start_point.system + 100); + + /* Input dejitter and PCR delay are accounted together. */ + result = test_clock_with_monotonic_clock_start( + logger, 30, 0, start_point, output_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 130); + assert(result == start_point.system + 130); + + /* Output dejitter will not account the current PCR delay. */ + result = test_clock_with_monotonic_clock_start( + logger, 0, 100, start_point, output_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 100); + assert(result == start_point.system + 100); + + /* The highest dejitter value is used. */ + result = test_clock_with_monotonic_clock_start( + logger, 33, 55, start_point, output_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 133); + assert(result == start_point.system + 133); + + /* The highest dejitter value is used. */ + result = test_clock_with_monotonic_clock_start( + logger, 33, 555, start_point, output_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 555); + assert(result == start_point.system + 555); + + start_point = (struct clock_point){ now, now * 1000 }; + output_point = (struct clock_point){ now, now * 1000 }; + result = test_clock_slave_update(logger, start_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, (vlc_tick_t)0); + assert(result == 0); + + output_point = (struct clock_point){ now + 10, now * 1000 }; + result = test_clock_slave_update(logger, start_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, (vlc_tick_t)-10); + assert(result == -10); + + output_point = (struct clock_point){ now, now * 1000 + 10 }; + result = test_clock_slave_update(logger, start_point, output_point); + fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, (vlc_tick_t)10); + assert(result == 10); + + test_clock_start_reset(logger); + test_clock_slave_only(logger); + + libvlc_release(libvlc); +} + diff --git a/test/src/meson.build b/test/src/meson.build index 5eac3cab553dba11037a4f8afc915d8bb8ac387e..ecf5c825a1aa8970f2a493616793d117deb17457 100644 --- a/test/src/meson.build +++ b/test/src/meson.build @@ -35,6 +35,16 @@ vlc_tests += { 'link_with' : [libvlc, libvlccore], } +vlc_tests += { + 'name' : 'test_src_clock_start', + 'sources' : files( + 'clock/clock_start.c', + '../../src/clock/clock.c', + '../../src/clock/clock_internal.c'), + 'suite' : ['src', 'test_src'], + 'link_with' : [libvlc, libvlccore], +} + vlc_tests += { 'name' : 'test_src_misc_variables', 'sources' : files('misc/variables.c'),