[vlc-commits] [Git][videolan/vlc][3.0.x] 18 commits: demux: ts: fix scale of first_dts

François Cartegnie (@fcartegnie) gitlab at videolan.org
Tue May 20 06:32:10 UTC 2025



François Cartegnie pushed to branch 3.0.x at VideoLAN / VLC


Commits:
f854d817 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: fix scale of first_dts

- - - - -
37b7094b by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: typedef timestamps

(adapted from commit 68fcf13842229e17c3db26ed62f457219484ff77)

- - - - -
fd441747 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: split PES processing

(adapted from commit 7ed1257d9d917e486ad58eabb7cde2f46ea199fc)

- - - - -
e63d73a2 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: replace push with explicit drain

(cherry picked from commit 7413a20bb2dbfd4691731dd8c5c8b2d8ea727385)

- - - - -
077e2dca by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: fix output from variable->fixed (refs #23654)

(cherry picked from commit 6efb4c78a573f754987a3bb3df107b720c5986bc)

- - - - -
58d21108 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: don't append empty ts packets into PES (refs #23654)

(cherry picked from commit 8210974f589be933cc9c9150d8d43f2052bce063)

- - - - -
d3465ce8 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: always end PES on PUSI

(cherry picked from commit d7ace86225528b61508845d3bb762c617e3fc2a1)

- - - - -
8aeda189 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: don't start PES without PUSI

(cherry picked from commit afe64934d2b5f488dd28c52dd654569e534f1721)

- - - - -
6fbb86e0 by François Cartegnie at 2025-05-19T03:45:06+00:00
test: add PES assembly tests

(adapted from commit cef77e6f7f0d47df92e3ca609da07916dc44a3fa)

- - - - -
76b66156 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: store last dts on pes struct

(cherry picked from commit cb1c8d6946ee1b08d47551127c45e485ff12ae2a)

- - - - -
4e3a0be7 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: workaround broken teletext

(adapted from commit 4d37595856b6164c4b9b6ce104ae97f21242295c)

- - - - -
76897796 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: change stime_t for ts_90khz_t

(adapted from 0a5bd92c5218f6416d498648254356b4fbc258aa)

- - - - -
ec978224 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: add TS_90KHZ_INVALID

(adapted from commit cf2f062fdf6ed1050ecb098d7c131daa46110243)

- - - - -
7f2c6b67 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: revert native scale changes to fix wrapping

Can never work with timestamp value 0

(adapted from from commit af38707d0e72f81c5e6e0258a91f4718ca7bdf76)

- - - - -
d22791c5 by François Cartegnie at 2025-05-19T03:45:06+00:00
tests: add timestamps wrapping check

(cherry picked from commit 56b1956461e627eb6c7f650b35fe239aaf21a6ac)

- - - - -
c43911de by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: temporary fix timestamps wrapping

for tests & before full fix

- - - - -
75132780 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: fix multiple wrapping

refs streams/ts/1fps93hours.ts

(adapted from commit caf3ea133abc93da7d640fbec5243e3478c3e1d8)

- - - - -
78988161 by François Cartegnie at 2025-05-19T03:45:06+00:00
demux: ts: wrap probing times

- - - - -


20 changed files:

- modules/demux/Makefile.am
- modules/demux/mpeg/pes.h
- modules/demux/mpeg/ps.h
- modules/demux/mpeg/timestamps.h
- modules/demux/mpeg/ts.c
- modules/demux/mpeg/ts_arib.c
- modules/demux/mpeg/ts_hotfixes.c
- + modules/demux/mpeg/ts_pes.c
- + modules/demux/mpeg/ts_pes.h
- modules/demux/mpeg/ts_pid.c
- modules/demux/mpeg/ts_psi.c
- modules/demux/mpeg/ts_psip.c
- modules/demux/mpeg/ts_scte.c
- modules/demux/mpeg/ts_streams.c
- modules/demux/mpeg/ts_streams_private.h
- modules/demux/pva.c
- modules/demux/ty.c
- test/Makefile.am
- + test/modules/demux/timestamps.c
- + test/modules/demux/ts_pes.c


Changes:

=====================================
modules/demux/Makefile.am
=====================================
@@ -261,6 +261,7 @@ libts_plugin_la_SOURCES = demux/mpeg/ts.c demux/mpeg/ts.h \
         demux/mpeg/ts_metadata.c demux/mpeg/ts_metadata.h \
         demux/mpeg/ts_hotfixes.c demux/mpeg/ts_hotfixes.h \
         demux/mpeg/ts_strings.h demux/mpeg/ts_streams_private.h \
+        demux/mpeg/ts_pes.c demux/mpeg/ts_pes.h \
         demux/mpeg/pes.h \
         demux/mpeg/timestamps.h \
         demux/dvb-text.h \


=====================================
modules/demux/mpeg/pes.h
=====================================
@@ -20,7 +20,9 @@
 #ifndef VLC_MPEG_PES_H
 #define VLC_MPEG_PES_H
 
-static inline bool ExtractPESTimestamp( const uint8_t *p_data, uint8_t i_flags, vlc_tick_t *ret )
+#include "timestamps.h"
+
+static inline bool ExtractPESTimestamp( const uint8_t *p_data, uint8_t i_flags, ts_90khz_t *ret )
 {
     /* !warn broken muxers set incorrect flags. see #17773 and #19140 */
     /* check marker bits, and i_flags = b 0010, 0011 or 0001 */
@@ -32,29 +34,29 @@ static inline bool ExtractPESTimestamp( const uint8_t *p_data, uint8_t i_flags,
         return false;
 
 
-    *ret =  ((vlc_tick_t)(p_data[ 0]&0x0e ) << 29)|
-             (vlc_tick_t)(p_data[1] << 22)|
-            ((vlc_tick_t)(p_data[2]&0xfe) << 14)|
-             (vlc_tick_t)(p_data[3] << 7)|
-             (vlc_tick_t)(p_data[4] >> 1);
+    *ret =  ((ts_90khz_t)(p_data[ 0]&0x0e ) << 29)|
+             (ts_90khz_t)(p_data[1] << 22)|
+            ((ts_90khz_t)(p_data[2]&0xfe) << 14)|
+             (ts_90khz_t)(p_data[3] << 7)|
+             (ts_90khz_t)(p_data[4] >> 1);
     return true;
 }
 
 /* PS SCR timestamp as defined in H222 2.5.3.2 */
-static inline vlc_tick_t ExtractPackHeaderTimestamp( const uint8_t *p_data )
+static inline ts_90khz_t ExtractPackHeaderTimestamp( const uint8_t *p_data )
 {
-    return ((vlc_tick_t)(p_data[ 0]&0x38 ) << 27)|
-            ((vlc_tick_t)(p_data[0]&0x03 ) << 28)|
-             (vlc_tick_t)(p_data[1] << 20)|
-            ((vlc_tick_t)(p_data[2]&0xf8 ) << 12)|
-            ((vlc_tick_t)(p_data[2]&0x03 ) << 13)|
-             (vlc_tick_t)(p_data[3] << 5) |
-             (vlc_tick_t)(p_data[4] >> 3);
+    return  ((ts_90khz_t)(p_data[0]&0x38 ) << 27)|
+            ((ts_90khz_t)(p_data[0]&0x03 ) << 28)|
+             (ts_90khz_t)(p_data[1] << 20)|
+            ((ts_90khz_t)(p_data[2]&0xf8 ) << 12)|
+            ((ts_90khz_t)(p_data[2]&0x03 ) << 13)|
+             (ts_90khz_t)(p_data[3] << 5) |
+             (ts_90khz_t)(p_data[4] >> 3);
 }
 
 inline
 static int ParsePESHeader( vlc_object_t *p_object, const uint8_t *p_header, size_t i_header,
-                           unsigned *pi_skip, vlc_tick_t *pi_dts, vlc_tick_t *pi_pts,
+                           unsigned *pi_skip, ts_90khz_t *pi_dts, ts_90khz_t *pi_pts,
                            uint8_t *pi_stream_id, bool *pb_pes_scambling )
 {
     unsigned i_skip;


=====================================
modules/demux/mpeg/ps.h
=====================================
@@ -442,9 +442,10 @@ static inline int ps_pkt_parse_pack( block_t *p_pkt, int64_t *pi_scr,
     }
     else if( p_pkt->i_buffer >= 12 && (p[4] >> 4) == 0x02 ) /* MPEG-1 Pack SCR, same bits as PES/PTS */
     {
-        if(!ExtractPESTimestamp( &p[4], 0x02, pi_scr ))
+        ts_90khz_t i_scr;
+        if(!ExtractPESTimestamp( &p[4], 0x02, &i_scr ))
             return VLC_EGENERIC;
-        *pi_scr = FROM_SCALE_NZ( *pi_scr );
+        *pi_scr = FROM_SCALE_NZ( i_scr );
         *pi_mux_rate = ( ( p[9]&0x7f )<< 15 )|( p[10] << 7 )|( p[11] >> 1);
     }
     else
@@ -494,8 +495,8 @@ static inline int ps_pkt_parse_system( block_t *p_pkt, ps_psm_t *p_psm,
 static inline int ps_pkt_parse_pes( vlc_object_t *p_object, block_t *p_pes, int i_skip_extra )
 {
     unsigned int i_skip  = 0;
-    vlc_tick_t i_pts = -1;
-    vlc_tick_t i_dts = -1;
+    ts_90khz_t i_pts = TS_90KHZ_INVALID;
+    ts_90khz_t i_dts = TS_90KHZ_INVALID;
     uint8_t i_stream_id = 0;
     bool b_pes_scrambling = false;
 
@@ -521,12 +522,12 @@ static inline int ps_pkt_parse_pes( vlc_object_t *p_object, block_t *p_pes, int
     p_pes->i_buffer -= i_skip;
 
     /* ISO/IEC 13818-1 2.7.5: if no pts and no dts, then dts == pts */
-    if( i_pts >= 0 && i_dts < 0 )
+    if( i_pts != TS_90KHZ_INVALID && i_dts == TS_90KHZ_INVALID )
         i_dts = i_pts;
 
-    if( i_dts >= 0 )
+    if( i_dts != TS_90KHZ_INVALID )
         p_pes->i_dts = FROM_SCALE( i_dts );
-    if( i_pts >= 0 )
+    if( i_pts != TS_90KHZ_INVALID )
         p_pes->i_pts = FROM_SCALE( i_pts );
 
     return VLC_SUCCESS;


=====================================
modules/demux/mpeg/timestamps.h
=====================================
@@ -25,13 +25,26 @@
 #define FROM_SCALE(x) (VLC_TICK_0 + FROM_SCALE_NZ(x))
 #define TO_SCALE(x)   TO_SCALE_NZ((x) - VLC_TICK_0)
 
-static inline int64_t TimeStampWrapAround( int64_t i_first_pcr, int64_t i_time )
+typedef int64_t ts_90khz_t;
+#define TS_90KHZ_INVALID -1
+
+
+#define TS_33BITS_ROLL_NZ      FROM_SCALE_NZ(0x1FFFFFFFF)
+#define TS_33BITS_HALF_ROLL_NZ FROM_SCALE_NZ(0x0FFFFFFFF)
+
+static inline vlc_tick_t TimeStampWrapAround( vlc_tick_t i_past_pcr, vlc_tick_t i_time )
 {
-    int64_t i_adjust = 0;
-    if( i_first_pcr > 0x0FFFFFFFF && i_time < 0x0FFFFFFFF )
-        i_adjust = 0x1FFFFFFFF;
+    if( i_past_pcr == VLC_TICK_INVALID || i_time >= i_past_pcr )
+        return i_time;
+
+    vlc_tick_t delta = i_past_pcr - i_time;
+    if( delta >= TS_33BITS_HALF_ROLL_NZ )
+    {
+        vlc_tick_t rolls = (delta + TS_33BITS_ROLL_NZ - 1) / TS_33BITS_ROLL_NZ;
+        i_time += rolls * TS_33BITS_ROLL_NZ;
+    }
 
-    return i_time + i_adjust;
+    return i_time;
 }
 
 #endif


=====================================
modules/demux/mpeg/ts.c
=====================================
@@ -39,6 +39,7 @@
 #include "ts_pid.h"
 #include "ts_streams.h"
 #include "ts_streams_private.h"
+#include "ts_pes.h"
 #include "ts_psi.h"
 #include "ts_si.h"
 #include "ts_psip.h"
@@ -188,17 +189,17 @@ static inline int PIDGet( block_t *p )
 {
     return ( (p->p_buffer[1]&0x1f)<<8 )|p->p_buffer[2];
 }
-static vlc_tick_t GetPCR( const block_t * );
+static ts_90khz_t GetPCR( const block_t * );
 
 static block_t * ProcessTSPacket( demux_t *p_demux, ts_pid_t *pid, block_t *p_pkt, int * );
-static bool GatherPESData( demux_t *p_demux, ts_pid_t *pid, block_t *p_bk, size_t );
 static bool GatherSectionsData( demux_t *p_demux, ts_pid_t *, block_t *, size_t );
+static bool GatherPESData( demux_t *p_demux, ts_pid_t *, block_t *, size_t );
 static void ProgramSetPCR( demux_t *p_demux, ts_pmt_t *p_prg, vlc_tick_t i_pcr );
 
 static block_t* ReadTSPacket( demux_t *p_demux );
-static int SeekToTime( demux_t *p_demux, const ts_pmt_t *, int64_t time );
+static int SeekToTime( demux_t *p_demux, const ts_pmt_t *, vlc_tick_t time );
 static void ReadyQueuesPostSeek( demux_t *p_demux );
-static void PCRHandle( demux_t *p_demux, ts_pid_t *, vlc_tick_t );
+static void PCRHandle( demux_t *p_demux, ts_pid_t *, ts_90khz_t );
 static void PCRFixHandle( demux_t *, ts_pmt_t *, block_t * );
 
 #define TS_PACKET_SIZE_188 188
@@ -409,7 +410,7 @@ static int Open( vlc_object_t *p_this )
 
     vlc_dictionary_init( &p_sys->attachments, 0 );
 
-    p_sys->patfix.i_first_dts = -1;
+    p_sys->patfix.i_first_dts = VLC_TICK_INVALID;
     p_sys->patfix.i_timesourcepid = 0;
     p_sys->patfix.status = var_CreateGetBool( p_demux, "ts-patfix" ) ? PAT_WAITING : PAT_FIXTRIED;
 
@@ -690,8 +691,8 @@ static int Demux( demux_t *p_demux )
         }
 
         /* Adaptation field cannot be scrambled */
-        vlc_tick_t i_pcr = GetPCR( p_pkt );
-        if( i_pcr > VLC_TICK_INVALID )
+        ts_90khz_t i_pcr = GetPCR( p_pkt );
+        if( i_pcr != TS_90KHZ_INVALID )
             PCRHandle( p_demux, p_pid, i_pcr );
 
         /* Probe streams to build PAT/PMT after MIN_PAT_INTERVAL in case we don't see any PAT */
@@ -930,14 +931,13 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
 
         if( !p_sys->b_ignore_time_for_positions &&
              p_pmt &&
-             p_pmt->pcr.i_first > -1 && p_pmt->i_last_dts > VLC_TICK_INVALID &&
-             p_pmt->pcr.i_current > -1 )
+             p_pmt->pcr.i_first != VLC_TICK_INVALID &&
+             p_pmt->i_last_dts != VLC_TICK_INVALID &&
+             p_pmt->pcr.i_current != VLC_TICK_INVALID )
         {
-            double i_length = TimeStampWrapAround( p_pmt->pcr.i_first,
-                                                   p_pmt->i_last_dts ) - p_pmt->pcr.i_first;
+            double i_length = p_pmt->i_last_dts - p_pmt->pcr.i_first;
             i_length += p_pmt->pcr.i_pcroffset;
-            double i_pos = TimeStampWrapAround( p_pmt->pcr.i_first,
-                                                p_pmt->pcr.i_current ) - p_pmt->pcr.i_first;
+            double i_pos = p_pmt->pcr.i_current - p_pmt->pcr.i_first;
             if( i_length > 0 )
             {
                 *pf = i_pos / i_length;
@@ -963,30 +963,30 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
         if( p_sys->b_access_control &&
            !p_sys->b_ignore_time_for_positions && b_bool && p_pmt )
         {
-            time_t i_time, i_length;
+            time_t i_time, i_length = 0;
+            vlc_tick_t i_seektime = VLC_TICK_0 + vlc_tick_from_sec( i_length * f );
             if( !EITCurrentEventTime( p_pmt, p_sys, &i_time, &i_length ) &&
-                 i_length > 0 && !SeekToTime( p_demux, p_pmt, (int64_t)(TO_SCALE(i_length * CLOCK_FREQ) * f) ) )
+                 i_length > 0 && !SeekToTime( p_demux, p_pmt, i_seektime ) )
             {
                 ReadyQueuesPostSeek( p_demux );
-                es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME,
-                                (int64_t)(TO_SCALE(i_length * CLOCK_FREQ) * f) );
+                es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, i_seektime );
                 return VLC_SUCCESS;
             }
         }
 
         if( !p_sys->b_ignore_time_for_positions && b_bool && p_pmt &&
-             p_pmt->pcr.i_first > -1 && p_pmt->i_last_dts > VLC_TICK_INVALID &&
-             p_pmt->pcr.i_current > -1 )
+             p_pmt->pcr.i_first != VLC_TICK_INVALID &&
+             p_pmt->i_last_dts != VLC_TICK_INVALID &&
+             p_pmt->pcr.i_current != VLC_TICK_INVALID )
         {
-            int64_t i_length = TimeStampWrapAround( p_pmt->pcr.i_first,
-                                                   p_pmt->i_last_dts ) - p_pmt->pcr.i_first;
-            i64 = p_pmt->pcr.i_first + (int64_t)(i_length * f);
+            vlc_tick_t i_length = p_pmt->i_last_dts - p_pmt->pcr.i_first;
+            i64 = p_pmt->pcr.i_first + i_length * f;
             if( i64 <= p_pmt->i_last_dts )
             {
                 if( !SeekToTime( p_demux, p_pmt, i64 ) )
                 {
                     ReadyQueuesPostSeek( p_demux );
-                    es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, FROM_SCALE(i64) );
+                    es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, i64 );
                     return VLC_SUCCESS;
                 }
             }
@@ -1004,12 +1004,12 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
     case DEMUX_SET_TIME:
         i64 = va_arg( args, int64_t );
 
-        if( p_sys->b_canseek && p_pmt && p_pmt->pcr.i_first > -1 &&
-           !SeekToTime( p_demux, p_pmt, p_pmt->pcr.i_first + TO_SCALE(i64) ) )
+        if( p_sys->b_canseek && p_pmt && p_pmt->pcr.i_first != VLC_TICK_INVALID &&
+           !SeekToTime( p_demux, p_pmt, p_pmt->pcr.i_first + i64 ) )
         {
             ReadyQueuesPostSeek( p_demux );
             es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME,
-                            FROM_SCALE(p_pmt->pcr.i_first) + i64 - VLC_TICK_0 );
+                            p_pmt->pcr.i_first + i64 - VLC_TICK_0 );
             return VLC_SUCCESS;
         }
         break;
@@ -1027,10 +1027,9 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
             }
         }
 
-        if( p_pmt && p_pmt->pcr.i_current > -1 && p_pmt->pcr.i_first > -1 )
+        if( p_pmt && p_pmt->pcr.i_current != VLC_TICK_INVALID && p_pmt->pcr.i_first != VLC_TICK_INVALID )
         {
-            int64_t i_pcr = TimeStampWrapAround( p_pmt->pcr.i_first, p_pmt->pcr.i_current );
-            *pi64 = FROM_SCALE(i_pcr - p_pmt->pcr.i_first);
+            *pi64 = p_pmt->pcr.i_current - p_pmt->pcr.i_first;
             return VLC_SUCCESS;
         }
         break;
@@ -1050,14 +1049,14 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
 
         if( !p_sys->b_ignore_time_for_positions &&
             p_pmt &&
-           ( p_pmt->pcr.i_first > -1 || p_pmt->pcr.i_first_dts > VLC_TICK_INVALID ) &&
-             p_pmt->i_last_dts > 0 )
+           ( p_pmt->pcr.i_first != VLC_TICK_INVALID || p_pmt->pcr.i_first_dts != VLC_TICK_INVALID ) &&
+             p_pmt->i_last_dts != VLC_TICK_INVALID )
         {
-            int64_t i_start = (p_pmt->pcr.i_first > -1) ? p_pmt->pcr.i_first :
-                              TO_SCALE(p_pmt->pcr.i_first_dts);
-            int64_t i_last = TimeStampWrapAround( p_pmt->pcr.i_first, p_pmt->i_last_dts );
+            vlc_tick_t i_start = (p_pmt->pcr.i_first != VLC_TICK_INVALID) ? p_pmt->pcr.i_first :
+                                  p_pmt->pcr.i_first_dts;
+            vlc_tick_t i_last = p_pmt->i_last_dts;
             i_last += p_pmt->pcr.i_pcroffset;
-            *pi64 = FROM_SCALE(i_last - i_start);
+            *pi64 = i_last - i_start;
             return VLC_SUCCESS;
         }
         break;
@@ -1319,6 +1318,33 @@ invalid:
     return NULL;
 }
 
+static vlc_tick_t GetTimeForUntimed( const ts_pmt_t *p_pmt )
+{
+    vlc_tick_t i_ts = p_pmt->pcr.i_current;
+    const ts_stream_t *p_cand = NULL;
+    for( int i=0; i< p_pmt->e_streams.i_size; i++ )
+    {
+        const ts_pid_t *p_pid = p_pmt->e_streams.p_elems[i];
+        if( (p_pid->i_flags & FLAG_FILTERED) && SEEN(p_pid) &&
+             p_pid->type == TYPE_STREAM &&
+             p_pid->u.p_stream->p_es &&
+             p_pid->u.p_stream->i_last_dts != VLC_TICK_INVALID )
+        {
+            const ts_es_t *p_es = p_pid->u.p_stream->p_es;
+            if( p_es->fmt.i_cat == VIDEO_ES || p_es->fmt.i_cat == AUDIO_ES )
+            {
+                if( !p_cand || (p_es->fmt.i_cat == VIDEO_ES &&
+                                p_cand->p_es->fmt.i_cat != VIDEO_ES) )
+                {
+                    p_cand = p_pid->u.p_stream;
+                    i_ts = p_cand->i_last_dts;
+                }
+            }
+        }
+    }
+    return i_ts;
+}
+
 static block_t * ConvertPESBlock( demux_t *p_demux, ts_es_t *p_es,
                                   size_t i_pes_size, uint8_t i_stream_id,
                                   block_t *p_block )
@@ -1339,13 +1365,21 @@ static block_t * ConvertPESBlock( demux_t *p_demux, ts_es_t *p_es,
     }
     else if( p_es->fmt.i_codec == VLC_CODEC_TELETEXT )
     {
-        if( p_block->i_pts <= VLC_TICK_INVALID )
+        const ts_pmt_t *p_pmt = p_es->p_program;
+        if( p_block->i_pts != VLC_TICK_INVALID &&
+            p_pmt->pcr.i_current != VLC_TICK_INVALID )
+        {
+            /* Teletext can have totally offset timestamps... RAI1, German */
+            if( p_pmt->pcr.i_current < p_block->i_pts || p_pmt->pcr.i_current - p_block->i_pts > CLOCK_FREQ )
+                p_block->i_dts = p_block->i_pts = VLC_TICK_INVALID;
+        }
+        if( p_block->i_pts == VLC_TICK_INVALID )
         {
             /* Teletext may have missing PTS (ETSI EN 300 472 Annexe A)
              * In this case use the last PCR + 40ms */
-            vlc_tick_t i_pcr = p_es->p_program->pcr.i_current;
-            if( i_pcr > VLC_TICK_INVALID )
-                p_block->i_pts = FROM_SCALE(i_pcr) + 40000;
+            vlc_tick_t i_ts = GetTimeForUntimed( p_es->p_program );
+            if( i_ts != VLC_TICK_INVALID )
+                p_block->i_dts = p_block->i_pts = i_ts + VLC_TICK_FROM_MS(40);
         }
     }
     else if( p_es->fmt.i_codec == VLC_CODEC_ARIB_A ||
@@ -1448,14 +1482,16 @@ static void SendDataChain( demux_t *p_demux, ts_es_t *p_es, block_t *p_chain )
  * gathering stuff
  ****************************************************************************/
 static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
-                               int64_t i_append_pcr )
+                               ts_90khz_t i_append_pcr )
 {
     uint8_t header[34];
     unsigned i_pes_size = 0;
     unsigned i_skip = 0;
-    vlc_tick_t i_dts = -1;
-    vlc_tick_t i_pts = -1;
-    vlc_tick_t i_length = 0;
+    ts_90khz_t i_pktdts = TS_90KHZ_INVALID;
+    ts_90khz_t i_pktpts = TS_90KHZ_INVALID;
+    ts_90khz_t i_length = 0;
+    vlc_tick_t i_dts = VLC_TICK_INVALID;
+    vlc_tick_t i_pts = VLC_TICK_INVALID;
     uint8_t i_stream_id;
     bool b_pes_scrambling = false;
     const es_mpeg4_descriptor_t *p_mpeg4desc = NULL;
@@ -1486,17 +1522,17 @@ static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
     ts_es_t *p_es = pid->u.p_stream->p_es;
 
     if( ParsePESHeader( VLC_OBJECT(p_demux), (uint8_t*)&header, i_max, &i_skip,
-                        &i_dts, &i_pts, &i_stream_id, &b_pes_scrambling ) == VLC_EGENERIC )
+                        &i_pktdts, &i_pktpts, &i_stream_id, &b_pes_scrambling ) == VLC_EGENERIC )
     {
         block_ChainRelease( p_pes );
         return;
     }
     else
     {
-        if( i_pts != -1 && p_es->p_program )
-            i_pts = TimeStampWrapAround( p_es->p_program->pcr.i_first, i_pts );
-        if( i_dts != -1 && p_es->p_program )
-            i_dts = TimeStampWrapAround( p_es->p_program->pcr.i_first, i_dts );
+        if( i_pktpts != TS_90KHZ_INVALID && p_es->p_program )
+            i_pts = TimeStampWrapAround( p_es->p_program->pcr.i_first, FROM_SCALE(i_pktpts) );
+        if( i_pktdts != TS_90KHZ_INVALID && p_es->p_program )
+            i_dts = TimeStampWrapAround( p_es->p_program->pcr.i_first, FROM_SCALE(i_pktdts) );
         if( b_pes_scrambling )
             p_pes->i_flags |= BLOCK_FLAG_SCRAMBLED;
     }
@@ -1555,9 +1591,12 @@ static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
     }
 
     /* ISO/IEC 13818-1 2.7.5: if no pts and no dts, then dts == pts */
-    if( i_pts >= 0 && i_dts < 0 )
+    if( i_pts != VLC_TICK_INVALID && i_dts == VLC_TICK_INVALID )
         i_dts = i_pts;
 
+    if( i_dts != TS_90KHZ_INVALID )
+        pid->u.p_stream->i_last_dts = i_dts;
+
     if( p_pes )
     {
         ts_pmt_t *p_pmt = p_es->p_program;
@@ -1567,11 +1606,11 @@ static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
             return;
         }
 
-        if( i_dts >= 0 )
-            p_pes->i_dts = FROM_SCALE(i_dts);
+        if( i_dts != VLC_TICK_INVALID )
+            p_pes->i_dts = i_dts;
 
-        if( i_pts >= 0 )
-            p_pes->i_pts = FROM_SCALE(i_pts);
+        if( i_pts != VLC_TICK_INVALID )
+            p_pes->i_pts = i_pts;
 
         p_pes->i_length = FROM_SCALE_NZ(i_length);
 
@@ -1585,7 +1624,7 @@ static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
             if( !p_pmt->pcr.b_fix_done ) /* Not seen yet */
                 PCRFixHandle( p_demux, p_pmt, p_block );
 
-            if( p_es->id && (p_pmt->pcr.i_current > -1 || p_pmt->pcr.b_disable) )
+            if( p_es->id && (p_pmt->pcr.i_current != VLC_TICK_INVALID || p_pmt->pcr.b_disable) )
             {
                 if( pid->u.p_stream->prepcr.p_head )
                 {
@@ -1603,34 +1642,31 @@ static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
                 if ( p_pmt->pcr.b_disable && p_block->i_dts > VLC_TICK_INVALID &&
                      ( p_pmt->i_pid_pcr == pid->i_pid || p_pmt->i_pid_pcr == 0x1FFF ) )
                 {
-                    ProgramSetPCR( p_demux, p_pmt, TO_SCALE(p_block->i_dts) - 120000 );
+                    ProgramSetPCR( p_demux, p_pmt, p_block->i_dts - 120000 );
                 }
 
                 /* Compute PCR/DTS offset if any */
-                int64_t i_pcrref = i_append_pcr > VLC_TICK_INVALID ? i_append_pcr : p_pmt->pcr.i_first;
-                if( p_pmt->pcr.i_pcroffset == -1 && p_block->i_dts > VLC_TICK_INVALID &&
-                    i_pcrref > VLC_TICK_INVALID &&
-                   (p_es->fmt.i_cat == VIDEO_ES || p_es->fmt.i_cat == AUDIO_ES) )
+                vlc_tick_t i_pcrref = (i_append_pcr != TS_90KHZ_INVALID) ? FROM_SCALE(i_append_pcr) : p_pmt->pcr.i_first;
+                if( p_pmt->pcr.i_pcroffset == -1 && p_block->i_dts != VLC_TICK_INVALID &&
+                    i_pcrref != VLC_TICK_INVALID &&
+                    (p_es->fmt.i_cat == VIDEO_ES || p_es->fmt.i_cat == AUDIO_ES) )
                 {
-                    int64_t i_dts27 = TO_SCALE(p_block->i_dts);
-                    i_dts27 = TimeStampWrapAround( i_pcrref, i_dts27 );
-                    i_pcrref = TimeStampWrapAround( p_pmt->pcr.i_first, i_pcrref );
-                    if( i_dts27 + (CLOCK_FREQ/90000) < i_pcrref )
+                    if( p_block->i_dts + FROM_SCALE_NZ(CLOCK_FREQ/90000) < i_pcrref )
                     {
-                        p_pmt->pcr.i_pcroffset = i_pcrref - i_dts27 + TO_SCALE_NZ(80000);
+                        p_pmt->pcr.i_pcroffset = i_pcrref - p_block->i_dts + VLC_TICK_FROM_MS(80);
                         msg_Warn( p_demux, "Broken stream: pid %d sends packets with dts %"PRId64
                                            "us later than pcr, applying delay",
-                                  pid->i_pid, FROM_SCALE_NZ(i_pcrref - i_dts27) );
+                                  pid->i_pid, i_pcrref - p_block->i_dts );
                     }
                     else p_pmt->pcr.i_pcroffset = 0;
                 }
 
                 if( p_pmt->pcr.i_pcroffset != -1 )
                 {
-                    if( p_block->i_dts > VLC_TICK_INVALID )
-                        p_block->i_dts += FROM_SCALE_NZ(p_pmt->pcr.i_pcroffset);
-                    if( p_block->i_pts > VLC_TICK_INVALID )
-                        p_block->i_pts += FROM_SCALE_NZ(p_pmt->pcr.i_pcroffset);
+                    if( p_block->i_dts != VLC_TICK_INVALID )
+                        p_block->i_dts += p_pmt->pcr.i_pcroffset;
+                    if( p_block->i_pts != VLC_TICK_INVALID )
+                        p_block->i_pts += p_pmt->pcr.i_pcroffset;
                 }
 
                 /*** From here, block can become a chain again though conversion below ***/
@@ -1657,7 +1693,7 @@ static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
                 block_ChainLastAppend( &pid->u.p_stream->prepcr.pp_last, p_block );
 
                 /* PCR Seen and no es->id, cleanup current and prepcr blocks */
-                if( p_pmt->pcr.i_current > -1)
+                if( p_pmt->pcr.i_current != VLC_TICK_INVALID )
                 {
                     block_ChainRelease( pid->u.p_stream->prepcr.p_head );
                     pid->u.p_stream->prepcr.p_head = NULL;
@@ -1672,49 +1708,9 @@ static void ParsePESDataChain( demux_t *p_demux, ts_pid_t *pid, block_t *p_pes,
     }
 }
 
-static bool PushPESBlock( demux_t *p_demux, ts_pid_t *pid, block_t *p_pkt, bool b_unit_start,
-                          int64_t i_append_pcr )
+static void PESDataChainHandle( vlc_object_t *p_obj, void *priv, block_t *p_data, ts_90khz_t i_append_pcr )
 {
-    bool b_ret = false;
-    ts_stream_t *p_pes = pid->u.p_stream;
-
-    if ( b_unit_start && p_pes->gather.p_data )
-    {
-        block_t *p_datachain = p_pes->gather.p_data;
-        /* Flush the pes from pid */
-        p_pes->gather.p_data = NULL;
-        p_pes->gather.i_data_size = 0;
-        p_pes->gather.i_gathered = 0;
-        p_pes->gather.pp_last = &p_pes->gather.p_data;
-        ParsePESDataChain( p_demux, pid, p_datachain, p_pes->gather.i_append_pcr );
-        b_ret = true;
-    }
-
-    if( b_unit_start )
-        p_pes->gather.i_append_pcr = i_append_pcr;
-
-    if( p_pkt == NULL )
-        return b_ret;
-
-    if( !b_unit_start && p_pes->gather.p_data == NULL )
-    {
-        /* msg_Dbg( p_demux, "broken packet" ); */
-        block_Release( p_pkt );
-        return b_ret;
-    }
-
-    block_ChainLastAppend( &p_pes->gather.pp_last, p_pkt );
-    p_pes->gather.i_gathered += p_pkt->i_buffer;
-
-    if( p_pes->gather.i_data_size > 0 &&
-        p_pes->gather.i_gathered >= p_pes->gather.i_data_size )
-    {
-        /* re-enter in Flush above */
-        assert(p_pes->gather.p_data);
-        return PushPESBlock( p_demux, pid, NULL, true, i_append_pcr );
-    }
-
-    return b_ret;
+    ParsePESDataChain( (demux_t *)p_obj, (ts_pid_t *) priv, p_data, i_append_pcr );
 }
 
 static block_t* ReadTSPacket( demux_t *p_demux )
@@ -1795,11 +1791,11 @@ static block_t* ReadTSPacket( demux_t *p_demux )
     return p_pkt;
 }
 
-static vlc_tick_t GetPCR( const block_t *p_pkt )
+static ts_90khz_t GetPCR( const block_t *p_pkt )
 {
     const uint8_t *p = p_pkt->p_buffer;
 
-    vlc_tick_t i_pcr = -1;
+    ts_90khz_t i_pcr = TS_90KHZ_INVALID;
 
     if(unlikely(p_pkt->i_buffer < 12))
         return i_pcr;
@@ -1812,11 +1808,11 @@ static vlc_tick_t GetPCR( const block_t *p_pkt )
         ( p[5] & 0x10 ) ) /* PCR carry flag */
     {
         /* PCR is 33 bits */
-        i_pcr = ( (vlc_tick_t)p[6] << 25 ) |
-                ( (vlc_tick_t)p[7] << 17 ) |
-                ( (vlc_tick_t)p[8] << 9 ) |
-                ( (vlc_tick_t)p[9] << 1 ) |
-                ( (vlc_tick_t)p[10] >> 7 );
+        i_pcr = ( (ts_90khz_t)p[6] << 25 ) |
+                ( (ts_90khz_t)p[7] << 17 ) |
+                ( (ts_90khz_t)p[8] << 9 ) |
+                ( (ts_90khz_t)p[9] << 1 ) |
+                ( (ts_90khz_t)p[10] >> 7 );
     }
     return i_pcr;
 }
@@ -1882,7 +1878,7 @@ static void ReadyQueuesPostSeek( demux_t *p_demux )
             for( ts_es_t *p_es = p_pes->p_es; p_es; p_es = p_es->p_next )
                 p_es->i_next_block_flags |= BLOCK_FLAG_DISCONTINUITY;
 
-            pid->i_cc = 0xff;
+            pid->u.p_stream->i_last_dts = VLC_TICK_INVALID;
 
             if( pid->u.p_stream->prepcr.p_head )
             {
@@ -1896,16 +1892,16 @@ static void ReadyQueuesPostSeek( demux_t *p_demux )
 
             FlushESBuffer( pid->u.p_stream );
         }
-        p_pmt->pcr.i_current = -1;
+        p_pmt->pcr.i_current = VLC_TICK_INVALID;
     }
 }
 
-static int SeekToTime( demux_t *p_demux, const ts_pmt_t *p_pmt, int64_t i_scaledtime )
+static int SeekToTime( demux_t *p_demux, const ts_pmt_t *p_pmt, vlc_tick_t i_seektime )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
 
     /* Deal with common but worst binary search case */
-    if( p_pmt->pcr.i_first == i_scaledtime && p_sys->b_canseek )
+    if( p_pmt->pcr.i_first == i_seektime && p_sys->b_canseek )
         return vlc_stream_Seek( p_sys->stream, 0 );
 
     const int64_t i_stream_size = stream_Size( p_sys->stream );
@@ -1934,7 +1930,7 @@ static int SeekToTime( demux_t *p_demux, const ts_pmt_t *p_pmt, int64_t i_scaled
         uint64_t i_pos = i_splitpos;
         while( i_pos < i_tail_pos )
         {
-            int64_t i_pcr = -1;
+            ts_90khz_t i_pktpcr = TS_90KHZ_INVALID;
             block_t *p_pkt = ReadTSPacket( p_demux );
             if( !p_pkt )
             {
@@ -1954,37 +1950,37 @@ static int SeekToTime( demux_t *p_demux, const ts_pmt_t *p_pmt, int64_t i_scaled
                     if( p_pkt->i_buffer >= 4 + 2 + 5 )
                     {
                         if( p_pmt->i_pid_pcr == i_pid )
-                            i_pcr = GetPCR( p_pkt );
+                            i_pktpcr = GetPCR( p_pkt );
                         i_skip += 1 + __MIN(p_pkt->p_buffer[4], 182);
                     }
                 }
 
-                if( i_pcr == -1 && p_pid->type == TYPE_STREAM &&
+                if( i_pktpcr == TS_90KHZ_INVALID && p_pid->type == TYPE_STREAM &&
                     ts_stream_Find_es( p_pid->u.p_stream, p_pmt ) &&
                    (p_pkt->p_buffer[1] & 0xC0) == 0x40 && /* Payload start but not corrupt */
                    (p_pkt->p_buffer[3] & 0xD0) == 0x10    /* Has payload but is not encrypted */
                 )
                 {
-                    vlc_tick_t i_dts = -1;
-                    vlc_tick_t i_pts = -1;
+                    ts_90khz_t i_pktdts = TS_90KHZ_INVALID;
+                    ts_90khz_t i_pktpts = TS_90KHZ_INVALID;
                     uint8_t i_stream_id;
                     if ( VLC_SUCCESS == ParsePESHeader( VLC_OBJECT(p_demux), &p_pkt->p_buffer[i_skip],
                                                         p_pkt->i_buffer - i_skip, &i_skip,
-                                                        &i_dts, &i_pts, &i_stream_id, NULL ) )
+                                                        &i_pktdts, &i_pktpts, &i_stream_id, NULL ) )
                     {
-                        if( i_dts > -1 )
-                            i_pcr = i_dts;
+                        if( i_pktdts != TS_90KHZ_INVALID )
+                            i_pktpcr = i_pktdts;
                     }
                 }
             }
             block_Release( p_pkt );
 
-            if( i_pcr != -1 )
+            if( i_pktpcr != TS_90KHZ_INVALID )
             {
-                int64_t i_diff = i_scaledtime - TimeStampWrapAround( p_pmt->pcr.i_first, i_pcr );
+                vlc_tick_t i_diff = i_seektime - TimeStampWrapAround( p_pmt->pcr.i_first, FROM_SCALE(i_pktpcr) );
                 if ( i_diff < 0 )
                     i_tail_pos = (i_splitpos >= p_sys->i_packet_size) ? i_splitpos - p_sys->i_packet_size : 0;
-                else if( i_diff < TO_SCALE(VLC_TICK_0 + CLOCK_FREQ / 2) ) // 500ms
+                else if( i_diff < CLOCK_FREQ / 2 ) // 500ms
                     b_found = true;
                 else
                     i_head_pos = i_pos;
@@ -2005,7 +2001,7 @@ static int SeekToTime( demux_t *p_demux, const ts_pmt_t *p_pmt, int64_t i_scaled
     return VLC_SUCCESS;
 }
 
-static int ProbeChunk( demux_t *p_demux, int i_program, bool b_end, int64_t *pi_pcr, bool *pb_found )
+static int ProbeChunk( demux_t *p_demux, int i_program, bool b_end, ts_90khz_t *pi_pcr, bool *pb_found )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
     int i_count = 0;
@@ -2013,7 +2009,7 @@ static int ProbeChunk( demux_t *p_demux, int i_program, bool b_end, int64_t *pi_
 
     for( ;; )
     {
-        *pi_pcr = -1;
+        *pi_pcr = TS_90KHZ_INVALID;
 
         if( i_count++ > PROBE_CHUNK_COUNT || !( p_pkt = ReadTSPacket( p_demux ) ) )
         {
@@ -2040,7 +2036,7 @@ static int ProbeChunk( demux_t *p_demux, int i_program, bool b_end, int64_t *pi_
             if( b_adaptfield && p_pkt->i_buffer >= 4 + 2 + 5 )
                 *pi_pcr = GetPCR( p_pkt );
 
-            if( *pi_pcr == -1 &&
+            if( *pi_pcr == TS_90KHZ_INVALID &&
                 (p_pkt->p_buffer[1] & 0xC0) == 0x40 && /* payload start */
                 (p_pkt->p_buffer[3] & 0xD0) == 0x10 && /* Has payload but is not encrypted */
                 p_pid->type == TYPE_STREAM &&
@@ -2048,8 +2044,8 @@ static int ProbeChunk( demux_t *p_demux, int i_program, bool b_end, int64_t *pi_
               )
             {
                 b_pcrresult = false;
-                vlc_tick_t i_dts = -1;
-                vlc_tick_t i_pts = -1;
+                ts_90khz_t i_dts = TS_90KHZ_INVALID;
+                ts_90khz_t i_pts = TS_90KHZ_INVALID;
                 uint8_t i_stream_id;
                 unsigned i_skip = 4;
                 if ( b_adaptfield ) // adaptation field
@@ -2059,14 +2055,14 @@ static int ProbeChunk( demux_t *p_demux, int i_program, bool b_end, int64_t *pi_
                                                     p_pkt->i_buffer - i_skip, &i_skip,
                                                     &i_dts, &i_pts, &i_stream_id, NULL ) )
                 {
-                    if( i_dts != -1 )
+                    if( i_dts != TS_90KHZ_INVALID )
                         *pi_pcr = i_dts;
-                    else if( i_pts != -1 )
+                    else if( i_pts != TS_90KHZ_INVALID )
                         *pi_pcr = i_pts;
                 }
             }
 
-            if( *pi_pcr != -1 )
+            if( *pi_pcr != TS_90KHZ_INVALID )
             {
                 ts_pat_t *p_pat = GetPID(p_sys, 0)->u.p_pat;
                 for( int i=0; i<p_pat->programs.i_size; i++ )
@@ -2079,15 +2075,15 @@ static int ProbeChunk( demux_t *p_demux, int i_program, bool b_end, int64_t *pi_
                     {
                         if( b_end )
                         {
-                            p_pmt->i_last_dts = *pi_pcr;
+                            p_pmt->i_last_dts = FROM_SCALE(*pi_pcr);
                             p_pmt->i_last_dts_byte = vlc_stream_Tell( p_sys->stream );
                         }
                         /* Start, only keep first */
-                        else if( b_pcrresult && p_pmt->pcr.i_first == -1 )
+                        else if( b_pcrresult && p_pmt->pcr.i_first == VLC_TICK_INVALID )
                         {
-                            p_pmt->pcr.i_first = *pi_pcr;
+                            p_pmt->pcr.i_first = FROM_SCALE(*pi_pcr);
                         }
-                        else if( p_pmt->pcr.i_first_dts < VLC_TICK_0 )
+                        else if( p_pmt->pcr.i_first_dts == VLC_TICK_INVALID )
                         {
                             p_pmt->pcr.i_first_dts = FROM_SCALE(*pi_pcr);
                         }
@@ -2113,7 +2109,7 @@ int ProbeStart( demux_t *p_demux, int i_program )
 
     int i_probe_count = 0;
     int64_t i_pos;
-    vlc_tick_t i_pcr = -1;
+    ts_90khz_t i_pcr = TS_90KHZ_INVALID;
     bool b_found = false;
 
     do
@@ -2145,7 +2141,7 @@ int ProbeEnd( demux_t *p_demux, int i_program )
 
     int i_probe_count = PROBE_CHUNK_COUNT;
     int64_t i_pos;
-    vlc_tick_t i_pcr = -1;
+    ts_90khz_t i_pcr = TS_90KHZ_INVALID;
     bool b_found = false;
 
     do
@@ -2175,9 +2171,9 @@ static void ProgramSetPCR( demux_t *p_demux, ts_pmt_t *p_pmt, vlc_tick_t i_pcr )
 
     /* Check if we have enqueued blocks waiting the/before the
        PCR barrier, and then adapt pcr so they have valid PCR when dequeuing */
-    if( p_pmt->pcr.i_current == -1 && p_pmt->pcr.b_fix_done )
+    if( p_pmt->pcr.i_current == VLC_TICK_INVALID && p_pmt->pcr.b_fix_done )
     {
-        vlc_tick_t i_mindts = -1;
+        vlc_tick_t i_mindts = VLC_TS_INVALID;
 
         ts_pat_t *p_pat = GetPID(p_sys, 0)->u.p_pat;
         for( int i=0; i< p_pat->programs.i_size; i++ )
@@ -2190,7 +2186,7 @@ static void ProgramSetPCR( demux_t *p_demux, ts_pmt_t *p_pmt, vlc_tick_t i_pcr )
                 while( p_block && p_block->i_dts == VLC_TICK_INVALID )
                     p_block = p_block->p_next;
 
-                if( p_block && ( i_mindts == -1 || p_block->i_dts < i_mindts ) )
+                if( p_block && ( i_mindts == VLC_TS_INVALID || p_block->i_dts < i_mindts ) )
                     i_mindts = p_block->i_dts;
             }
         }
@@ -2198,20 +2194,21 @@ static void ProgramSetPCR( demux_t *p_demux, ts_pmt_t *p_pmt, vlc_tick_t i_pcr )
         if( i_mindts > VLC_TICK_INVALID )
         {
             msg_Dbg( p_demux, "Program %d PCR prequeue fixup %"PRId64"->%"PRId64,
-                     p_pmt->i_number, TO_SCALE(i_mindts), i_pcr );
-            i_pcr = TO_SCALE(i_mindts);
+                     p_pmt->i_number, i_mindts, i_pcr );
+            i_pcr = i_mindts;
         }
     }
 
     p_pmt->pcr.i_current = i_pcr;
-    if( p_pmt->pcr.i_first == -1 )
+
+    if( p_pmt->pcr.i_first == VLC_TICK_INVALID )
     {
         p_pmt->pcr.i_first = i_pcr; // now seen
     }
 
     if ( p_sys->i_pmt_es )
     {
-        es_out_Control( p_demux->out, ES_OUT_SET_GROUP_PCR, p_pmt->i_number, FROM_SCALE(i_pcr) );
+        es_out_Control( p_demux->out, ES_OUT_SET_GROUP_PCR, p_pmt->i_number, i_pcr );
         /* growing files/named fifo handling */
         if( p_sys->b_access_control == false &&
             vlc_stream_Tell( p_sys->stream ) > p_pmt->i_last_dts_byte )
@@ -2279,40 +2276,45 @@ static void PCRCheckDTS( demux_t *p_demux, ts_pmt_t *p_pmt, vlc_tick_t i_pcr)
             continue;
 
         unsigned i_skip = 0;
-        vlc_tick_t i_dts = -1;
-        vlc_tick_t i_pts = -1;
+        ts_90khz_t i_pktdts = TS_90KHZ_INVALID;
+        ts_90khz_t i_pktpts = TS_90KHZ_INVALID;
+        vlc_tick_t i_dts = VLC_TICK_INVALID;
+        vlc_tick_t i_pts = VLC_TICK_INVALID;
         uint8_t i_stream_id;
 
         if( ParsePESHeader( VLC_OBJECT(p_demux), (uint8_t*)&header, i_max, &i_skip,
-                            &i_dts, &i_pts, &i_stream_id, NULL ) == VLC_EGENERIC )
+                            &i_pktdts, &i_pktpts, &i_stream_id, NULL ) == VLC_EGENERIC )
             continue;
 
+        if( i_pktdts != TS_90KHZ_INVALID )
+            i_dts = TimeStampWrapAround( i_pcr, FROM_SCALE(i_pktdts) );
+        if( i_pktpts > TS_90KHZ_INVALID )
+            i_pts = TimeStampWrapAround( i_pcr, FROM_SCALE(i_pktpts) );
+
         if (p_pmt->pcr.i_pcroffset > 0) {
-            if( i_dts > VLC_TICK_INVALID )
+            if( i_dts != VLC_TICK_INVALID )
                 i_dts += p_pmt->pcr.i_pcroffset;
-            if( i_pts > VLC_TICK_INVALID )
+            if( i_pts != VLC_TICK_INVALID )
                 i_pts += p_pmt->pcr.i_pcroffset;
         }
 
-        if( i_dts > VLC_TICK_INVALID )
-            i_dts = TimeStampWrapAround( i_pcr, i_dts );
-        if( i_pts > VLC_TICK_INVALID )
-            i_pts = TimeStampWrapAround( i_pcr, i_pts );
-
-        if(( i_dts > VLC_TICK_INVALID && i_dts <= i_pcr ) ||
-           ( i_pts > VLC_TICK_INVALID && i_pts <= i_pcr ))
+        if(( i_dts != VLC_TICK_INVALID && i_dts <= i_pcr ) ||
+           ( i_pts != VLC_TICK_INVALID && i_pts <= i_pcr ))
         {
             if( IsVideoEnd( p_pid ) )
             {
                 msg_Warn( p_demux, "send queued data for pid %d: TS %"PRId64" <= PCR %"PRId64"\n",
-                          p_pid->i_pid, i_dts > VLC_TICK_INVALID ? i_dts : i_pts, i_pcr);
-                PushPESBlock( p_demux, p_pid, NULL, true, VLC_TICK_INVALID ); /* Flush */
+                          p_pid->i_pid, i_dts != VLC_TICK_INVALID ? i_dts : i_pts, i_pcr);
+                ts_pes_parse_callback cb = { .p_obj = VLC_OBJECT(p_demux),
+                                             .priv = p_pid,
+                                             .pf_parse = PESDataChainHandle };
+                ts_pes_Drain( &cb, p_pes );
             }
         }
     }
 }
 
-static void PCRHandle( demux_t *p_demux, ts_pid_t *pid, vlc_tick_t i_pcr )
+static void PCRHandle( demux_t *p_demux, ts_pid_t *pid, ts_90khz_t i_pcr )
 {
     demux_sys_t   *p_sys = p_demux->p_sys;
 
@@ -2331,7 +2333,12 @@ static void PCRHandle( demux_t *p_demux, ts_pid_t *pid, vlc_tick_t i_pcr )
         ts_pmt_t *p_pmt = p_pat->programs.p_elems[i]->u.p_pmt;
         if( p_pmt->pcr.b_disable )
             continue;
-        vlc_tick_t i_program_pcr = TimeStampWrapAround( p_pmt->pcr.i_first, i_pcr );
+
+        vlc_tick_t i_past_pcr = p_pmt->pcr.i_current;
+        if( i_past_pcr == VLC_TICK_INVALID )
+            i_past_pcr = p_pmt->pcr.i_first;
+
+        vlc_tick_t i_program_pcr = TimeStampWrapAround( i_past_pcr, FROM_SCALE(i_pcr) );
 
         if( p_pmt->i_pid_pcr == 0x1FFF ) /* That program has no dedicated PCR pid ISO/IEC 13818-1 2.4.4.9 */
         {
@@ -2347,7 +2354,7 @@ static void PCRHandle( demux_t *p_demux, ts_pid_t *pid, vlc_tick_t i_pcr )
             if( p_pmt->i_pid_pcr == pid->i_pid ) /* If that program references current pid as PCR */
             {
                 /* We've found a target group for update */
-                PCRCheckDTS( p_demux, p_pmt, i_pcr );
+                PCRCheckDTS( p_demux, p_pmt, FROM_SCALE(i_pcr) );
                 ProgramSetPCR( p_demux, p_pmt, i_program_pcr );
             }
         }
@@ -2407,13 +2414,13 @@ static void PCRFixHandle( demux_t *p_demux, ts_pmt_t *p_pmt, block_t *p_block )
         return;
     }
     /* Record the first data packet timestamp in case there won't be any PCR */
-    else if( !p_pmt->pcr.i_first_dts )
+    else if( p_pmt->pcr.i_first_dts == VLC_TICK_INVALID )
     {
-        p_pmt->pcr.i_first_dts = p_block->i_dts;
+        p_pmt->pcr.i_first_dts = TO_SCALE(p_block->i_dts);
     }
-    else if( p_block->i_dts - p_pmt->pcr.i_first_dts > CLOCK_FREQ / 2 ) /* "PCR repeat rate shall not exceed 100ms" */
+    else if( p_block->i_dts - FROM_SCALE(p_pmt->pcr.i_first_dts) > CLOCK_FREQ / 2 ) /* "PCR repeat rate shall not exceed 100ms" */
     {
-        if( p_pmt->pcr.i_current < 0 &&
+        if( p_pmt->pcr.i_current == VLC_TICK_INVALID &&
             GetPID( p_demux->p_sys, p_pmt->i_pid_pcr )->probed.i_pcr_count == 0 )
         {
             int i_cand = FindPCRCandidate( p_pmt );
@@ -2571,216 +2578,27 @@ static block_t * ProcessTSPacket( demux_t *p_demux, ts_pid_t *pid, block_t *p_pk
     return p_pkt;
 }
 
-/* Avoids largest memcpy */
-static bool block_Split( block_t **pp_block, block_t **pp_remain, size_t i_offset )
-{
-    block_t *p_block = *pp_block;
-    block_t *p_split = NULL;
-    *pp_remain = NULL;
-
-    size_t i_tocopy = p_block->i_buffer - i_offset;
-    if( i_tocopy > i_offset ) /* make new block for head */
-    {
-        if( i_offset > 0 )
-        {
-            p_split = block_Alloc( i_offset );
-            if( p_split == NULL )
-                return false;
-            memcpy( p_split->p_buffer, p_block->p_buffer, i_offset );
-            p_block->p_buffer += i_offset;
-            p_block->i_buffer -= i_offset;
-        }
-        *pp_remain = p_block;
-        *pp_block = p_split;
-    }
-    else /* other gets the tail of our split */
-    {
-        if( i_tocopy > 0 )
-        {
-            p_split = block_Alloc( i_tocopy );
-            if( p_split == NULL )
-                return false;
-            memcpy( p_split->p_buffer, &p_block->p_buffer[i_offset], i_tocopy );
-            p_block->i_buffer -= i_tocopy;
-        }
-        *pp_remain = p_split;
-    }
-    return true;
-}
-
-static uint8_t *FindNextPESHeader( uint8_t *p_buf, size_t i_buffer )
-{
-    const uint8_t *p_end = &p_buf[i_buffer];
-    unsigned i_bitflow = 0;
-    for( ; p_buf != p_end; p_buf++ )
-    {
-        i_bitflow <<= 1;
-        if( !*p_buf )
-        {
-            i_bitflow |= 1;
-        }
-        else if( *p_buf == 0x01 && (i_bitflow & 0x06) == 0x06 ) /* >= two zero prefixed 1 */
-        {
-            return p_buf - 2;
-        }
-    }
-    return NULL;
-}
-
-static const uint8_t pes_sync[] = { 0, 0, 1 };
-
-static bool MayHaveStartCodeOnEnd( const uint8_t *p_buf, size_t i_buf )
-{
-    assert(i_buf > 2);
-    return !( *(--p_buf) > 1 || *(--p_buf) > 0 || *(--p_buf) > 0 );
-}
-
-static bool GatherPESData( demux_t *p_demux, ts_pid_t *pid, block_t *p_pkt, size_t i_skip )
+static bool GatherPESData( demux_t *p_demux, ts_pid_t *p_pid, block_t *p_pkt, size_t i_skip )
 {
+    demux_sys_t *p_sys = p_demux->p_sys;
+    ts_pes_parse_callback cb = { .p_obj = VLC_OBJECT(p_demux),
+                                 .priv = p_pid,
+                                 .pf_parse = PESDataChainHandle };
     const bool b_unit_start = p_pkt->p_buffer[1]&0x40;
-    bool b_ret = false;
-    ts_stream_t *p_pes = pid->u.p_stream;
-    const ts_es_t *p_es = p_pes->p_es;
-    int64_t i_append_pcr = ( p_es && p_es->p_program ) ? p_es->p_program->pcr.i_current : -1;
 
-    /* We have to gather it */
-    p_pkt->p_buffer += i_skip;
+    p_pkt->p_buffer += i_skip; /* point to PES */
     p_pkt->i_buffer -= i_skip;
 
-    bool b_single_payload = b_unit_start; /* Single payload in case of unit start */
-    bool b_aligned_ts_payload = true;
-
-    if( unlikely(p_pes->b_broken_PUSI_conformance) )
-    {
-        /* Stream does not conform to payload_unit_start flag
-         * applied to PES packets (AdTech private_stream_1) */
-        b_aligned_ts_payload = false;
-        b_single_payload = false;
-
-    }
-
-    /* We'll cannot parse any pes data */
-    if( (p_pkt->i_flags & BLOCK_FLAG_SCRAMBLED) && p_demux->p_sys->b_valid_scrambling )
-    {
-        block_Release( p_pkt );
-        return PushPESBlock( p_demux, pid, NULL, true, i_append_pcr );
-    }
-
-    /* Data discontinuity, we need to drop or output currently
-     * gathered data as it can't match the target size or can
-     * have dropped next sync code */
-    if( p_pkt->i_flags & BLOCK_FLAG_DISCONTINUITY )
-    {
-        p_pes->gather.i_saved = 0;
-        /* Flush/output current */
-        b_ret |= PushPESBlock( p_demux, pid, NULL, true, i_append_pcr );
-        /* Propagate to output block to notify packetizers/decoders */
-        if( p_pes->p_es )
-            p_pes->p_es->i_next_block_flags |= BLOCK_FLAG_DISCONTINUITY;
-    }
-
-    if ( unlikely(p_pes->gather.i_saved > 0) )
-    {
-        /* Saved from previous packet end */
-        assert(p_pes->gather.i_saved < 6);
-        if( !b_aligned_ts_payload )
-        {
-            p_pkt = block_Realloc( p_pkt, p_pes->gather.i_saved, p_pkt->i_buffer );
-            if( p_pkt )
-                memcpy( p_pkt->p_buffer, p_pes->gather.saved, p_pes->gather.i_saved );
-        }
-        p_pes->gather.i_saved = 0;
-    }
-
-    for( bool b_first_sync_done = false; p_pkt; )
-    {
-        assert( p_pes->gather.i_saved == 0 );
-
-        if( p_pes->gather.p_data == NULL && !b_first_sync_done && p_pkt->i_buffer >= 6 )
-        {
-            if( likely(b_aligned_ts_payload) )
-            {
-                if( memcmp( p_pkt->p_buffer, pes_sync, 3 ) )
-                {
-                    block_Release( p_pkt );
-                    return b_ret;
-                }
-            }
-            else
-            {
-                /* Need to find sync code */
-                uint8_t *p_buf = FindNextPESHeader( p_pkt->p_buffer, p_pkt->i_buffer - 3 );
-                if( p_buf == NULL )
-                {
-                    /* no first sync code */
-                    if( MayHaveStartCodeOnEnd( p_pkt->p_buffer, p_pkt->i_buffer ) )
-                    {
-                        /* Drop everything except last bytes for next packet */
-                        p_pkt->p_buffer += p_pkt->i_buffer - 3;
-                        p_pes->gather.i_saved = p_pkt->i_buffer = 3;
-                        memcpy(p_pes->gather.saved, p_pkt->p_buffer, p_pkt->i_buffer);
-                    }
-                    block_Release( p_pkt );
-                    return b_ret;
-                }
-                p_pkt->i_buffer -= p_buf - p_pkt->p_buffer;
-                p_pkt->p_buffer = p_buf;
-            }
-            /* now points to PES header */
-            p_pes->gather.i_data_size = GetWBE(&p_pkt->p_buffer[4]);
-            if( p_pes->gather.i_data_size > 0 )
-                p_pes->gather.i_data_size += 6;
-            b_first_sync_done = true; /* Because if size is 0, we woud not look for second sync */
-        }
-        else
-        {
-            assert( p_pes->gather.i_data_size > p_pes->gather.i_gathered ||
-                    p_pes->gather.i_data_size == 0 );
-
-            /* If we started reading a fixed size */
-            if( p_pes->gather.i_data_size > p_pes->gather.i_gathered )
-            {
-                const size_t i_remain = p_pes->gather.i_data_size - p_pes->gather.i_gathered;
-                /* Append whole block */
-                if( likely(p_pkt->i_buffer <= i_remain || b_single_payload) )
-                {
-                    b_ret |= PushPESBlock( p_demux, pid, p_pkt, p_pes->gather.p_data == NULL, i_append_pcr );
-                    p_pkt = NULL;
-                }
-                else /* p_pkt->i_buffer > i_remain */
-                {
-                    block_t *p_split;
-                    if( !block_Split( &p_pkt, &p_split, i_remain ) )
-                    {
-                        block_Release( p_pkt );
-                        return false;
-                    }
-                    b_ret |= PushPESBlock( p_demux, pid, p_pkt, p_pes->gather.p_data == NULL, i_append_pcr );
-                    p_pkt = p_split;
-                    b_first_sync_done = false;
-                }
-            }
-            else /* if( p_pes->gather.i_data_size == 0 ) // see next packet */
-            {
-                /* Append or finish current/start new PES depending on unit_start */
-                b_ret |= PushPESBlock( p_demux, pid, p_pkt, b_unit_start, i_append_pcr );
-                p_pkt = NULL;
-            }
-        }
+    const ts_es_t *p_es = p_pid->u.p_stream->p_es;
+    ts_90khz_t i_append_pcr = ( p_es && p_es->p_program && p_es->p_program->pcr.i_current != VLC_TICK_INVALID )
+                                  ? TO_SCALE(p_es->p_program->pcr.i_current)
+                                  : TS_90KHZ_INVALID;
 
-        if( unlikely(p_pkt && p_pkt->i_buffer < 6) )
-        {
-            /* save and prepend to next packet */
-            assert(!b_single_payload);
-            assert(p_pes->gather.i_saved == 0);
-            p_pes->gather.i_saved = p_pkt->i_buffer;
-            memcpy(p_pes->gather.saved, p_pkt->p_buffer, p_pkt->i_buffer);
-            block_Release( p_pkt );
-            p_pkt = NULL;
-        }
-    }
+    return ts_pes_Gather( &cb, p_pid->u.p_stream,
+                         p_pkt, b_unit_start,
+                         p_sys->b_valid_scrambling,
+                         i_append_pcr );
 
-    return b_ret;
 }
 
 static bool GatherSectionsData( demux_t *p_demux, ts_pid_t *p_pid, block_t *p_pkt, size_t i_skip )


=====================================
modules/demux/mpeg/ts_arib.c
=====================================
@@ -23,6 +23,7 @@
 #include <vlc_common.h>
 #include <vlc_demux.h>
 
+#include "timestamps.h"
 #include "ts_pid.h"
 #include "ts.h"
 


=====================================
modules/demux/mpeg/ts_hotfixes.c
=====================================
@@ -82,7 +82,7 @@ void ProbePES( demux_t *p_demux, ts_pid_t *pid, const uint8_t *p_pesstart, size_
         return;
 
     size_t i_pesextoffset = 8;
-    vlc_tick_t i_dts = -1;
+    ts_90khz_t i_dts = TS_90KHZ_INVALID;
     if( p_pes[7] & 0x80 ) // PTS
     {
         i_pesextoffset += 5;
@@ -192,15 +192,15 @@ void ProbePES( demux_t *p_demux, ts_pid_t *pid, const uint8_t *p_pesstart, size_
     }
 
     /* Track timestamps and flag missing PAT */
-    if( !p_sys->patfix.i_timesourcepid && i_dts > -1 )
+    if( !p_sys->patfix.i_timesourcepid && i_dts != TS_90KHZ_INVALID )
     {
-        p_sys->patfix.i_first_dts = i_dts;
+        p_sys->patfix.i_first_dts = FROM_SCALE(i_dts);
         p_sys->patfix.i_timesourcepid = pid->i_pid;
     }
-    else if( p_sys->patfix.i_timesourcepid == pid->i_pid && i_dts > -1 &&
+    else if( p_sys->patfix.i_timesourcepid == pid->i_pid && i_dts != TS_90KHZ_INVALID &&
              p_sys->patfix.status == PAT_WAITING )
     {
-        if( i_dts - p_sys->patfix.i_first_dts > TO_SCALE(MIN_PAT_INTERVAL) )
+        if( i_dts - p_sys->patfix.i_first_dts > MIN_PAT_INTERVAL )
             p_sys->patfix.status = PAT_MISSING;
     }
 


=====================================
modules/demux/mpeg/ts_pes.c
=====================================
@@ -0,0 +1,306 @@
+/*****************************************************************************
+ * ts_pes.c: Transport Stream input module for VLC.
+ *****************************************************************************
+ * Copyright (C) 2004-2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_demux.h>
+
+#include "ts_streams.h"
+#include "ts_pid.h"
+#include "ts_streams_private.h"
+
+#include "ts_pes.h"
+
+#include <assert.h>
+
+/* Avoids largest memcpy */
+static bool block_Split( block_t **pp_block, block_t **pp_remain, size_t i_offset )
+{
+    block_t *p_block = *pp_block;
+    block_t *p_split = NULL;
+    *pp_remain = NULL;
+
+    size_t i_tocopy = p_block->i_buffer - i_offset;
+    if( i_tocopy > i_offset ) /* make new block for head */
+    {
+        if( i_offset > 0 )
+        {
+            p_split = block_Alloc( i_offset );
+            if( p_split == NULL )
+                return false;
+            memcpy( p_split->p_buffer, p_block->p_buffer, i_offset );
+            p_block->p_buffer += i_offset;
+            p_block->i_buffer -= i_offset;
+        }
+        *pp_remain = p_block;
+        *pp_block = p_split;
+    }
+    else /* other gets the tail of our split */
+    {
+        if( i_tocopy > 0 )
+        {
+            p_split = block_Alloc( i_tocopy );
+            if( p_split == NULL )
+                return false;
+            memcpy( p_split->p_buffer, &p_block->p_buffer[i_offset], i_tocopy );
+            p_block->i_buffer -= i_tocopy;
+        }
+        *pp_remain = p_split;
+    }
+    return true;
+}
+
+static const uint8_t pes_sync[] = { 0, 0, 1 };
+
+static bool MayHaveStartCodeOnEnd( const uint8_t *p_buf, size_t i_buf )
+{
+    assert(i_buf > 2);
+    return !( *(--p_buf) > 1 || *(--p_buf) > 0 || *(--p_buf) > 0 );
+}
+
+static uint8_t *FindNextPESHeader( uint8_t *p_buf, size_t i_buffer )
+{
+    const uint8_t *p_end = &p_buf[i_buffer];
+    unsigned i_bitflow = 0;
+    for( ; p_buf != p_end; p_buf++ )
+    {
+        i_bitflow <<= 1;
+        if( !*p_buf )
+        {
+            i_bitflow |= 1;
+        }
+        else if( *p_buf == 0x01 && (i_bitflow & 0x06) == 0x06 ) /* >= two zero prefixed 1 */
+        {
+            return p_buf - 2;
+        }
+    }
+    return NULL;
+}
+
+static bool ts_pes_Push( ts_pes_parse_callback *cb,
+                  ts_stream_t *p_pes, block_t *p_pkt,
+                  bool b_unit_start, ts_90khz_t i_append_pcr )
+{
+    bool b_ret = false;
+
+    if ( b_unit_start && p_pes->gather.p_data )
+    {
+        block_t *p_datachain = p_pes->gather.p_data;
+        /* Flush the pes from pid */
+        p_pes->gather.p_data = NULL;
+        p_pes->gather.i_data_size = 0;
+        p_pes->gather.i_gathered = 0;
+        p_pes->gather.pp_last = &p_pes->gather.p_data;
+        cb->pf_parse( cb->p_obj, cb->priv, p_datachain, p_pes->gather.i_append_pcr );
+        b_ret = true;
+    }
+
+    if( b_unit_start )
+        p_pes->gather.i_append_pcr = i_append_pcr;
+
+    if( p_pkt == NULL )
+        return b_ret;
+
+    if( p_pkt->i_buffer == 0 )
+    {
+        block_Release( p_pkt );
+        return b_ret;
+    }
+
+    if( !b_unit_start && p_pes->gather.p_data == NULL )
+    {
+        /* msg_Dbg( p_demux, "broken packet" ); */
+        block_Release( p_pkt );
+        return b_ret;
+    }
+
+    block_ChainLastAppend( &p_pes->gather.pp_last, p_pkt );
+    p_pes->gather.i_gathered += p_pkt->i_buffer;
+
+    if( p_pes->gather.i_data_size > 0 &&
+        p_pes->gather.i_gathered >= p_pes->gather.i_data_size )
+    {
+        /* re-enter in Flush above */
+        assert(p_pes->gather.p_data);
+        return ts_pes_Push( cb, p_pes, NULL, true, i_append_pcr );
+    }
+
+    return b_ret;
+}
+
+bool ts_pes_Drain( ts_pes_parse_callback *cb, ts_stream_t *p_pes )
+{
+    return ts_pes_Push( cb, p_pes, NULL, true, VLC_TICK_INVALID );
+}
+
+bool ts_pes_Gather( ts_pes_parse_callback *cb,
+                    ts_stream_t *p_pes, block_t *p_pkt,
+                    bool b_unit_start, bool b_valid_scrambling,
+                    ts_90khz_t i_append_pcr )
+{
+    bool b_ret = false;
+    bool b_single_payload = b_unit_start; /* Single payload in case of unit start */
+    bool b_aligned_ts_payload = true;
+
+    if( unlikely(p_pes->b_broken_PUSI_conformance) )
+    {
+        /* Stream does not conform to payload_unit_start flag
+         * applied to PES packets (AdTech private_stream_1) */
+        b_aligned_ts_payload = false;
+        b_single_payload = false;
+
+    }
+
+    /* We'll cannot parse any pes data */
+    if( (p_pkt->i_flags & BLOCK_FLAG_SCRAMBLED) && b_valid_scrambling )
+    {
+        block_Release( p_pkt );
+        return ts_pes_Push( cb, p_pes, NULL, true, i_append_pcr );
+    }
+
+    /* Data discontinuity, we need to drop or output currently
+     * gathered data as it can't match the target size or can
+     * have dropped next sync code */
+    if( p_pkt->i_flags & BLOCK_FLAG_DISCONTINUITY )
+    {
+        p_pes->gather.i_saved = 0;
+        /* Flush/output current */
+        b_ret |= ts_pes_Push( cb, p_pes, NULL, true, i_append_pcr );
+        /* Propagate to output block to notify packetizers/decoders */
+        if( p_pes->p_es )
+            p_pes->p_es->i_next_block_flags |= BLOCK_FLAG_DISCONTINUITY;
+    }
+
+    if ( unlikely(p_pes->gather.i_saved > 0) )
+    {
+        /* Saved from previous packet end */
+        assert(p_pes->gather.i_saved < 6);
+        if( !b_aligned_ts_payload )
+        {
+            p_pkt = block_Realloc( p_pkt, p_pes->gather.i_saved, p_pkt->i_buffer );
+            if( p_pkt )
+                memcpy( p_pkt->p_buffer, p_pes->gather.saved, p_pes->gather.i_saved );
+        }
+        p_pes->gather.i_saved = 0;
+    }
+
+    for( bool b_first_sync_done = false; p_pkt; )
+    {
+        assert( p_pes->gather.i_saved == 0 );
+
+        if( p_pes->gather.p_data == NULL && b_unit_start && !b_first_sync_done && p_pkt->i_buffer >= 6 )
+        {
+            if( likely(b_aligned_ts_payload) )
+            {
+                if( memcmp( p_pkt->p_buffer, pes_sync, 3 ) )
+                {
+                    block_Release( p_pkt );
+                    return b_ret;
+                }
+            }
+            else
+            {
+                /* Need to find sync code */
+                uint8_t *p_buf = FindNextPESHeader( p_pkt->p_buffer, p_pkt->i_buffer - 3 );
+                if( p_buf == NULL )
+                {
+                    /* no first sync code */
+                    if( MayHaveStartCodeOnEnd( p_pkt->p_buffer, p_pkt->i_buffer ) )
+                    {
+                        /* Drop everything except last bytes for next packet */
+                        p_pkt->p_buffer += p_pkt->i_buffer - 3;
+                        p_pes->gather.i_saved = p_pkt->i_buffer = 3;
+                        memcpy(p_pes->gather.saved, p_pkt->p_buffer, p_pkt->i_buffer);
+                    }
+                    block_Release( p_pkt );
+                    return b_ret;
+                }
+                p_pkt->i_buffer -= p_buf - p_pkt->p_buffer;
+                p_pkt->p_buffer = p_buf;
+            }
+            /* now points to PES header */
+            p_pes->gather.i_data_size = GetWBE(&p_pkt->p_buffer[4]);
+            if( p_pes->gather.i_data_size > 0 )
+                p_pes->gather.i_data_size += 6;
+            b_first_sync_done = true; /* Because if size is 0, we woud not look for second sync */
+        }
+        else
+        {
+            assert( p_pes->gather.i_data_size > p_pes->gather.i_gathered ||
+                    p_pes->gather.i_data_size == 0 );
+
+            /* If we started reading a fixed size */
+            if( p_pes->gather.i_data_size > p_pes->gather.i_gathered && !b_single_payload )
+            {
+                const size_t i_remain = p_pes->gather.i_data_size - p_pes->gather.i_gathered;
+                /* Append whole block */
+                if( likely(p_pkt->i_buffer <= i_remain) )
+                {
+                    b_ret |= ts_pes_Push( cb, p_pes, p_pkt, p_pes->gather.p_data == NULL, i_append_pcr );
+                    p_pkt = NULL;
+                }
+                else /* p_pkt->i_buffer > i_remain */
+                {
+                    block_t *p_split;
+                    if( !block_Split( &p_pkt, &p_split, i_remain ) )
+                    {
+                        block_Release( p_pkt );
+                        return false;
+                    }
+                    b_ret |= ts_pes_Push( cb, p_pes, p_pkt, p_pes->gather.p_data == NULL, i_append_pcr );
+                    p_pkt = p_split;
+                    b_first_sync_done = false;
+                }
+            }
+            else /* if( p_pes->gather.i_data_size == 0 ) // see next packet */
+            {
+                if( likely(b_aligned_ts_payload) && b_unit_start )
+                {
+                    b_ret |= ts_pes_Push( cb, p_pes, NULL, true, i_append_pcr );
+                    /* now points to PES header */
+                    if( p_pkt->i_buffer >= 6 )
+                    {
+                        p_pes->gather.i_data_size = GetWBE(&p_pkt->p_buffer[4]);
+                        if( p_pes->gather.i_data_size > 0 )
+                            p_pes->gather.i_data_size += 6;
+                    }
+                }
+                /* Append or finish current/start new PES depending on unit_start */
+                b_ret |= ts_pes_Push( cb, p_pes, p_pkt, b_unit_start, i_append_pcr );
+                p_pkt = NULL;
+            }
+        }
+
+        if( unlikely(p_pkt && p_pkt->i_buffer < 6) )
+        {
+            /* save and prepend to next packet */
+            assert(!b_single_payload);
+            assert(p_pes->gather.i_saved == 0);
+            p_pes->gather.i_saved = p_pkt->i_buffer;
+            memcpy(p_pes->gather.saved, p_pkt->p_buffer, p_pkt->i_buffer);
+            block_Release( p_pkt );
+            p_pkt = NULL;
+        }
+    }
+
+    return b_ret;
+}


=====================================
modules/demux/mpeg/ts_pes.h
=====================================
@@ -0,0 +1,40 @@
+/*****************************************************************************
+ * ts_pes.h: Transport Stream input module for VLC.
+ *****************************************************************************
+ * Copyright (C) 2004-2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef VLC_TS_PES_H
+#define VLC_TS_PES_H
+
+#include "timestamps.h"
+
+typedef struct
+{
+    vlc_object_t *p_obj;
+    void *priv;
+    void(*pf_parse)(vlc_object_t *, void *, block_t *, ts_90khz_t );
+} ts_pes_parse_callback;
+
+bool ts_pes_Drain( ts_pes_parse_callback *cb, ts_stream_t *p_pes );
+
+bool ts_pes_Gather( ts_pes_parse_callback *cb,
+                    ts_stream_t *p_pes, block_t *p_pkt,
+                    bool b_unit_start, bool b_valid_scrambling,
+                    ts_90khz_t i_append_pcr );
+
+
+#endif


=====================================
modules/demux/mpeg/ts_pid.c
=====================================
@@ -25,6 +25,7 @@
 
 #include "ts_pid.h"
 #include "ts_streams.h"
+#include "timestamps.h"
 
 #include "ts.h"
 


=====================================
modules/demux/mpeg/ts_psi.c
=====================================
@@ -2028,11 +2028,14 @@ static void PMTCallBack( void *data, dvbpsi_pmt_t *p_dvbpsipmt )
     UpdatePESFilters( p_demux, p_demux->p_sys->seltype == PROGRAM_ALL );
 
     /* Probe Boundaries */
-    if( p_sys->b_canfastseek && p_pmt->i_last_dts == -1 )
+    if( p_sys->b_canfastseek && p_pmt->i_last_dts == VLC_TICK_INVALID )
     {
-        p_pmt->i_last_dts = 0;
+        p_pmt->i_last_dts = VLC_TICK_INVALID;
         ProbeStart( p_demux, p_pmt->i_number );
         ProbeEnd( p_demux, p_pmt->i_number );
+        if( p_pmt->i_last_dts != VLC_TICK_INVALID &&
+            p_pmt->i_last_dts < p_pmt->pcr.i_first_dts )
+            p_pmt->i_last_dts = TimeStampWrapAround( p_pmt->pcr.i_first_dts, p_pmt->i_last_dts );
     }
 
     dvbpsi_pmt_delete( p_dvbpsipmt );


=====================================
modules/demux/mpeg/ts_psip.c
=====================================
@@ -43,6 +43,7 @@
 #include "ts_decoders.h"
 #include "ts_psip_dvbpsi_fixes.h"
 
+#include "timestamps.h"
 #include "ts_pid.h"
 #include "ts.h"
 #include "ts_streams_private.h"


=====================================
modules/demux/mpeg/ts_scte.c
=====================================
@@ -61,10 +61,9 @@ void SCTE18_Section_Callback( dvbpsi_t *p_handle, const dvbpsi_psi_section_t* p_
                 continue;
 
             const ts_pmt_t *p_pmt = p_es->p_program;
-            const vlc_tick_t i_date = TimeStampWrapAround( p_pmt->pcr.i_first, p_pmt->pcr.i_current );
             block_t *p_block = block_Alloc( p_section->p_payload_end - p_section->p_payload_start );
             memcpy( p_block->p_buffer, p_section->p_payload_start, i_payload );
-            p_block->i_dts = p_block->i_pts = FROM_SCALE( i_date );
+            p_block->i_dts = p_block->i_pts = p_pmt->pcr.i_current;
 
             es_out_Control( p_demux->out, ES_OUT_SET_ES_STATE, p_es->id, true );
             es_out_Send( p_demux->out, p_es->id, p_block );
@@ -102,9 +101,9 @@ void SCTE27_Section_Callback( demux_t *p_demux,
         bool is_immediate = p_content->p_buffer[i_offset + 3] & 0x40;
         if( !is_immediate )
         {
-            vlc_tick_t i_display_in = GetDWBE( &p_content->p_buffer[i_offset + 4] );
+            vlc_tick_t i_display_in = FROM_SCALE(GetDWBE( &p_content->p_buffer[i_offset + 4] ));
             if( i_display_in < i_date )
-                i_date = i_display_in + (1ll << 32);
+                i_date = i_display_in + FROM_SCALE_NZ(1ll << 32);
             else
                 i_date = i_display_in;
         }


=====================================
modules/demux/mpeg/ts_streams.c
=====================================
@@ -122,14 +122,14 @@ ts_pmt_t *ts_pmt_New( demux_t *p_demux )
     pmt->od.i_version = -1;
     ARRAY_INIT( pmt->od.objects );
 
-    pmt->i_last_dts = -1;
+    pmt->i_last_dts = VLC_TICK_INVALID;
     pmt->i_last_dts_byte = 0;
 
     pmt->p_atsc_si_basepid      = NULL;
     pmt->p_si_sdt_pid = NULL;
 
-    pmt->pcr.i_current = -1;
-    pmt->pcr.i_first  = -1;
+    pmt->pcr.i_current = VLC_TICK_INVALID;
+    pmt->pcr.i_first  = VLC_TICK_INVALID;
     pmt->pcr.b_disable = false;
     pmt->pcr.i_first_dts = VLC_TICK_INVALID;
     pmt->pcr.i_pcroffset = -1;
@@ -286,13 +286,14 @@ ts_stream_t *ts_stream_New( demux_t *p_demux, ts_pmt_t *p_program )
     pes->gather.p_data = NULL;
     pes->gather.pp_last = &pes->gather.p_data;
     pes->gather.i_saved = 0;
-    pes->gather.i_append_pcr = VLC_TICK_INVALID;
+    pes->gather.i_append_pcr = TS_90KHZ_INVALID;
     pes->b_broken_PUSI_conformance = false;
     pes->b_always_receive = false;
     pes->p_sections_proc = NULL;
     pes->p_proc = NULL;
     pes->prepcr.p_head = NULL;
     pes->prepcr.pp_last = &pes->prepcr.p_head;
+    pes->i_last_dts = VLC_TICK_INVALID;
 
     return pes;
 }


=====================================
modules/demux/mpeg/ts_streams_private.h
=====================================
@@ -23,6 +23,7 @@ typedef struct dvbpsi_s dvbpsi_t;
 typedef struct ts_sections_processor_t ts_sections_processor_t;
 
 #include "mpeg4_iod.h"
+#include "timestamps.h"
 
 #include <vlc_common.h>
 #include <vlc_es.h>
@@ -58,7 +59,7 @@ struct ts_pmt_t
     struct
     {
         vlc_tick_t i_current;
-        vlc_tick_t i_first; // seen <> != -1
+        vlc_tick_t i_first; // seen <> != TS_TICK_UNKNOWN
         /* broken PCR handling */
         vlc_tick_t i_first_dts;
         vlc_tick_t i_pcroffset;
@@ -125,7 +126,7 @@ struct ts_stream_t
         block_t     **pp_last;
         uint8_t     saved[5];
         size_t      i_saved;
-        int64_t     i_append_pcr;
+        ts_90khz_t  i_append_pcr;
     } gather;
 
     bool        b_always_receive;
@@ -138,6 +139,8 @@ struct ts_stream_t
         block_t *p_head;
         block_t **pp_last;
     } prepcr;
+
+    vlc_tick_t i_last_dts;
 };
 
 typedef struct ts_si_context_t ts_si_context_t;


=====================================
modules/demux/pva.c
=====================================
@@ -32,6 +32,7 @@
 #include <vlc_common.h>
 #include <vlc_plugin.h>
 #include <vlc_demux.h>
+#include "mpeg/timestamps.h"
 
 /*****************************************************************************
  * Module descriptor
@@ -393,8 +394,8 @@ static void ParsePES( demux_t *p_demux )
     uint8_t     hdr[30];
 
     unsigned    i_skip;
-    vlc_tick_t  i_dts = -1;
-    vlc_tick_t  i_pts = -1;
+    ts_90khz_t  i_dts = TS_90KHZ_INVALID;
+    ts_90khz_t  i_pts = TS_90KHZ_INVALID;
 
     p_sys->p_pes = NULL;
 
@@ -416,19 +417,19 @@ static void ParsePES( demux_t *p_demux )
     i_skip = hdr[8] + 9;
     if( hdr[7]&0x80 )    /* has pts */
     {
-        i_pts = ((vlc_tick_t)(hdr[ 9]&0x0e ) << 29)|
-                 (vlc_tick_t)(hdr[10] << 22)|
-                ((vlc_tick_t)(hdr[11]&0xfe) << 14)|
-                 (vlc_tick_t)(hdr[12] << 7)|
-                 (vlc_tick_t)(hdr[12] >> 1);
+        i_pts = ((ts_90khz_t)(hdr[ 9]&0x0e ) << 29)|
+                 (ts_90khz_t)(hdr[10] << 22)|
+                ((ts_90khz_t)(hdr[11]&0xfe) << 14)|
+                 (ts_90khz_t)(hdr[12] << 7)|
+                 (ts_90khz_t)(hdr[12] >> 1);
 
         if( hdr[7]&0x40 )    /* has dts */
         {
-             i_dts = ((vlc_tick_t)(hdr[14]&0x0e ) << 29)|
-                     (vlc_tick_t)(hdr[15] << 22)|
-                    ((vlc_tick_t)(hdr[16]&0xfe) << 14)|
-                     (vlc_tick_t)(hdr[17] << 7)|
-                     (vlc_tick_t)(hdr[18] >> 1);
+             i_dts = ((ts_90khz_t)(hdr[14]&0x0e ) << 29)|
+                     (ts_90khz_t)(hdr[15] << 22)|
+                    ((ts_90khz_t)(hdr[16]&0xfe) << 14)|
+                     (ts_90khz_t)(hdr[17] << 7)|
+                     (ts_90khz_t)(hdr[18] >> 1);
         }
     }
 
@@ -444,10 +445,10 @@ static void ParsePES( demux_t *p_demux )
     p_pes->i_buffer -= i_skip;
     p_pes->p_buffer += i_skip;
 
-    if( i_dts >= 0 )
-        p_pes->i_dts = VLC_TICK_0 + i_dts * 100 / 9;
-    if( i_pts >= 0 )
-        p_pes->i_pts = VLC_TICK_0 + i_pts * 100 / 9;
+    if( i_dts != TS_90KHZ_INVALID )
+        p_pes->i_dts = FROM_SCALE(i_dts);
+    if( i_pts != TS_90KHZ_INVALID )
+        p_pes->i_pts = FROM_SCALE(i_pts);
 
     /* Set PCR */
     if( p_pes->i_pts > 0 )


=====================================
modules/demux/ty.c
=====================================
@@ -46,6 +46,7 @@
 #include <vlc_meta.h>
 #include <vlc_input.h>
 #include "../codec/cc.h"
+#include "mpeg/timestamps.h"
 
 #include <assert.h>
 
@@ -558,13 +559,13 @@ static void Close( vlc_object_t *p_this )
  * Assume buf points to beginning of PTS */
 static vlc_tick_t get_pts( const uint8_t *buf )
 {
-    vlc_tick_t i_pts;
+    ts_90khz_t i_pts;
 
-    i_pts = ((vlc_tick_t)(buf[0]&0x0e ) << 29)|
-             (vlc_tick_t)(buf[1] << 22)|
-            ((vlc_tick_t)(buf[2]&0xfe) << 14)|
-             (vlc_tick_t)(buf[3] << 7)|
-             (vlc_tick_t)(buf[4] >> 1);
+    i_pts = ((ts_90khz_t)(buf[0]&0x0e ) << 29)|
+             (ts_90khz_t)(buf[1] << 22)|
+            ((ts_90khz_t)(buf[2]&0xfe) << 14)|
+             (ts_90khz_t)(buf[3] << 7)|
+             (ts_90khz_t)(buf[4] >> 1);
     i_pts *= 100 / 9;   /* convert PTS (90Khz clock) to microseconds */
     return i_pts;
 }


=====================================
test/Makefile.am
=====================================
@@ -31,6 +31,8 @@ check_PROGRAMS = \
 	test_src_misc_epg \
 	test_src_misc_keystore \
 	test_modules_packetizer_hxxx \
+	test_modules_demux_timestamps \
+	test_modules_demux_ts_pes \
 	test_modules_keystore
 
 if ENABLE_SOUT
@@ -130,6 +132,11 @@ test_modules_keystore_SOURCES = modules/keystore/test.c
 test_modules_keystore_LDADD = $(LIBVLCCORE) $(LIBVLC)
 test_modules_tls_SOURCES = modules/misc/tls.c
 test_modules_tls_LDADD = $(LIBVLCCORE) $(LIBVLC)
+test_modules_demux_timestamps_SOURCES = modules/demux/timestamps.c
+test_modules_demux_ts_pes_LDADD = $(LIBVLCCORE) $(LIBVLC)
+test_modules_demux_ts_pes_SOURCES = modules/demux/ts_pes.c \
+				../modules/demux/mpeg/ts_pes.c \
+				../modules/demux/mpeg/ts_pes.h
 
 checkall:
 	$(MAKE) check_PROGRAMS="$(check_PROGRAMS) $(EXTRA_PROGRAMS)" check


=====================================
test/modules/demux/timestamps.c
=====================================
@@ -0,0 +1,96 @@
+/*****************************************************************************
+ * timestamps.c:
+ *****************************************************************************
+ * Copyright © 2025 VideoLabs, VideoLAN and VLC Authors
+ *
+ * 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 "../../../modules/demux/mpeg/timestamps.h"
+
+#define ASSERT(a) do {\
+if(!(a)) { \
+        fprintf(stderr, "failed line %d\n", __LINE__); \
+        return 1; } \
+} while(0)
+
+int main(void)
+{
+    /* Should not wrap without reference */
+    vlc_tick_t ts1 = 50;
+    vlc_tick_t ts2 = TimeStampWrapAround(VLC_TICK_INVALID, ts1);
+    ASSERT(ts2 == ts1);
+
+    ts1 = TS_33BITS_ROLL_NZ * 3/4;
+    ts2 = TimeStampWrapAround(VLC_TICK_INVALID, ts1);
+    ASSERT(ts2 == ts1);
+
+    /* Should not wrap */
+    ts1 = VLC_TICK_0 + TS_33BITS_HALF_ROLL_NZ;
+    ts2 = TimeStampWrapAround(VLC_TICK_0, ts1);
+    ASSERT(ts2 == ts1);
+
+    ts1 = VLC_TICK_0 + TS_33BITS_ROLL_NZ;
+    ts2 = TimeStampWrapAround(VLC_TICK_0, ts1);
+    ASSERT(ts2 == ts1);
+
+    ts1 = VLC_TICK_0 + TS_33BITS_ROLL_NZ;
+    ts2 = TimeStampWrapAround(ts1, ts1);
+    ASSERT(ts2 == ts1);
+
+    ts1 = VLC_TICK_0 + TS_33BITS_ROLL_NZ;
+    ts2 = TimeStampWrapAround(ts1 - 100, ts1);
+    ASSERT(ts2 == ts1);
+
+    ts1 = VLC_TICK_0 + TS_33BITS_ROLL_NZ;
+    ts2 = TimeStampWrapAround(VLC_TICK_0 + TS_33BITS_HALF_ROLL_NZ, ts1);
+    ASSERT(ts2 == ts1);
+
+    /* Should wrap */
+    ts1 = VLC_TICK_0;
+    ts2 = TimeStampWrapAround(VLC_TICK_0 + TS_33BITS_HALF_ROLL_NZ, ts1);
+    ASSERT(ts2 > ts1);
+    ASSERT(ts2 == ts1 + TS_33BITS_ROLL_NZ);
+
+    ts1 = VLC_TICK_0;
+    ts2 = TimeStampWrapAround(VLC_TICK_0 + TS_33BITS_ROLL_NZ * 3/4, ts1);
+    ASSERT(ts2 == ts1 + TS_33BITS_ROLL_NZ);
+
+    ts1 = VLC_TICK_0;
+    ts2 = TimeStampWrapAround(VLC_TICK_0 + TS_33BITS_ROLL_NZ, ts1);
+    ASSERT(ts2 == ts1 + TS_33BITS_ROLL_NZ);
+
+    ts1 = VLC_TICK_0 + TS_33BITS_HALF_ROLL_NZ;
+    ts2 = TimeStampWrapAround(VLC_TICK_0 + TS_33BITS_ROLL_NZ, ts1);
+    ASSERT(ts2 == ts1 + TS_33BITS_ROLL_NZ);
+
+    /* Should wrap multiple times */
+    ts1 = VLC_TICK_0;
+    ts2 = TimeStampWrapAround(VLC_TICK_0 + TS_33BITS_ROLL_NZ * 2, ts1);
+    ASSERT(ts2 > ts1);
+    ASSERT(ts2 == ts1 + TS_33BITS_ROLL_NZ * 2);
+
+    ts1 = VLC_TICK_0 + TS_33BITS_HALF_ROLL_NZ;
+    ts2 = TimeStampWrapAround(VLC_TICK_0 + TS_33BITS_ROLL_NZ * 5, ts1);
+    ASSERT(ts2 > ts1);
+    ASSERT(ts2 == ts1 + TS_33BITS_ROLL_NZ * 5);
+
+    return 0;
+}


=====================================
test/modules/demux/ts_pes.c
=====================================
@@ -0,0 +1,271 @@
+/*****************************************************************************
+ * ts_pes.c: MPEG PES assembly tests
+ *****************************************************************************
+ * Copyright (C) 2020 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <vlc_common.h>
+#include <vlc_block.h>
+
+#include "../../../modules/demux/mpeg/ts_streams.h"
+#include "../../../modules/demux/mpeg/ts_pid_fwd.h"
+#include "../../../modules/demux/mpeg/ts_streams_private.h"
+#include "../../../modules/demux/mpeg/ts_pes.h"
+
+#include "../../libvlc/test.h"
+
+static void Parse(vlc_object_t *obj, void *priv, block_t *data, ts_90khz_t append_pcr)
+{
+    VLC_UNUSED(obj);
+    VLC_UNUSED(append_pcr);
+    block_t **pp_append = (block_t **) priv;
+    fprintf(stderr, "recv: ");
+    data = block_ChainGather(data);
+    for(size_t i=0; i<data->i_buffer; i++)
+        fprintf(stderr, "%2.2x ", data->p_buffer[i]);
+    fprintf(stderr, "\n");
+    block_ChainAppend(pp_append, data);
+}
+
+#define RESET do {\
+    block_ChainRelease(output);\
+    output = NULL;\
+    block_ChainRelease(pes.gather.p_data);\
+    memset(&pes, 0, sizeof(pes));\
+    pes.transport = TS_TRANSPORT_PES;\
+    pes.gather.pp_last = &pes.gather.p_data;\
+    } while(0)
+
+#define ASSERT(a) do {\
+    if(!(a)) { RESET; \
+        fprintf(stderr, "failed line %d\n", __LINE__); \
+        return 1; } \
+    } while(0)
+
+#define PKT_FROMSZ(a, b) do {\
+    pkt = block_Alloc(sizeof(a) + b);\
+    ASSERT(pkt);\
+    memcpy(pkt->p_buffer, a, sizeof(a));\
+    for(size_t i=1; i<1+b;i++)\
+        pkt->p_buffer[sizeof(a) + i] = i % 0xFF;\
+} while(0)
+
+#define PKT_FROM(a) PKT_FROMSZ(a, 0)
+
+int main()
+{
+    block_t *pkt;
+    block_t *output = NULL;
+    int outputcount = 0;
+    size_t outputsize = 0;
+
+    test_init();
+
+    ts_pes_parse_callback cb =
+    {
+        .p_obj = NULL,
+        .priv = &output,
+        .pf_parse = Parse
+    };
+
+    ts_stream_t pes;
+    memset(&pes, 0, sizeof(pes));
+    pes.transport = TS_TRANSPORT_PES;
+    pes.gather.pp_last = &pes.gather.p_data;
+
+    /* General case, aligned payloads */
+    /* payload == 0 */
+    const uint8_t aligned0[] = {
+        0x00, 0x00, 0x01, 0xe0, 0x00, 0x03, 0x80, 0x00, 0x00,
+    };
+    PKT_FROM(aligned0);
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 1);
+    ASSERT(outputsize == 6+3);
+    ASSERT(!memcmp(aligned0, output->p_buffer, outputsize));
+    RESET;
+    /* no output if not unit start */
+    PKT_FROM(aligned0);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, false, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    RESET;
+    /* no output if not unit start */
+    PKT_FROM(aligned0);
+    pkt->i_buffer = 1;
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, false, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    RESET;
+
+    /* payload == 6 */
+    const uint8_t aligned1[] = {
+        0x00, 0x00, 0x01, 0xe0, 0x00, 0x09, 0x80, 0x00, 0x00,
+        0xAA, 0xBB, 0xAA, 0xBB, 0xAA, 0xBB,
+    };
+    PKT_FROM(aligned1);
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 1);
+    ASSERT(outputsize == 6+3+6);
+    ASSERT(!memcmp(aligned1, output->p_buffer, outputsize));
+    RESET;
+    /* no output if not unit start */
+    PKT_FROM(aligned1);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, false, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    RESET;
+
+    /* payload == 30, uncomplete */
+    PKT_FROM(aligned1);
+    SetWBE(&pkt->p_buffer[4], 30);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    RESET;
+
+    /* packets assembly, payload > 188 - 6 - 4 */
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    SetWBE(&pkt->p_buffer[4], 250);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    ASSERT(pes.gather.i_data_size == 256);
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, false, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 1);
+    ASSERT(outputsize == 256);
+    RESET;
+
+    /* no packets assembly from unit start */
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    SetWBE(&pkt->p_buffer[4], 250);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    ASSERT(pes.gather.i_data_size == 256);
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 2);
+    RESET;
+
+    /* packets assembly, payload undef, use next sync code from another payload undef */
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    SetWBE(&pkt->p_buffer[4], 0);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    ASSERT(pes.gather.i_data_size == 0);
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    SetWBE(&pkt->p_buffer[4], 0);
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 1);
+    ASSERT(outputsize == 188);
+    RESET;
+
+    /* packets assembly, payload undef, use next sync code from fixed size */
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    SetWBE(&pkt->p_buffer[4], 0);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    ASSERT(pes.gather.i_data_size == 0);
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 2); /* secondary */
+    RESET;
+
+    /* packets assembly, payload undef, use next sync code from fixed size but uncomplete */
+    PKT_FROMSZ(aligned1, 188-sizeof(aligned1));
+    SetWBE(&pkt->p_buffer[4], 0);
+    ASSERT(!ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(!output);
+    ASSERT(pes.gather.i_data_size == 0);
+    PKT_FROM(aligned1);
+    pkt->i_buffer = 6;
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 1); /* can't output */
+    PKT_FROM(aligned1);
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, false, true, TS_90KHZ_INVALID)); /* add data for last output */
+    ASSERT(output); /* output */
+    RESET;
+
+    const uint8_t aligned2[] = {
+        0x00, 0x00, 0x01, 0xe0, 0x00, 0x03, 0x80, 0x00, 0x00,
+
+        0x00, 0x00, 0x01, 0xe0, 0x00, 0x07, 0x80, 0x00, 0x00,  /* PES 0xdb header */
+        0x00, 0x01, 0x02, 0x03,                                /* PES payload */
+
+        0x00, 0x00, 0x01, 0xe0, 0x00, 0x07, 0x80, 0x00, 0x00,  /* PES 0xdb header */
+        0xAA, 0xBB, 0xCC, 0xDD,                                /* PES payload */
+    };
+
+    /* If the payload_unit_start_indicator is set to '1', then one and only one
+     * PES packet starts in this transport stream packet. */
+    PKT_FROM(aligned2);
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 1);
+    RESET;
+
+    /* Broken PUSI tests */
+    pes.b_broken_PUSI_conformance = true;
+    PKT_FROM(aligned2);
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 3);
+    RESET;
+
+    pes.b_broken_PUSI_conformance = true;
+    PKT_FROM(aligned2);
+    pkt->p_buffer[0] = 0xFF;
+    ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+    ASSERT(output);
+    block_ChainProperties(output, &outputcount, &outputsize, NULL);
+    ASSERT(outputcount == 2);
+    RESET;
+
+    for(int split=12; split>9; split--)
+    {
+        pes.b_broken_PUSI_conformance = true;
+        PKT_FROM(aligned2);
+        pkt->i_buffer = split;
+        ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+        ASSERT(output);
+
+        PKT_FROM(aligned2);
+        pkt->p_buffer += split;
+        pkt->i_buffer -= split;
+        ASSERT(ts_pes_Gather(&cb, &pes, pkt, true, true, TS_90KHZ_INVALID));
+        ASSERT(output);
+
+        RESET;
+    }
+
+    return 0;
+}



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e4cf8ccefe49d9a2a4717409e18997f556b84e21...78988161fdc66355d04ae9540e7947c970b6ea81

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e4cf8ccefe49d9a2a4717409e18997f556b84e21...78988161fdc66355d04ae9540e7947c970b6ea81
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