[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