Commit 1583a494 authored by François Cartegnie's avatar François Cartegnie 🤞

demux: mp4: rewrite main non fragmented Demux logic

Makes main demux aware of interleaving, and avoid
access layer seeks. Also improves the large interleaves
and non interleaved playback.

Fixes the pcr jumps, as a min() dts based logic isn't
suitable for edit lists.

Improves empty edits handling by keeping demuxing on
empty samples time.
parent 66c4cbb0
......@@ -79,7 +79,6 @@ struct demux_sys_t
bool b_fragmented; /* fMP4 */
bool b_seekable;
bool b_fastseekable;
bool b_seekmode;
bool b_index_probed;
bool b_fragments_probed;
......@@ -106,6 +105,9 @@ struct demux_sys_t
int64_t i_preroll_start;
};
#define DEMUX_INCREMENT (CLOCK_FREQ / 8) /* How far the pcr will go, each round */
#define DEMUX_TRACK_MAX_PRELOAD (CLOCK_FREQ * 15) /* maximum preloading, to deal with interleaving */
/*****************************************************************************
* Declaration of local function
*****************************************************************************/
......@@ -125,7 +127,7 @@ static uint32_t MP4_TrackGetReadSize( mp4_track_t *, uint32_t * );
static int MP4_TrackNextSample( demux_t *, mp4_track_t *, uint32_t );
static void MP4_TrackSetELST( demux_t *, mp4_track_t *, int64_t );
static void MP4_UpdateSeekpoint( demux_t * );
static void MP4_UpdateSeekpoint( demux_t *, int64_t );
static MP4_Box_t * MP4_GetTrexByTrackID( MP4_Box_t *p_moov, const uint32_t i_id );
static void MP4_GetDefaultSizeAndDuration( demux_t *p_demux,
......@@ -400,6 +402,13 @@ static block_t * MP4_EIA608_Convert( block_t * p_block )
return p_newblock;
}
static uint32_t MP4_TrackGetRunSeq( mp4_track_t *p_track )
{
if( p_track->i_chunk_count > 0 )
return p_track->chunk[p_track->i_chunk].i_virtual_run_number;
return 0;
}
/* Analyzes chunks to find max interleave length
* sets flat flag if no interleaving is in use */
static void MP4_GetInterleaving( demux_t *p_demux, uint64_t *pi_max_contiguous, bool *pb_flat )
......@@ -587,7 +596,6 @@ static int Open( vlc_object_t * p_this )
vlc_stream_Control( p_demux->s, STREAM_CAN_SEEK, &p_sys->b_seekable );
vlc_stream_Control( p_demux->s, STREAM_CAN_FASTSEEK,
&p_sys->b_fastseekable );
p_sys->b_seekmode = p_sys->b_fastseekable;
/*Set exported functions */
p_demux->pf_demux = Demux;
......@@ -907,16 +915,15 @@ static int Open( vlc_object_t * p_this )
DumpFragments( VLC_OBJECT(p_demux), &p_sys->fragments, p_sys->i_timescale );
#endif
if( !p_sys->b_fragmented && p_sys->i_tracks > 1 && p_sys->b_seekable && !p_sys->b_seekmode )
if( !p_sys->b_fragmented && p_sys->i_tracks > 1 && !p_sys->b_fastseekable )
{
uint64_t i_max_continuity;
bool b_flat;
MP4_GetInterleaving( p_demux, &i_max_continuity, &b_flat );
if( b_flat )
{
msg_Warn( p_demux, "that media doesn't look interleaved, will need to seek");
p_sys->b_seekmode = true;
}
msg_Warn( p_demux, "that media doesn't look interleaved, will need to seek");
else if( i_max_continuity > DEMUX_TRACK_MAX_PRELOAD )
msg_Warn( p_demux, "that media doesn't look properly interleaved, will need to seek");
}
/* */
......@@ -1081,17 +1088,97 @@ static block_t * MP4_RTPHint_Convert( demux_t *p_demux, block_t *p_block, vlc_fo
*****************************************************************************
* TODO check for newly selected track (ie audio upt to now )
*****************************************************************************/
static int DemuxTrack( demux_t *p_demux, mp4_track_t *tk, uint64_t i_readpos )
{
uint32_t i_nb_samples = 0;
uint32_t i_samplessize = 0;
if( !tk->b_ok || tk->i_sample >= tk->i_sample_count )
return VLC_DEMUXER_EOF;
if( tk->b_chapters_source )
return VLC_DEMUXER_EGENERIC;
uint32_t i_run_seq = MP4_TrackGetRunSeq( tk );
mtime_t i_current_nzdts = MP4_TrackGetDTS( p_demux, tk );
const mtime_t i_demux_max_nzdts = i_current_nzdts + DEMUX_TRACK_MAX_PRELOAD;
for( ; i_demux_max_nzdts > i_current_nzdts; )
{
if( tk->i_sample >= tk->i_sample_count )
break;
#if 0
msg_Dbg( p_demux, "tk(%i)=%"PRId64" mv=%"PRId64" pos=%"PRIu64, tk->i_track_ID,
MP4_TrackGetDTS( p_demux, tk ),
MP4_GetMoviePTS( p_sys ), i_readpos );
#endif
i_samplessize = MP4_TrackGetReadSize( tk, &i_nb_samples );
if( i_samplessize > 0 )
{
block_t *p_block;
int64_t i_delta;
if( vlc_stream_Tell( p_demux->s ) != i_readpos )
{
if( vlc_stream_Seek( p_demux->s, i_readpos ) )
{
msg_Warn( p_demux, "track[0x%x] will be disabled (eof?)"
": Failed to seek to %"PRIu64,
tk->i_track_ID, i_readpos );
MP4_TrackUnselect( p_demux, tk );
goto end;
}
}
/* now read pes */
if( !(p_block = MP4_Block_Read( p_demux, tk, i_samplessize )) )
{
msg_Warn( p_demux, "track[0x%x] will be disabled (eof?)"
": Failed to read %d bytes sample at %"PRIu64,
tk->i_track_ID, i_samplessize, i_readpos );
MP4_TrackUnselect( p_demux, tk );
goto end;
}
/* dts */
p_block->i_dts = VLC_TS_0 + i_current_nzdts;
/* pts */
if( MP4_TrackGetPTSDelta( p_demux, tk, &i_delta ) )
p_block->i_pts = p_block->i_dts + i_delta;
else if( tk->fmt.i_cat != VIDEO_ES )
p_block->i_pts = p_block->i_dts;
else
p_block->i_pts = VLC_TS_INVALID;
MP4_Block_Send( p_demux, tk, p_block );
}
/* Next sample */
if ( i_nb_samples ) /* sample size could be 0, need to go fwd. see return */
MP4_TrackNextSample( p_demux, tk, i_nb_samples );
uint32_t i_next_run_seq = MP4_TrackGetRunSeq( tk );
if( i_next_run_seq != i_run_seq )
break;
i_current_nzdts = MP4_TrackGetDTS( p_demux, tk );
i_readpos = MP4_TrackGetPos( tk );
}
return VLC_DEMUXER_SUCCESS;
end:
return VLC_DEMUXER_EGENERIC;
}
static int Demux( demux_t *p_demux )
{
demux_sys_t *p_sys = p_demux->p_sys;
unsigned int i_track;
unsigned int i_track_selected;
/* check for newly selected/unselected track */
for( i_track = 0, i_track_selected = 0; i_track < p_sys->i_tracks;
i_track++ )
for( i_track = 0; i_track < p_sys->i_tracks; i_track++ )
{
mp4_track_t *tk = &p_sys->track[i_track];
bool b;
......@@ -1112,178 +1199,108 @@ static int Demux( demux_t *p_demux )
{
MP4_TrackSelect( p_demux, tk, MP4_GetMoviePTS( p_sys ) );
}
if( tk->b_selected )
{
i_track_selected++;
}
}
if( i_track_selected <= 0 )
const mtime_t i_nztime = MP4_GetMoviePTS( p_sys );
/* We demux/set pcr, even without selected tracks, (empty edits, ...) */
if( p_sys->i_pcr != VLC_TS_INVALID /* not after a seek */ )
{
p_sys->i_time += __MAX( p_sys->i_timescale / 10 , 1 );
if( p_sys->i_timescale > 0 )
bool b_eof = true;
for( i_track = 0; i_track < p_sys->i_tracks; i_track++ )
{
int64_t i_length = CLOCK_FREQ *
(mtime_t)p_sys->i_overall_duration /
(mtime_t)p_sys->i_timescale;
if( MP4_GetMoviePTS( p_sys ) >= i_length )
return VLC_DEMUXER_EOF;
return VLC_DEMUXER_SUCCESS;
mp4_track_t *tk = &p_sys->track[i_track];
if( !tk->b_ok || tk->b_chapters_source )
continue;
/* Test for EOF on each track (samples count, edit list) */
b_eof &= ( i_nztime > MP4_TrackGetDTS( p_demux, tk ) );
}
msg_Warn( p_demux, "no track selected, exiting..." );
return VLC_DEMUXER_EOF;
if( b_eof )
return VLC_DEMUXER_EOF;
}
/* */
MP4_UpdateSeekpoint( p_demux );
/* first wait for the good time to read a packet */
p_sys->i_pcr = MP4_GetMoviePTS( p_sys );
/* !important! Ensure clock is set before sending data */
if( p_sys->i_pcr == VLC_TS_INVALID )
es_out_Control( p_demux->out, ES_OUT_SET_PCR, VLC_TS_0 + i_nztime );
bool b_data_sent = false;
/* Find next track matching contiguous data */
mp4_track_t *tk = NULL;
uint64_t i_candidate_pos = UINT64_MAX;
mtime_t i_candidate_dts = INT64_MAX;
for( i_track = 0; i_track < p_sys->i_tracks; i_track++ )
const mtime_t i_scaledincrement = DEMUX_INCREMENT * p_sys->i_timescale / CLOCK_FREQ;
int i_status;
/* demux up to increment amount of data on every track, or just set pcr if empty data */
for( ;; )
{
mp4_track_t *tk_tmp = &p_sys->track[i_track];
if( !tk_tmp->b_ok || tk_tmp->b_chapters_source || !tk_tmp->b_selected || tk_tmp->i_sample >= tk_tmp->i_sample_count )
continue;
mp4_track_t *tk = NULL;
i_status = VLC_DEMUXER_EGENERIC;
if ( p_sys->b_seekmode )
{
mtime_t i_dts = MP4_TrackGetDTS( p_demux, tk_tmp );
if ( i_dts <= i_candidate_dts )
{
tk = tk_tmp;
i_candidate_dts = i_dts;
i_candidate_pos = MP4_TrackGetPos( tk_tmp );
}
}
else
/* First pass, find any track within our target increment, ordered by position */
for( i_track = 0; i_track < p_sys->i_tracks; i_track++ )
{
/* Try to avoid seeking on non fastseekable. Will fail with non interleaved content */
uint64_t i_pos = MP4_TrackGetPos( tk_tmp );
if ( i_pos <= i_candidate_pos )
mp4_track_t *tk_tmp = &p_sys->track[i_track];
if( !tk_tmp->b_ok || tk_tmp->b_chapters_source ||
!tk_tmp->b_selected || tk_tmp->i_sample >= tk_tmp->i_sample_count )
continue;
if ( MP4_TrackGetDTS( p_demux, tk_tmp ) <= i_nztime + DEMUX_INCREMENT )
{
i_candidate_pos = i_pos;
tk = tk_tmp;
if( tk == NULL || MP4_TrackGetPos( tk_tmp ) < MP4_TrackGetPos( tk ) )
tk = tk_tmp;
}
}
}
uint32_t i_nb_samples = 0;
uint32_t i_samplessize = 0;
if ( !tk )
{
msg_Dbg( p_demux, "Could not select track by data position" );
goto end;
}
else if ( p_sys->b_seekmode )
{
if( vlc_stream_Seek( p_demux->s, i_candidate_pos ) )
if( tk )
{
msg_Warn( p_demux, "track[0x%x] will be disabled (eof?)",
tk->i_track_ID );
MP4_TrackUnselect( p_demux, tk );
goto end;
}
}
#if 0
msg_Dbg( p_demux, "tk(%i)=%"PRId64" mv=%"PRId64" pos=%"PRIu64, tk->i_track_ID,
MP4_TrackGetDTS( p_demux, tk ),
MP4_GetMoviePTS( p_sys ), i_candidate_pos );
#endif
i_samplessize = MP4_TrackGetReadSize( tk, &i_nb_samples );
if( i_samplessize > 0 )
{
block_t *p_block;
int64_t i_delta;
uint64_t i_current_pos;
/* go,go go ! */
i_current_pos = vlc_stream_Tell( p_demux->s );
if( i_current_pos != i_candidate_pos )
{
if( vlc_stream_Seek( p_demux->s, i_candidate_pos ) )
/* Second pass, refine and find any best candidate having a chunk pos closer than
* current candidate (avoids seeks when increment falls between the 2) from
* current position, but within extended interleave time */
for( i_track = 0; i_track < p_sys->i_tracks; i_track++ )
{
msg_Warn( p_demux, "track[0x%x] will be disabled (eof?)"
": Failed to seek to %"PRIu64,
tk->i_track_ID, i_candidate_pos );
MP4_TrackUnselect( p_demux, tk );
goto end;
mp4_track_t *tk_tmp = &p_sys->track[i_track];
if( tk_tmp == tk ||
!tk_tmp->b_ok || tk_tmp->b_chapters_source ||
!tk_tmp->b_selected || tk_tmp->i_sample >= tk_tmp->i_sample_count )
continue;
mtime_t i_nzdts = MP4_TrackGetDTS( p_demux, tk_tmp );
assert(i_nzdts >= p_sys->i_pcr);
if ( i_nzdts <= i_nztime + DEMUX_TRACK_MAX_PRELOAD )
{
/* Found a better candidate to avoid seeking */
if( MP4_TrackGetPos( tk_tmp ) < MP4_TrackGetPos( tk ) )
tk = tk_tmp;
/* Note: previous candidate will be repicked on next loop */
}
}
i_current_pos = i_candidate_pos;
}
/* now read pes */
if( !(p_block = MP4_Block_Read( p_demux, tk, i_samplessize )) )
{
msg_Warn( p_demux, "track[0x%x] will be disabled (eof?)"
": Failed to read %d bytes sample at %"PRIu64,
tk->i_track_ID, i_samplessize, i_current_pos );
MP4_TrackUnselect( p_demux, tk );
goto end;
uint64_t i_pos = MP4_TrackGetPos( tk );
int i_ret = DemuxTrack( p_demux, tk, i_pos );
if( i_ret == VLC_DEMUXER_SUCCESS )
i_status = VLC_DEMUXER_SUCCESS;
}
/* dts */
p_block->i_dts = VLC_TS_0 + MP4_TrackGetDTS( p_demux, tk );
/* pts */
if( MP4_TrackGetPTSDelta( p_demux, tk, &i_delta ) )
p_block->i_pts = p_block->i_dts + i_delta;
else if( tk->fmt.i_cat != VIDEO_ES )
p_block->i_pts = p_block->i_dts;
else
p_block->i_pts = VLC_TS_INVALID;
if ( !b_data_sent )
{
es_out_Control( p_demux->out, ES_OUT_SET_PCR, VLC_TS_0 + p_sys->i_pcr );
b_data_sent = true;
i_status = VLC_DEMUXER_SUCCESS;
}
MP4_Block_Send( p_demux, tk, p_block );
if( i_status != VLC_DEMUXER_SUCCESS || !tk )
break;
}
/* Next sample */
if ( i_nb_samples ) /* sample size could be 0, need to go fwd. see return */
MP4_TrackNextSample( p_demux, tk, i_nb_samples );
p_sys->i_time += __MAX(i_scaledincrement, 1);
p_sys->i_pcr = i_nztime + DEMUX_INCREMENT;
end:
if ( b_data_sent )
{
p_sys->i_pcr = INT64_MAX;
for( i_track = 0; i_track < p_sys->i_tracks; i_track++ )
{
mp4_track_t *tk = &p_sys->track[i_track];
if ( !tk->b_ok || !tk->b_selected ||
(tk->fmt.i_cat != AUDIO_ES && tk->fmt.i_cat != VIDEO_ES) )
continue;
/* */
MP4_UpdateSeekpoint( p_demux, i_nztime + DEMUX_INCREMENT );
mtime_t i_dts = MP4_TrackGetDTS( p_demux, tk );
p_sys->i_pcr = __MIN( i_dts, p_sys->i_pcr );
p_sys->i_time = p_sys->i_pcr * p_sys->i_timescale / CLOCK_FREQ;
}
}
es_out_Control( p_demux->out, ES_OUT_SET_PCR, VLC_TS_0 + p_sys->i_pcr );
return (b_data_sent || ( i_samplessize == 0 && i_nb_samples )) ? VLC_DEMUXER_SUCCESS : VLC_DEMUXER_EOF;
return (i_status == VLC_DEMUXER_SUCCESS) ? VLC_DEMUXER_SUCCESS : VLC_DEMUXER_EOF;
}
static void MP4_UpdateSeekpoint( demux_t *p_demux )
static void MP4_UpdateSeekpoint( demux_t *p_demux, int64_t i_time )
{
demux_sys_t *p_sys = p_demux->p_sys;
int64_t i_time;
int i;
if( !p_sys->p_title )
return;
i_time = MP4_GetMoviePTS( p_sys );
for( i = 0; i < p_sys->p_title->i_seekpoint; i++ )
{
if( i_time < p_sys->p_title->seekpoint[i]->i_time_offset )
......@@ -1315,7 +1332,7 @@ static int Seek( demux_t *p_demux, mtime_t i_date )
mp4_track_t *tk = &p_sys->track[i_track];
MP4_TrackSeek( p_demux, tk, i_date );
}
MP4_UpdateSeekpoint( p_demux );
MP4_UpdateSeekpoint( p_demux, i_date );
MP4ASF_ResetFrames( p_sys );
es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, i_date );
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment