[vlc-commits] [Git][videolan/vlc][master] 17 commits: transcode: pcr_helper: increase the dummy max delay value

François Cartegnie (@fcartegnie) gitlab at videolan.org
Sat Dec 17 18:32:31 UTC 2022



François Cartegnie pushed to branch master at VideoLAN / VLC


Commits:
954857f3 by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_helper: increase the dummy max delay value

To ensure less false-positive dropped frame inferences. This value is
still a place-holder until I have time to implement a mechanism to ask
decoders/encoders their inner-queue size.

- - - - -
30bfd8c3 by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_helper: rename timestamp variable

To better match its actual use-case.

- - - - -
357e232a by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_helper: fix a typo

- - - - -
c14cf91e by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_helper: handle split frame output

Some encoders have different output frame count than input.
This can be true and completely valid for audio encoders for instance,
where several output frames contains the same amount of samples as the
input frame that entered the encoder. For instance:

```
+-------+    +---------+    +---+    +---+
|   F   | => | Encoder | => | F | -> | F |
+-------+    +---------+    +---+    +---+
```

Fixes #27336 #27492 #27639

- - - - -
74287028 by Alaric Senat at 2022-12-17T18:10:36+00:00
test: add `pcr_helper` unit tests

These add coverage for the previous commit case and will be expanded in
the future.

Refs #27336

- - - - -
d07bfd9b by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_sync: rename last frame variable

It makes it clearer that this variable contains the DTS of input frames
rather than output.

- - - - -
97a66c84 by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_sync: watch frame output DTS

This has become necessary to properly determine if all the frames have
been output and whether to fast forward a PCR value or not.

- - - - -
4a737f61 by Alaric Senat at 2022-12-17T18:10:36+00:00
test: pcr_sync: assert no fast-forward happens in generic tests

- - - - -
8ff9f025 by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_sync: change the PCR fast-forwarding

Fast-forwarding a PCR means that the pcr_sync immediately tells the API
user that he can send it in the case there is no frame currently being
watched.
This happen in three scenarios:
  - The first SetPCR call is always fast-forwarded because no data
    should have entered the pcr_sync yet.
  - The stream has a "data-hole" and no frames enters it (most usual
    case being subtitles) while PCR keeps coming. This ensure PCR values
    are still forwarded even when the frame queue is empty.
  - The encoder has no delay. Ie: A frame is immediately encoded and
    returned. All PCR values must then be forwarded.

The last point here wasn't handled correctly due to bad implementation
and was a blocker for no-delay encoder (subtitles or some audio ones).

Now, we check if the frame input and output are on the same page before
queuing a PCR event and if it's the case, the PCR value is simply
fast-forwarded.

A following commit will add test cases for this usage.

- - - - -
27983e0a by Alaric Senat at 2022-12-17T18:10:36+00:00
test: pcr_sync: add fast-forwarding unit tests

- - - - -
a0d3c822 by Alaric Senat at 2022-12-17T18:10:36+00:00
test: pcr_sync: fix file title

- - - - -
26991374 by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_sync: add a list access helper

Wrap up the very verbose vlc_list accessor to only have to pass head
around.

- - - - -
33d785bb by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_sync: add forgotten check

We must check that the es recorded dts before the pcr has already been
reached by a previous frame output before decreasing the number of es
still holding a non-reached dts.

- - - - -
9cf8b5bd by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: pcr_sync: record last pcr events in es data

Recording the current PCR event being treated by each ES allow to
support tracks being processed far slower than the others.
A typical example is transcoding both video and audio. The video
pipeline will be significantly slower than the audio one causing PCR
events audio-side being reached and marked as "passed" in advance
relatively to the video ones.

By keeping track of the current pcr_event being treated in each ES
metadata, each track is now able to be processed independently from the
others while having the PCR output of the pcr_sync utility still valid
and consistent.

This patch also removes the previous method that was both flawed (using
the invalid `last_dts == VLC_TICK_INVALID` as a triggering condition)
and CPU intensive (crawling up the pcr_events list to check the DTS
every time).

- - - - -
be8a27ef by Alaric Senat at 2022-12-17T18:10:36+00:00
tests: pcr_sync: add a complex test with two tracks

Mimicking a classic audio/video transcoding scenario.

- - - - -
aca20342 by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: forward PCR if no track is transcoding

As the pcr_sync utility should not be used in that case. The pcr_helper
and pcr_sync tools are only signaled frames from tracks that are
transcoded.
This allow a normal PCR flow when transcoding effectively does nothing.

- - - - -
37f12716 by Alaric Senat at 2022-12-17T18:10:36+00:00
transcode: handle first-PCR issues

Previously, we fast-forwarded the PCR values no matter what. This used
to be handy but caused plausible invalid PCR as the fast-forwarded first
PCRs values can't be checked against future encoded DTS.

This patch approach the problem by synthesizing the first PCR as
`VLC_TICK_0` to ensure no following DTS is lower and still giving a
starting point of the stream to the next modules. We also avoid
forwarding PCR values before any input is signaled to the `pcr_sync`
utility, to avoid the exact same issue talked above.

- - - - -


7 changed files:

- modules/stream_out/transcode/pcr_helper.c
- modules/stream_out/transcode/pcr_helper.h
- modules/stream_out/transcode/pcr_sync.c
- modules/stream_out/transcode/transcode.c
- modules/stream_out/transcode/transcode.h
- test/Makefile.am
- test/modules/stream_out/pcr_sync.c


Changes:

=====================================
modules/stream_out/transcode/pcr_helper.c
=====================================
@@ -33,7 +33,9 @@
 struct transcode_track_pcr_helper
 {
     vlc_tick_t max_delay;
-    vlc_tick_t current_media_time;
+    /// Represents the media time held by the frame processing unit.
+    vlc_tick_t held_media_time;
+    vlc_tick_t input_media_time;
     vlc_tick_t last_dts_output;
 
     struct vlc_list delayed_frames_data;
@@ -46,6 +48,7 @@ typedef struct
 {
     vlc_tick_t length;
     vlc_tick_t dts;
+    vlc_tick_t media_time;
     struct vlc_list node;
 } delayed_frame_data_t;
 
@@ -63,8 +66,10 @@ transcode_track_pcr_helper_t *transcode_track_pcr_helper_New(vlc_pcr_sync_t *syn
     }
 
     ret->max_delay = max_delay;
-    ret->current_media_time = 0;
+    ret->held_media_time = 0;
+    ret->input_media_time = 0;
     ret->last_dts_output = VLC_TICK_INVALID;
+
     vlc_list_init(&ret->delayed_frames_data);
     ret->sync_ref = sync_ref;
 
@@ -112,10 +117,12 @@ int transcode_track_pcr_helper_SignalEnteringFrame(transcode_track_pcr_helper_t
     if (unlikely(bdata == NULL))
         return VLC_ENOMEM;
 
+    pcr_helper->input_media_time += frame->i_length;
+    pcr_helper->held_media_time += frame->i_length;
+
     bdata->length = frame->i_length;
     bdata->dts = frame->i_dts;
-
-    pcr_helper->current_media_time += bdata->length;
+    bdata->media_time = pcr_helper->input_media_time;
 
     vlc_pcr_sync_SignalFrame(pcr_helper->sync_ref, pcr_helper->pcr_sync_es_id, frame);
 
@@ -125,7 +132,7 @@ int transcode_track_pcr_helper_SignalEnteringFrame(transcode_track_pcr_helper_t
     // Exceeding this limit usually means the frame was dropped. So in our case, act like it went
     // through.
     // TODO needs to be properly unit-tested.
-    if (pcr_helper->current_media_time > pcr_helper->max_delay)
+    if (pcr_helper->held_media_time > pcr_helper->max_delay)
     {
         delayed_frame_data_t *first_bdata = vlc_list_first_entry_or_null(
             &pcr_helper->delayed_frames_data, delayed_frame_data_t, node);
@@ -136,7 +143,7 @@ int transcode_track_pcr_helper_SignalEnteringFrame(transcode_track_pcr_helper_t
                                 ? pcr
                                 : __MIN(pcr_helper->last_dts_output, first_bdata->dts);
 
-        pcr_helper->current_media_time -= first_bdata->length;
+        pcr_helper->held_media_time -= first_bdata->length;
 
         vlc_list_remove(&first_bdata->node);
         free(first_bdata);
@@ -151,21 +158,29 @@ int transcode_track_pcr_helper_SignalEnteringFrame(transcode_track_pcr_helper_t
 vlc_tick_t transcode_track_pcr_helper_SignalLeavingFrame(transcode_track_pcr_helper_t *pcr_helper,
                                                          const vlc_frame_t *frame)
 {
-    delayed_frame_data_t *frame_data =
-        vlc_list_first_entry_or_null(&pcr_helper->delayed_frames_data, delayed_frame_data_t, node);
+    assert(!vlc_list_is_empty(&pcr_helper->delayed_frames_data));
 
-    assert(frame_data != NULL);
     pcr_helper->last_dts_output = frame->i_dts;
-    pcr_helper->current_media_time -= frame_data->length;
-
-    const vlc_tick_t pcr = transcode_track_pcr_helper_GetFramePCR(pcr_helper, frame_data->dts);
 
-    vlc_list_remove(&frame_data->node);
-    free(frame_data);
+    pcr_helper->held_media_time -= frame->i_length;
+    const vlc_tick_t output_media_time =
+        pcr_helper->input_media_time - pcr_helper->held_media_time;
 
-    if (pcr == VLC_TICK_INVALID)
+    vlc_tick_t pcr = VLC_TICK_INVALID;
+    delayed_frame_data_t *frame_data;
+    vlc_list_foreach(frame_data, &pcr_helper->delayed_frames_data, node)
     {
-        return VLC_TICK_INVALID;
+        if (output_media_time < frame_data->media_time)
+            break;
+
+        const vlc_tick_t current_pcr =
+            transcode_track_pcr_helper_GetFramePCR(pcr_helper, frame_data->dts);
+        if (current_pcr != VLC_TICK_INVALID)
+            pcr = current_pcr;
+
+        vlc_list_remove(&frame_data->node);
+        free(frame_data);
     }
-    return __MIN(frame->i_dts, pcr);
+    return (pcr == VLC_TICK_INVALID) ? VLC_TICK_INVALID
+                                     : __MIN(frame->i_dts, pcr);
 }


=====================================
modules/stream_out/transcode/pcr_helper.h
=====================================
@@ -30,7 +30,7 @@
 /**
  * The transcode PCR helper is a wrapper of the pcr_sync helper specific for processing
  * units that have a high risk of altering the frames timestamps or simply dropping them. The usual
- * case is an encoder and/or a filter chain which is excactly what transcode does.
+ * case is an encoder and/or a filter chain which is exactly what transcode does.
  *
  * This helper uses an approximation of the max frame `delay` taken by the processing unit. When
  * this delay is bypassed, it is assumed that a frame was dropped and eventually a new PCR can be


=====================================
modules/stream_out/transcode/pcr_sync.c
=====================================
@@ -60,14 +60,18 @@ static inline void pcr_event_Delete(pcr_event_t *ev)
 struct es_data
 {
     bool is_deleted;
-    vlc_tick_t last_frame_dts;
+    vlc_tick_t last_input_dts;
+    vlc_tick_t last_output_dts;
     vlc_tick_t discontinuity;
+    pcr_event_t *last_pcr_event;
 };
 
 static inline struct es_data es_data_Init()
 {
-    return (struct es_data){
-        .is_deleted = false, .last_frame_dts = VLC_TICK_INVALID, .discontinuity = VLC_TICK_INVALID};
+    return (struct es_data){.is_deleted = false,
+                            .last_input_dts = VLC_TICK_INVALID,
+                            .last_output_dts = VLC_TICK_INVALID,
+                            .discontinuity = VLC_TICK_INVALID};
 }
 
 struct es_data_vec VLC_VECTOR(struct es_data);
@@ -106,7 +110,7 @@ static bool pcr_sync_ShouldFastForwardPCR(vlc_pcr_sync_t *pcr_sync)
     struct es_data it;
     vlc_vector_foreach(it, &pcr_sync->es_data)
     {
-        if (!it.is_deleted && it.last_frame_dts != VLC_TICK_INVALID)
+        if (!it.is_deleted && it.last_output_dts != it.last_input_dts)
             return false;
     }
     return vlc_list_is_empty(&pcr_sync->pcr_events);
@@ -123,11 +127,11 @@ static bool pcr_sync_HadFrameInputSinceLastPCR(vlc_pcr_sync_t *pcr_sync)
     for (unsigned int i = 0; i < pcr_sync->es_data.size; ++i)
     {
         const struct es_data *curr = &pcr_sync->es_data.data[i];
-        if (curr->is_deleted || curr->last_frame_dts == VLC_TICK_INVALID)
+        if (curr->is_deleted || curr->last_input_dts == VLC_TICK_INVALID)
             continue;
 
         const bool is_oob = i >= pcr_event->es_last_dts_entries.size;
-        if (!is_oob && curr->last_frame_dts != pcr_event->es_last_dts_entries.data[i].dts)
+        if (!is_oob && curr->last_input_dts != pcr_event->es_last_dts_entries.data[i].dts)
             return true;
         else if (is_oob)
             return true;
@@ -160,9 +164,9 @@ static int pcr_sync_SignalPCRLocked(vlc_pcr_sync_t *pcr_sync, vlc_tick_t pcr)
         if (!data.is_deleted)
         {
             vlc_vector_push(&event->es_last_dts_entries,
-                            ((struct es_dts_entry){.dts = data.last_frame_dts,
+                            ((struct es_dts_entry){.dts = data.last_input_dts,
                                                    .discontinuity = data.discontinuity}));
-            if (data.last_frame_dts != VLC_TICK_INVALID)
+            if (data.last_input_dts != VLC_TICK_INVALID)
                 ++entries_left;
             data.discontinuity = VLC_TICK_INVALID;
         }
@@ -193,7 +197,7 @@ void vlc_pcr_sync_SignalFrame(vlc_pcr_sync_t *pcr_sync, unsigned int id, const v
     struct es_data *data = &pcr_sync->es_data.data[id];
     assert(!data->is_deleted);
     if (frame->i_dts != VLC_TICK_INVALID)
-        data->last_frame_dts = frame->i_dts;
+        data->last_input_dts = frame->i_dts;
     if (frame->i_flags & VLC_FRAME_FLAG_DISCONTINUITY)
     {
         assert(frame->i_dts != VLC_TICK_INVALID);
@@ -241,21 +245,23 @@ void vlc_pcr_sync_DelESID(vlc_pcr_sync_t *pcr_sync, unsigned int id)
     vlc_mutex_unlock(&pcr_sync->lock);
 }
 
-static inline void pcr_sync_ResetLastReceivedFrames(vlc_pcr_sync_t *pcr_sync)
-{
-    for (size_t i = 0; i < pcr_sync->es_data.size; ++i)
-    {
-        pcr_sync->es_data.data[i].last_frame_dts = VLC_TICK_INVALID;
-    }
-}
+#define pcr_event_FirstEntry(head)                                                                 \
+    vlc_list_first_entry_or_null(head, pcr_event_t, node)
+#define pcr_event_NextEntry(head, entry)                                                           \
+    vlc_list_next_entry_or_null(head, entry, pcr_event_t, node)
 
 vlc_tick_t
 vlc_pcr_sync_SignalFrameOutput(vlc_pcr_sync_t *pcr_sync, unsigned int id, const vlc_frame_t *frame)
 {
     vlc_mutex_lock(&pcr_sync->lock);
 
-    pcr_event_t *pcr_event = vlc_list_first_entry_or_null(&pcr_sync->pcr_events, pcr_event_t, node);
+    struct es_data *es = &pcr_sync->es_data.data[id];
+    assert(!es->is_deleted);
+    es->last_output_dts = frame->i_dts;
 
+    pcr_event_t *pcr_event = (es->last_pcr_event == NULL)
+                                 ? pcr_event_FirstEntry(&pcr_sync->pcr_events)
+                                 : es->last_pcr_event;
     if (pcr_event == NULL)
         goto no_pcr;
 
@@ -264,44 +270,26 @@ vlc_pcr_sync_SignalFrameOutput(vlc_pcr_sync_t *pcr_sync, unsigned int id, const
     const vlc_tick_t pcr = pcr_event->pcr;
 
     if (pcr_event->no_frame_before)
+    {
+        es->last_pcr_event = pcr_event_NextEntry(&pcr_sync->pcr_events, pcr_event);
         goto return_pcr;
+    }
 
     assert(pcr_event->entries_left != 0);
+
     struct es_dts_entry *dts_entry = &pcr_event->es_last_dts_entries.data[id];
-    const vlc_tick_t last_dts = dts_entry->dts;
 
-    // Handle scenarios where the current track is ahead of the others in a big way.
-    // When it happen, the PCR threshold of the current track has already been reached and we need
-    // to keep checking the DTS with the next pcr_events entries.
-    if (last_dts == VLC_TICK_INVALID)
-    {
-        pcr_event_t *it;
-        vlc_list_foreach(it, &pcr_sync->pcr_events, node)
-        {
-            if (it == pcr_event)
-                continue;
-
-            struct es_dts_entry *entry = &it->es_last_dts_entries.data[id];
-            if (entry->dts == VLC_TICK_INVALID || entry->passed)
-                continue;
-            if (entry->discontinuity != VLC_TICK_INVALID && frame->i_dts > entry->discontinuity)
-                break;
-            if (frame->i_dts < entry->dts)
-                break;
-
-            entry->passed = true;
-            --it->entries_left;
-            assert(it->entries_left != 0);
-        }
-        goto no_pcr;
-    }
     if (dts_entry->discontinuity != VLC_TICK_INVALID && frame->i_dts > dts_entry->discontinuity)
         goto no_pcr;
 
-    if (frame->i_dts < last_dts)
+    if (frame->i_dts < dts_entry->dts)
+        goto no_pcr;
+
+    if (dts_entry->passed)
         goto no_pcr;
 
     dts_entry->passed = true;
+    es->last_pcr_event = pcr_event_NextEntry(&pcr_sync->pcr_events, pcr_event);
     if (--pcr_event->entries_left != 0)
         goto no_pcr;
 
@@ -309,7 +297,6 @@ return_pcr:
     vlc_list_remove(&pcr_event->node);
     pcr_event_Delete(pcr_event);
 
-    pcr_sync_ResetLastReceivedFrames(pcr_sync);
     vlc_mutex_unlock(&pcr_sync->lock);
     return pcr;
 


=====================================
modules/stream_out/transcode/transcode.c
=====================================
@@ -392,6 +392,9 @@ static int Open( vlc_object_t *p_this )
         free( p_sys );
         return VLC_ENOMEM;
     }
+    p_sys->first_pcr_sent = false;
+    p_sys->pcr_sync_has_input = false;
+    p_sys->transcoded_stream_nb = 0u;
 
     /* Audio transcoding parameters */
     transcode_encoder_config_init( &p_sys->aenc_cfg );
@@ -661,9 +664,11 @@ static void *Add( sout_stream_t *p_stream, const es_format_t *p_fmt )
     if( id->b_transcode )
     {
         // TODO properly estimate the delay
-        id->pcr_helper = transcode_track_pcr_helper_New( p_sys->pcr_sync, VLC_TICK_FROM_SEC( 2 ) );
+        id->pcr_helper = transcode_track_pcr_helper_New( p_sys->pcr_sync, VLC_TICK_FROM_SEC( 4 ) );
         if( unlikely( id->pcr_helper == NULL ) )
             goto error;
+
+        ++p_sys->transcoded_stream_nb;
     }
     else
     {
@@ -717,6 +722,7 @@ static void Del( sout_stream_t *p_stream, void *_id )
             break;
         }
         transcode_track_pcr_helper_Delete( id->pcr_helper );
+        --p_sys->transcoded_stream_nb;
     }
     else
         dec_Delete( id->p_decoder );
@@ -745,6 +751,11 @@ static int Send( sout_stream_t *p_stream, void *_id, block_t *p_buffer )
     if( p_buffer != NULL )
     {
         assert( p_buffer->p_next == NULL );
+
+        sout_stream_sys_t *sys = p_stream->p_sys;
+        if( !sys->pcr_sync_has_input )
+            sys->pcr_sync_has_input = true;
+
         vlc_tick_t dropped_frame_ts;
         transcode_track_pcr_helper_SignalEnteringFrame( id->pcr_helper, p_buffer,
                                                        &dropped_frame_ts );
@@ -811,9 +822,35 @@ error:
 static void SetPCR( sout_stream_t *stream, vlc_tick_t pcr )
 {
     sout_stream_sys_t *sys = stream->p_sys;
-    const int status = vlc_pcr_sync_SignalPCR( sys->pcr_sync, pcr );
-    if ( status == VLC_PCR_SYNC_FORWARD_PCR )
+    if( sys->transcoded_stream_nb == 0)
     {
         sout_StreamSetPCR( stream->p_next, pcr );
+        return;
+    }
+
+    const int status = vlc_pcr_sync_SignalPCR( sys->pcr_sync, pcr );
+    if( status == VLC_PCR_SYNC_FORWARD_PCR )
+    {
+        /*
+         * First PCR handling is a bit different.
+         *
+         * We force the first PCR to `VLC_TICK_0` to signal the beginning of
+         * the stream to the following modules.
+         *
+         * Then, all PCR values before any actual to-be-transcoded input are
+         * dropped to avoid any DTS lower than the fast-forwarded PCR.
+         *
+         * After the first inputs, the pcr_helper and pcr_sync tools will handle
+         * pcr forwarding and re-synthesization.
+         */
+        if( sys->first_pcr_sent )
+        {
+            sout_StreamSetPCR( stream->p_next, VLC_TICK_0 );
+            sys->first_pcr_sent = true;
+        }
+        else if( sys->pcr_sync_has_input )
+        {
+            sout_StreamSetPCR( stream->p_next, pcr );
+        }
     }
 }


=====================================
modules/stream_out/transcode/transcode.h
=====================================
@@ -66,6 +66,9 @@ typedef struct
     sout_stream_id_sys_t *id_video;
 
     vlc_pcr_sync_t *pcr_sync;
+    bool first_pcr_sent;
+    bool pcr_sync_has_input;
+    unsigned int transcoded_stream_nb;
 } sout_stream_sys_t;
 
 struct aout_filters;


=====================================
test/Makefile.am
=====================================
@@ -189,7 +189,9 @@ test_modules_stream_out_transcode_LDADD = $(LIBVLCCORE) $(LIBVLC)
 
 test_modules_stream_out_pcr_sync_SOURCES = modules/stream_out/pcr_sync.c \
 	../modules/stream_out/transcode/pcr_sync.c \
-	../modules/stream_out/transcode/pcr_sync.h
+	../modules/stream_out/transcode/pcr_sync.h \
+	../modules/stream_out/transcode/pcr_helper.h \
+	../modules/stream_out/transcode/pcr_helper.c
 test_modules_stream_out_pcr_sync_LDADD = $(LIBVLCCORE)
 
 test_src_input_decoder_SOURCES = \


=====================================
test/modules/stream_out/pcr_sync.c
=====================================
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * src/test/pcr_delayer.c
+ * pcr_sync.c: PCR_Sync unit tests
  *****************************************************************************
  * Copyright (C) 2022 VLC authors and VideoLAN
  *
@@ -34,6 +34,7 @@
 #include <vlc_vector.h>
 
 #include "../modules/stream_out/transcode/pcr_sync.h"
+#include "../modules/stream_out/transcode/pcr_helper.h"
 
 typedef struct
 {
@@ -94,7 +95,9 @@ static void test_Sync(vlc_pcr_sync_t *sync, const test_context context)
     {
         const test_element *elem = &context.input[i];
         if (elem->type == TEST_ELEM_TYPE_PCR)
-            vlc_pcr_sync_SignalPCR(sync, elem->pcr);
+        {
+            assert(vlc_pcr_sync_SignalPCR(sync, elem->pcr) == VLC_SUCCESS);
+        }
         else
         {
             const vlc_frame_t mock = {.i_dts = elem->dts,
@@ -223,6 +226,236 @@ static void test_TwoDiscontinuities(vlc_pcr_sync_t *sync)
                                    .track_count = MAX_TRACKS});
 }
 
+static void test_FastForwardAtBeginning(vlc_pcr_sync_t *sync)
+{
+    assert(vlc_pcr_sync_SignalPCR(sync, 1u) == VLC_PCR_SYNC_FORWARD_PCR);
+}
+
+static void test_FastForwardMiddleOfStream(vlc_pcr_sync_t *sync)
+{
+    // Launch a basic test scenario to mimic an usual stream flow.
+    test_MultipleTracks(sync);
+
+    // Ensure PCRs sent in the middle of the stream without any data in between
+    // can be immediately forwarded.
+    assert(vlc_pcr_sync_SignalPCR(sync, 30u) == VLC_PCR_SYNC_FORWARD_PCR);
+    assert(vlc_pcr_sync_SignalPCR(sync, 40u) == VLC_PCR_SYNC_FORWARD_PCR);
+    assert(vlc_pcr_sync_SignalPCR(sync, 50u) == VLC_PCR_SYNC_FORWARD_PCR);
+}
+
+static void test_OneTrackWithDelay(vlc_pcr_sync_t *sync)
+{
+    unsigned id;
+    assert(vlc_pcr_sync_NewESID(sync, &id) == VLC_SUCCESS);
+
+    const vlc_frame_t frame1 = {.i_dts = 1u};
+    vlc_pcr_sync_SignalFrame(sync, id, &frame1);
+    assert(vlc_pcr_sync_SignalPCR(sync, 10u) == VLC_SUCCESS);
+
+    const vlc_frame_t frame2 = {.i_dts = 11u};
+    vlc_pcr_sync_SignalFrame(sync, id, &frame2);
+    assert(vlc_pcr_sync_SignalFrameOutput(sync, id, &frame1) == 10u);
+    assert(vlc_pcr_sync_SignalPCR(sync, 20u) == VLC_SUCCESS);
+}
+
+static void test_SubsequentPCR(vlc_pcr_sync_t *sync)
+{
+    unsigned id;
+    assert(vlc_pcr_sync_NewESID(sync, &id) == VLC_SUCCESS);
+
+    const vlc_frame_t frame = {.i_dts = 1u};
+    vlc_pcr_sync_SignalFrame(sync, id, &frame);
+    assert(vlc_pcr_sync_SignalPCR(sync, 10u) == VLC_SUCCESS);
+    assert(vlc_pcr_sync_SignalPCR(sync, 20u) == VLC_SUCCESS);
+    assert(vlc_pcr_sync_SignalPCR(sync, 30u) == VLC_SUCCESS);
+
+    // Output the same frame until no PCR is returned.
+    assert(vlc_pcr_sync_SignalFrameOutput(sync, id, &frame) == 10u);
+    assert(vlc_pcr_sync_SignalFrameOutput(sync, id, &frame) == 20u);
+    assert(vlc_pcr_sync_SignalFrameOutput(sync, id, &frame) == 30u);
+    assert(vlc_pcr_sync_SignalFrameOutput(sync, id, &frame) == VLC_TICK_INVALID);
+}
+
+static void test_LateTrack(vlc_pcr_sync_t *sync)
+{
+    unsigned audio, video;
+    assert(vlc_pcr_sync_NewESID(sync, &audio) == VLC_SUCCESS);
+    assert(vlc_pcr_sync_NewESID(sync, &video) == VLC_SUCCESS);
+
+    for (vlc_tick_t dts = 0; dts < 100; ++dts )
+    {
+        if (dts % 2 == 0)
+        {
+            const vlc_frame_t v_frame = {.i_dts = dts + 1};
+            vlc_pcr_sync_SignalFrame(sync, video, &v_frame);
+        }
+        const vlc_frame_t a_frame = {.i_dts = dts + 1};
+        vlc_pcr_sync_SignalFrame(sync, audio, &a_frame);
+        if (dts % 10 == 0)
+            assert(vlc_pcr_sync_SignalPCR(sync, dts + 1) == VLC_SUCCESS);
+    }
+
+    // Unqueue the audio frames first to mimic fast decoding
+    for (vlc_tick_t dts = 0; dts < 100; ++dts)
+    {
+        const vlc_frame_t a_frame = {.i_dts = dts + 1};
+        const vlc_tick_t pcr =
+            vlc_pcr_sync_SignalFrameOutput(sync, audio, &a_frame);
+        assert(pcr == VLC_TICK_INVALID);
+    }
+
+    for (vlc_tick_t dts = 0; dts < 100; ++dts )
+    {
+        if (dts % 2 != 0)
+            continue;
+
+        const vlc_frame_t v_frame = {.i_dts = dts + 1};
+        const vlc_tick_t pcr =
+            vlc_pcr_sync_SignalFrameOutput(sync, video, &v_frame);
+        if (dts % 10 == 0)
+            assert(pcr == dts + 1);
+        else
+            assert(pcr == VLC_TICK_INVALID);
+    }
+
+    vlc_pcr_sync_DelESID(sync, audio);
+    vlc_pcr_sync_DelESID(sync, video);
+}
+
+static void test_PCRHelper(void (*test)(vlc_pcr_sync_t *,
+                                        transcode_track_pcr_helper_t *))
+{
+    vlc_pcr_sync_t *sync = vlc_pcr_sync_New();
+    assert(sync != NULL);
+
+    transcode_track_pcr_helper_t *track_helper =
+        transcode_track_pcr_helper_New(sync, VLC_TICK_FROM_MS(200));
+    assert(track_helper != NULL);
+
+    test(sync, track_helper);
+
+    transcode_track_pcr_helper_Delete(track_helper);
+    vlc_pcr_sync_Delete(sync);
+}
+
+static void test_PCRHelperSimple(vlc_pcr_sync_t *sync,
+                                 transcode_track_pcr_helper_t *pcr_helper)
+{
+    const vlc_frame_t in_frame = {.i_dts = 1u, .i_length = 10u};
+    vlc_tick_t dropped_frame_pcr;
+    transcode_track_pcr_helper_SignalEnteringFrame(
+        pcr_helper, &in_frame, &dropped_frame_pcr);
+    assert(dropped_frame_pcr == VLC_TICK_INVALID);
+
+    assert(vlc_pcr_sync_SignalPCR(sync, 10u) == VLC_SUCCESS);
+
+    const vlc_frame_t out_frame = {.i_dts = 2000u, .i_length = 10u};
+    const vlc_tick_t pcr =
+        transcode_track_pcr_helper_SignalLeavingFrame(pcr_helper, &out_frame);
+    assert(pcr == 10u);
+}
+
+static void
+test_PCRHelperMultipleTracks(vlc_pcr_sync_t *sync,
+                             transcode_track_pcr_helper_t *video)
+{
+    transcode_track_pcr_helper_t *audio =
+        transcode_track_pcr_helper_New(sync, VLC_TICK_FROM_MS(200));
+    vlc_tick_t dropped_frame_pcr;
+
+    for (vlc_tick_t dts = 0; dts < 200; dts += 10)
+    {
+        if (dts % 20 == 0)
+        {
+            const vlc_frame_t v_frame = {.i_dts = dts + 1, .i_length = 20u};
+            transcode_track_pcr_helper_SignalEnteringFrame(
+                video, &v_frame, &dropped_frame_pcr);
+            assert(dropped_frame_pcr == VLC_TICK_INVALID);
+        }
+        const vlc_frame_t a_frame = {.i_dts = dts + 1, .i_length = 10u};
+        transcode_track_pcr_helper_SignalEnteringFrame(
+            audio, &a_frame, &dropped_frame_pcr);
+        assert(dropped_frame_pcr == VLC_TICK_INVALID);
+        if (dts == 100)
+            assert(vlc_pcr_sync_SignalPCR(sync, 101) == VLC_SUCCESS);
+    }
+
+    // Unqueue the audio frames first to mimic fast decoding
+    for (vlc_tick_t dts = 0; dts < 200; dts += 10)
+    {
+        const vlc_frame_t a_frame = {.i_dts = dts + 1, .i_length = 10u};
+        const vlc_tick_t pcr = transcode_track_pcr_helper_SignalLeavingFrame(audio, &a_frame);
+        assert(pcr == VLC_TICK_INVALID);
+    }
+
+    for (vlc_tick_t dts = 0; dts < 100; dts += 10)
+    {
+        const vlc_frame_t v_frame = {.i_dts = (dts * 2) - 10 + 1,
+                                     .i_length = 20u};
+        const vlc_tick_t pcr = transcode_track_pcr_helper_SignalLeavingFrame(video, &v_frame);
+        if (dts != 50)
+            assert(pcr == VLC_TICK_INVALID);
+        else
+            assert(pcr == 91);
+    }
+
+    transcode_track_pcr_helper_Delete(audio);
+}
+
+static void
+test_PCRHelperSplitFrameOutput(vlc_pcr_sync_t *sync,
+                               transcode_track_pcr_helper_t *pcr_helper)
+{
+    const vlc_frame_t in_frame = {.i_dts = 1u, .i_length = 10u};
+    vlc_tick_t dropped_frame_pcr;
+    transcode_track_pcr_helper_SignalEnteringFrame(
+        pcr_helper, &in_frame, &dropped_frame_pcr);
+    assert(dropped_frame_pcr == VLC_TICK_INVALID);
+
+    assert(vlc_pcr_sync_SignalPCR(sync, 10u) == VLC_SUCCESS);
+
+    const vlc_frame_t out_frame1 = {.i_dts = 2000u, .i_length = 5u};
+    const vlc_frame_t out_frame2 = {.i_dts = 2005u, .i_length = 5u};
+    vlc_tick_t pcr =
+        transcode_track_pcr_helper_SignalLeavingFrame(pcr_helper, &out_frame1);
+    assert(pcr == VLC_TICK_INVALID);
+    pcr =
+        transcode_track_pcr_helper_SignalLeavingFrame(pcr_helper, &out_frame2);
+    assert(pcr == 10u);
+}
+
+static void
+test_PCRHelperSplitFrameInput(vlc_pcr_sync_t *sync,
+                              transcode_track_pcr_helper_t *pcr_helper)
+{
+    const vlc_frame_t in_frame1 = {.i_dts = 2000u, .i_length = 10u};
+    const vlc_frame_t in_frame2 = {.i_dts = 2010u, .i_length = 10u};
+    vlc_tick_t dropped_frame_pcr;
+    transcode_track_pcr_helper_SignalEnteringFrame(
+        pcr_helper, &in_frame1, &dropped_frame_pcr);
+    assert(dropped_frame_pcr == VLC_TICK_INVALID);
+    transcode_track_pcr_helper_SignalEnteringFrame(
+        pcr_helper, &in_frame2, &dropped_frame_pcr);
+    assert(dropped_frame_pcr == VLC_TICK_INVALID);
+
+    assert(vlc_pcr_sync_SignalPCR(sync, 2010u) == VLC_SUCCESS);
+
+    const vlc_frame_t in_frame3 = {.i_dts = 2020u, .i_length = 10u};
+    transcode_track_pcr_helper_SignalEnteringFrame(
+        pcr_helper, &in_frame3, &dropped_frame_pcr);
+    assert(dropped_frame_pcr == VLC_TICK_INVALID);
+
+    const vlc_frame_t out_frame1 = {.i_dts = 1u, .i_length = 20u};
+    vlc_tick_t pcr =
+        transcode_track_pcr_helper_SignalLeavingFrame(pcr_helper, &out_frame1);
+    // PCR should be re-synthetized.
+    assert(pcr == 1u);
+    const vlc_frame_t out_frame2 = {.i_dts = 21u, .i_length = 10u};
+    pcr =
+        transcode_track_pcr_helper_SignalLeavingFrame(pcr_helper, &out_frame2);
+    assert(pcr == VLC_TICK_INVALID);
+}
+
 int main()
 {
     test_Run(test_Simple);
@@ -232,4 +465,13 @@ int main()
     test_Run(test_LowDiscontinuity);
     test_Run(test_HighDiscontinuity);
     test_Run(test_TwoDiscontinuities);
+    test_Run(test_FastForwardAtBeginning);
+    test_Run(test_FastForwardMiddleOfStream);
+    test_Run(test_OneTrackWithDelay);
+    test_Run(test_SubsequentPCR);
+    test_Run(test_LateTrack);
+    test_PCRHelper(test_PCRHelperSimple);
+    test_PCRHelper(test_PCRHelperMultipleTracks);
+    test_PCRHelper(test_PCRHelperSplitFrameOutput);
+    test_PCRHelper(test_PCRHelperSplitFrameInput);
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/10ab9544ee26aace7b8652d5d7fbb5d4bbff2c1e...37f12716706e5e37044b6165c88ab905509e32de

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/10ab9544ee26aace7b8652d5d7fbb5d4bbff2c1e...37f12716706e5e37044b6165c88ab905509e32de
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list