[vlc-commits] [Git][videolan/vlc][master] asf: Implement Support TimeCode in ASF

Steve Lhomme (@robUx4) gitlab at videolan.org
Tue Jun 9 05:38:31 UTC 2026



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
84db0028 by Ojus Chugh at 2026-06-09T05:22:19+00:00
asf: Implement Support TimeCode in ASF

- - - - -


4 changed files:

- modules/demux/asf/asf.c
- modules/demux/asf/libasf.c
- modules/demux/asf/libasf.h
- modules/demux/asf/libasf_guid.h


Changes:

=====================================
modules/demux/asf/asf.c
=====================================
@@ -35,12 +35,14 @@
 #include <vlc_dialog.h>
 
 #include <vlc_meta.h>                  /* vlc_meta_Set*, vlc_meta_New */
+#include <vlc_input.h>                 /* seekpoint_t, vlc_seekpoint_New */
 #include <vlc_access.h>                /* GET_PRIVATE_ID_STATE */
 #include <vlc_codecs.h>                /* VLC_BITMAPINFOHEADER, WAVEFORMATEX */
 #include <vlc_vout.h>
 
 #include <limits.h>
 #include <stdckdint.h>
+#include <stdlib.h>
 
 #include "asfpacket.h"
 #include "libasf.h"
@@ -140,6 +142,10 @@ typedef struct
     asf_packet_sys_t    packet_sys;
 
     vlc_meta_t          *meta;
+
+    /* timecode seekpoints built from WM TC index */
+    seekpoint_t        **pp_seekpoints;
+    int                  i_seekpoints;
 } demux_sys_t;
 
 static int      DemuxInit( demux_t * );
@@ -559,6 +565,76 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
         vlc_meta_Merge( p_meta, p_sys->meta );
         return VLC_SUCCESS;
 
+    case DEMUX_GET_TITLE_INFO:
+    {
+        input_title_t ***ppp_title = va_arg( args, input_title_t *** );
+        int *pi_int             = va_arg( args, int * );
+        int *pi_title_offset    = va_arg( args, int * );
+        int *pi_seekpoint_offset = va_arg( args, int * );
+
+        if( p_sys->i_seekpoints == 0 )
+            return VLC_EGENERIC;
+
+        *pi_int = 1;
+        *ppp_title = malloc( sizeof(input_title_t *) );
+        if( !*ppp_title )
+            return VLC_ENOMEM;
+
+        input_title_t *p_title = (*ppp_title)[0] = vlc_input_title_New();
+        if( !p_title )
+        {
+            free( *ppp_title );
+            return VLC_ENOMEM;
+        }
+
+        for( int i = 0; i < p_sys->i_seekpoints; i++ )
+        {
+            seekpoint_t *p_sp = vlc_seekpoint_Duplicate( p_sys->pp_seekpoints[i] );
+            if( unlikely(!p_sp) )
+                break;
+            TAB_APPEND( p_title->i_seekpoint, p_title->seekpoint, p_sp );
+        }
+
+        *pi_title_offset    = 0;
+        *pi_seekpoint_offset = 0;
+        return VLC_SUCCESS;
+    }
+
+    case DEMUX_SET_TITLE:
+        /* only one title */
+        if( va_arg( args, int ) != 0 )
+            return VLC_EGENERIC;
+        return VLC_SUCCESS;
+
+    case DEMUX_SET_SEEKPOINT:
+    {
+        const int i_seekpoint = va_arg( args, int );
+        if( i_seekpoint < 0 || i_seekpoint >= p_sys->i_seekpoints )
+            return VLC_EGENERIC;
+
+        const seekpoint_t *p_sp = p_sys->pp_seekpoints[i_seekpoint];
+        if( p_sp->i_time_offset == VLC_TICK_INVALID )
+            return VLC_EGENERIC;
+
+        SeekPrepare( p_demux );
+
+        if( p_sys->b_index && p_sys->i_length != 0 )
+        {
+            if( !SeekIndex( p_demux, p_sp->i_time_offset, -1 ) )
+                return VLC_SUCCESS;
+        }
+        /* fallback: proportional seek by time offset */
+        if( p_sys->i_length > 0 )
+        {
+            double f_pos = (double)p_sp->i_time_offset / p_sys->i_length;
+            WaitKeyframe( p_demux );
+            return vlc_stream_Seek( p_demux->s,
+                p_sys->i_data_begin + (uint64_t)( f_pos *
+                    (double)( p_sys->i_data_end - p_sys->i_data_begin ) ) );
+        }
+        return VLC_EGENERIC;
+    }
+
     case DEMUX_CAN_SEEK:
         if ( !p_sys->p_fp ||
              ( !( p_sys->p_fp->i_flags & ASF_FILE_PROPERTIES_SEEKABLE ) && !p_sys->b_index ) )
@@ -816,6 +892,8 @@ static int DemuxInit( demux_t *p_demux )
     p_sys->i_data_end   = 0;
     p_sys->i_preroll_start = 0;
     p_sys->meta         = NULL;
+    p_sys->pp_seekpoints = NULL;
+    p_sys->i_seekpoints  = 0;
 
     /* Now load all object ( except raw data ) */
     vlc_stream_Control( p_demux->s, STREAM_CAN_FASTSEEK,
@@ -1365,6 +1443,39 @@ static int DemuxInit( demux_t *p_demux )
     p_sys->packet_sys.pi_preroll_start = &p_sys->i_preroll_start;
     p_sys->packet_sys.b_can_hold_multiple_packets = false;
 
+    /* Build seekpoints from the WM TC index.
+     * Each entry's i_timecode is a presentation time in milliseconds.
+     * The first two entries are header/metadata entries and are skipped. */
+    if( p_sys->p_root && p_sys->p_root->p_timecode_index )
+    {
+        const asf_object_timecode_index_t *p_tc =
+                p_sys->p_root->p_timecode_index;
+
+        msg_Dbg( p_demux, "timecode index: %u entries", p_tc->i_index_entry_count );
+
+        for( uint32_t i = 2; i < p_tc->i_index_entry_count; i++ )
+        {
+            const uint32_t i_tc_ms = p_tc->timecode_entry[i].i_timecode;
+
+            seekpoint_t *p_sp = vlc_seekpoint_New();
+            if( unlikely(!p_sp) )
+                break;
+
+            p_sp->i_time_offset = VLC_TICK_FROM_MS( i_tc_ms );
+
+            if( asprintf( &p_sp->psz_name, "%u:%02u:%02u",
+                          i_tc_ms / 3600000,
+                          (i_tc_ms % 3600000) / 60000,
+                          (i_tc_ms % 60000) / 1000 ) < 0 )
+            {
+                vlc_seekpoint_Delete( p_sp );
+                break;
+            }
+
+            TAB_APPEND( p_sys->i_seekpoints, p_sys->pp_seekpoints, p_sp );
+        }
+    }
+
     return VLC_SUCCESS;
 
 error:
@@ -1419,6 +1530,10 @@ static void DemuxEnd( demux_t *p_demux )
         p_sys->meta = NULL;
     }
 
+    for( int i = 0; i < p_sys->i_seekpoints; i++ )
+        vlc_seekpoint_Delete( p_sys->pp_seekpoints[i] );
+    TAB_CLEAN( p_sys->i_seekpoints, p_sys->pp_seekpoints );
+
     FlushQueues( p_demux );
 
     for( int i = 0; i < MAX_ASF_TRACKS; i++ )


=====================================
modules/demux/asf/libasf.c
=====================================
@@ -1430,6 +1430,99 @@ static void ASF_FreeObject_marker( asf_object_t *p_obj)
     FREENULL( p_mk->name );
 }
 
+static int ASF_ReadObject_timecode_index(stream_t *s, asf_object_t *p_obj)
+{
+    asf_object_timecode_index_t *p_tc_index = &p_obj->timecode_index;
+    const uint8_t      *p_peek;
+
+    /* minimum: 24 common + 24 specific header = 48 bytes */
+    if( p_tc_index->i_object_size < 48
+     || p_tc_index->i_object_size > INT32_MAX
+     || vlc_stream_Peek( s, &p_peek, p_tc_index->i_object_size )
+        < (int64_t)p_tc_index->i_object_size )
+        return VLC_SUCCESS;
+
+    /* specific header fields at offset 24 (after common GUID+size):
+     *   +0  DWORD index_entry_count (meaning unclear, not entry count)
+     *   +4  DWORD index_entry_time_interval
+     *   +8  DWORD max_packet_count
+     *   +12 WORD  index_specifiers_count
+     *   +14 ...   index specifiers (4 bytes each)
+     * entries start at offset 48 (24 common + 24 specific header)
+     * each entry: 4 bytes timecode_ms + 4 bytes packet_number = 8 bytes
+     */
+    p_tc_index->i_index_entry_time_interval = GetDWLE( p_peek + 28 );
+    p_tc_index->i_max_packet_count = GetDWLE( p_peek + 32 );
+    p_tc_index->i_index_entry_count = (p_tc_index->i_object_size - 48) / 8;
+    p_tc_index->timecode_entry = NULL;
+
+#ifdef ASF_DEBUG
+    msg_Dbg( s,
+            "read \"timecode index object\""
+            " index_entry_time_interval:%u max_packet_count:%u"
+            " index_entry_count:%u",
+            p_tc_index->i_index_entry_time_interval,
+            p_tc_index->i_max_packet_count,
+            p_tc_index->i_index_entry_count );
+#endif
+
+    if( !p_tc_index->i_index_entry_count )
+        return VLC_SUCCESS;
+
+    p_tc_index->timecode_entry = calloc( p_tc_index->i_index_entry_count,
+                                         sizeof(asf_timecode_entry_t) );
+    if( !p_tc_index->timecode_entry )
+    {
+        p_tc_index->i_index_entry_count = 0;
+        return VLC_ENOMEM;
+    }
+
+    const uint8_t *p_data = p_peek + 48;
+    for( uint32_t i = 0; i < p_tc_index->i_index_entry_count; i++, p_data += 8 )
+    {
+        /* on-disk layout: timecode_ms(4LE) then packet_number(4LE) */
+        p_tc_index->timecode_entry[i].i_timecode = GetDWLE( p_data );
+        p_tc_index->timecode_entry[i].i_packet_number = GetDWLE( p_data + 4 );
+    }
+
+    return VLC_SUCCESS;
+}
+
+static int ASF_ReadObject_timecode_index_parameters(stream_t *s, asf_object_t *p_obj)
+{
+    asf_object_timecode_index_parameters_t *p_tc_params = &p_obj->timecode_index_parameters;
+    const uint8_t      *p_peek;
+
+    if( p_tc_params->i_object_size < 40
+     || p_tc_params->i_object_size > INT32_MAX
+     || vlc_stream_Peek( s, &p_peek, p_tc_params->i_object_size )
+        < (int64_t)p_tc_params->i_object_size )
+        return VLC_SUCCESS;
+
+    p_tc_params->i_index_entry_time_interval = GetDWLE( p_peek + ASF_OBJECT_COMMON_SIZE );
+    p_tc_params->i_max_packet_count = GetDWLE( p_peek + ASF_OBJECT_COMMON_SIZE + 8 );
+    p_tc_params->i_index_entry_count = GetDWLE( p_peek + ASF_OBJECT_COMMON_SIZE + 12 );
+
+#ifdef ASF_DEBUG
+    msg_Dbg( s,
+            "read \"timecode index parameters object\" "
+            "index_entry_time_interval:%u max_packet_count:%u "
+            "index_entry_count:%u",
+            p_tc_params->i_index_entry_time_interval,
+            p_tc_params->i_max_packet_count,
+            p_tc_params->i_index_entry_count );
+#endif
+
+    return VLC_SUCCESS;
+}
+
+static void ASF_FreeObject_timecode_index( asf_object_t *p_obj )
+{
+    asf_object_timecode_index_t *p_tc_index = &p_obj->timecode_index;
+
+    FREENULL( p_tc_index->timecode_entry );
+}
+
 static int ASF_ReadObject_Raw(stream_t *s, asf_object_t *p_obj)
 {
     VLC_UNUSED(s);
@@ -1489,6 +1582,10 @@ static const struct ASF_Object_Function_entry
     { &asf_object_extended_content_description, ASF_OBJECT_OTHER,
       ASF_ReadObject_extended_content_description,
       ASF_FreeObject_extended_content_description },
+    { &asf_object_timecode_index_guid, ASF_OBJECT_TIMECODE_INDEX,
+      ASF_ReadObject_timecode_index, ASF_FreeObject_timecode_index },
+    { &asf_object_timecode_index_parameters_guid, ASF_OBJECT_TIMECODE_INDEX_PARAMETERS,
+      ASF_ReadObject_timecode_index_parameters, ASF_FreeObject_Null },
     { &asf_object_content_encryption_guid, ASF_OBJECT_OTHER,
       ASF_ReadObject_Raw, ASF_FreeObject_Null },
     { &asf_object_advanced_content_encryption_guid, ASF_OBJECT_OTHER,
@@ -1639,6 +1736,8 @@ static const struct
     { &asf_object_stream_prioritization, "Stream Prioritization" },
     { &asf_object_bitrate_mutual_exclusion_guid, "Bitrate Mutual Exclusion" },
     { &asf_object_extended_content_description, "Extended content description"},
+    { &asf_object_timecode_index_guid, "Timecode Index"},
+    { &asf_object_timecode_index_parameters_guid, "Timecode Index Parameters"},
     { &asf_object_content_encryption_guid, "Content Encryption"},
     { &asf_object_advanced_content_encryption_guid, "Advanced Content Encryption"},
     { &asf_object_extended_content_encryption_guid, "Extended Content Encryption"},
@@ -1721,7 +1820,10 @@ asf_object_root_t *ASF_ReadObjectRoot( stream_t *s, int b_seekable )
     p_root->p_data  = NULL;
     p_root->p_fp    = NULL;
     p_root->p_index = NULL;
+    p_root->p_he    = NULL;
     p_root->p_metadata = NULL;
+    p_root->p_timecode_index = NULL;
+    p_root->p_timecode_index_parameters = NULL;
 
     for( ; ; )
     {
@@ -1746,6 +1848,10 @@ asf_object_root_t *ASF_ReadObjectRoot( stream_t *s, int b_seekable )
                 if ( p_root->p_index ) break;
                 p_root->p_index = (asf_object_index_t*)p_obj;
                 break;
+            case( ASF_OBJECT_TIMECODE_INDEX ):
+                if ( p_root->p_timecode_index ) break;
+                p_root->p_timecode_index = (asf_object_timecode_index_t*)p_obj;
+                break;
             default:
                 msg_Warn( s, "unknown top-level object found: " GUID_FMT,
                       GUID_PRINT( p_obj->common.i_object_id ) );
@@ -1786,9 +1892,13 @@ asf_object_root_t *ASF_ReadObjectRoot( stream_t *s, int b_seekable )
                                 &asf_object_header_extension_guid, 0 );
             if( p_hdr_ext )
             {
+                p_root->p_he = (asf_object_header_extension_t *)p_hdr_ext;
                 p_root->p_metadata =
                     ASF_FindObject( p_hdr_ext,
                                     &asf_object_metadata_guid, 0 );
+                p_root->p_timecode_index_parameters =
+                    ASF_FindObject( p_hdr_ext,
+                                    &asf_object_timecode_index_parameters_guid, 0 );
             }
 
             ASF_ObjectDumpDebug( VLC_OBJECT(s),


=====================================
modules/demux/asf/libasf.h
=====================================
@@ -252,6 +252,28 @@ typedef struct
     } bitrate[ASF_MAX_STREAMNUMBER + 1];
 } asf_object_stream_bitrate_properties_t;
 
+typedef struct
+{
+    uint32_t i_packet_number;
+    uint32_t i_timecode; /* presentation time in milliseconds */
+} asf_timecode_entry_t;
+
+typedef struct
+{
+    ASF_OBJECT_COMMON
+    uint32_t    i_index_entry_time_interval;
+    uint32_t    i_max_packet_count;
+    uint32_t    i_index_entry_count;
+    asf_timecode_entry_t *timecode_entry;
+} asf_object_timecode_index_t;
+
+typedef struct
+{
+    ASF_OBJECT_COMMON
+    uint32_t    i_index_entry_time_interval;
+    uint32_t    i_max_packet_count;
+    uint32_t    i_index_entry_count;
+} asf_object_timecode_index_parameters_t;
 
 typedef struct
 {
@@ -353,10 +375,11 @@ typedef struct
 
     /* from asf_object_header_t */
     asf_object_file_properties_t *p_fp;
-
+    asf_object_header_extension_t *p_he;
     /* from asf_object_header_extension_t */
     asf_object_metadata_t *p_metadata;
-
+    asf_object_timecode_index_t *p_timecode_index;
+    asf_object_timecode_index_parameters_t *p_timecode_index_parameters;
 } asf_object_root_t;
 
 /****************************************************************************
@@ -383,7 +406,8 @@ typedef union asf_object_u
     asf_object_stream_prioritization_t stream_prioritization;
     asf_object_bitrate_mutual_exclusion_t bitrate_mutual_exclusion;
     asf_object_extended_content_description_t extended_content_description;
-
+    asf_object_timecode_index_t     timecode_index;
+    asf_object_timecode_index_parameters_t timecode_index_parameters;
 } asf_object_t;
 
 asf_object_root_t *ASF_ReadObjectRoot( stream_t *, int b_seekable );


=====================================
modules/demux/asf/libasf_guid.h
=====================================
@@ -43,6 +43,8 @@ enum
     ASF_OBJECT_CONTENT_DESCRIPTION,
     ASF_OBJECT_METADATA,
     ASF_OBJECT_PADDING,
+    ASF_OBJECT_TIMECODE_INDEX,
+    ASF_OBJECT_TIMECODE_INDEX_PARAMETERS,
     ASF_OBJECT_OTHER,
 };
 



View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/84db002877000d02e985c6cbcde6026626fc35ff

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/84db002877000d02e985c6cbcde6026626fc35ff
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list