Commit c9e1c0b8 authored by François Cartegnie's avatar François Cartegnie 🤞

mux: mp4: fix edit lists and track offsets

parent 8df6a21f
...@@ -55,13 +55,16 @@ bool mp4mux_trackinfo_Init(mp4mux_trackinfo_t *p_stream) ...@@ -55,13 +55,16 @@ bool mp4mux_trackinfo_Init(mp4mux_trackinfo_t *p_stream)
p_stream->i_read_duration = 0; p_stream->i_read_duration = 0;
p_stream->i_timescale = CLOCK_FREQ; p_stream->i_timescale = CLOCK_FREQ;
p_stream->i_starttime = 0; p_stream->i_firstdts = 0;
p_stream->b_hasbframes = false; p_stream->b_hasbframes = false;
p_stream->i_stco_pos = 0; p_stream->i_stco_pos = 0;
p_stream->i_trex_default_length = 0; p_stream->i_trex_default_length = 0;
p_stream->i_trex_default_size = 0; p_stream->i_trex_default_size = 0;
p_stream->i_edits_count = 0;
p_stream->p_edits = NULL;
return true; return true;
} }
...@@ -71,6 +74,7 @@ void mp4mux_trackinfo_Clear(mp4mux_trackinfo_t *p_stream) ...@@ -71,6 +74,7 @@ void mp4mux_trackinfo_Clear(mp4mux_trackinfo_t *p_stream)
if (p_stream->a52_frame) if (p_stream->a52_frame)
block_Release(p_stream->a52_frame); block_Release(p_stream->a52_frame);
free(p_stream->entry); free(p_stream->entry);
free(p_stream->p_edits);
} }
...@@ -164,6 +168,70 @@ static void matrix_apply_rotation(es_format_t *fmt, uint32_t mvhd_matrix[9]) ...@@ -164,6 +168,70 @@ static void matrix_apply_rotation(es_format_t *fmt, uint32_t mvhd_matrix[9])
mvhd_matrix[4] = mvhd_matrix[1] ? 0 : 0x10000; mvhd_matrix[4] = mvhd_matrix[1] ? 0 : 0x10000;
} }
static void AddEdit(bo_t *elst,
int64_t i_movie_scaled_duration,
int64_t i_media_scaled_time,
bool b_64_ext)
{
if(b_64_ext)
{
bo_add_64be(elst, i_movie_scaled_duration);
bo_add_64be(elst, i_media_scaled_time);
}
else
{
bo_add_32be(elst, i_movie_scaled_duration);
bo_add_32be(elst, i_media_scaled_time);
}
bo_add_16be(elst, 1);
bo_add_16be(elst, 0);
}
static bo_t *GetEDTS( mp4mux_trackinfo_t *p_track, uint32_t i_movietimescale, bool b_64_ext)
{
if(p_track->i_edits_count == 0)
return NULL;
bo_t *edts = box_new("edts");
bo_t *elst = box_full_new("elst", b_64_ext ? 1 : 0, 0);
if(!elst || !edts)
{
bo_free(elst);
bo_free(edts);
return NULL;
}
uint32_t i_total_edits = p_track->i_edits_count;
for(unsigned i=0; i<p_track->i_edits_count; i++)
{
/* !WARN! media time must start sample time 0, we need a -1 edit for start offsets */
if(p_track->p_edits[i].i_start_offset)
i_total_edits++;
}
bo_add_32be(elst, i_total_edits);
for(unsigned i=0; i<p_track->i_edits_count; i++)
{
if(p_track->p_edits[i].i_start_offset)
{
AddEdit(elst,
p_track->p_edits[i].i_start_offset * i_movietimescale / CLOCK_FREQ,
-1,
b_64_ext);
}
/* !WARN AGAIN! Uses different Timescales ! */
AddEdit(elst,
p_track->p_edits[i].i_duration * i_movietimescale / CLOCK_FREQ,
p_track->p_edits[i].i_start_time * p_track->i_timescale / CLOCK_FREQ,
b_64_ext);
}
box_gather(edts, elst);
return edts;
}
static bo_t *GetESDS(mp4mux_trackinfo_t *p_track) static bo_t *GetESDS(mp4mux_trackinfo_t *p_track)
{ {
bo_t *esds; bo_t *esds;
...@@ -1573,46 +1641,9 @@ bo_t * mp4mux_GetMoovBox(vlc_object_t *p_obj, mp4mux_trackinfo_t **pp_tracks, un ...@@ -1573,46 +1641,9 @@ bo_t * mp4mux_GetMoovBox(vlc_object_t *p_obj, mp4mux_trackinfo_t **pp_tracks, un
/* *** add /moov/trak/edts and elst */ /* *** add /moov/trak/edts and elst */
if ( !b_fragmented ) if ( !b_fragmented )
{ {
bo_t *elst = box_full_new("elst", b_64_ext ? 1 : 0, 0); bo_t *edts = GetEDTS(p_stream, i_movie_timescale, b_64_ext);
if(elst) if(edts)
{ box_gather(trak, edts);
if (p_stream->i_starttime > 0) {
bo_add_32be(elst, 2);
if (b_64_ext) {
bo_add_64be(elst, p_stream->i_starttime *
i_movie_timescale / CLOCK_FREQ);
bo_add_64be(elst, -1);
} else {
bo_add_32be(elst, p_stream->i_starttime *
i_movie_timescale / CLOCK_FREQ);
bo_add_32be(elst, -1);
}
bo_add_16be(elst, 1);
bo_add_16be(elst, 0);
} else {
bo_add_32be(elst, 1);
}
if (b_64_ext) {
bo_add_64be(elst, p_stream->i_read_duration *
i_movie_timescale / CLOCK_FREQ);
bo_add_64be(elst, 0);
} else {
bo_add_32be(elst, p_stream->i_read_duration *
i_movie_timescale / CLOCK_FREQ);
bo_add_32be(elst, 0);
}
bo_add_16be(elst, 1);
bo_add_16be(elst, 0);
bo_t *edts = box_new("edts");
if(edts)
{
box_gather(edts, elst);
box_gather(trak, edts);
}
else bo_free(elst);
}
} }
/* *** add /moov/trak/mdia *** */ /* *** add /moov/trak/mdia *** */
......
...@@ -34,6 +34,13 @@ typedef struct ...@@ -34,6 +34,13 @@ typedef struct
unsigned int i_flags; unsigned int i_flags;
} mp4mux_entry_t; } mp4mux_entry_t;
typedef struct
{
uint64_t i_duration;
mtime_t i_start_time;
mtime_t i_start_offset;
} mp4mux_edit_t;
typedef struct typedef struct
{ {
unsigned i_track_id; unsigned i_track_id;
...@@ -50,7 +57,7 @@ typedef struct ...@@ -50,7 +57,7 @@ typedef struct
/* stats */ /* stats */
int64_t i_read_duration; int64_t i_read_duration;
uint32_t i_timescale; uint32_t i_timescale;
mtime_t i_starttime; /* the really first packet */ mtime_t i_firstdts; /* the really first packet */
bool b_hasbframes; bool b_hasbframes;
/* temp stuff */ /* temp stuff */
...@@ -61,6 +68,10 @@ typedef struct ...@@ -61,6 +68,10 @@ typedef struct
uint32_t i_trex_default_length; uint32_t i_trex_default_length;
uint32_t i_trex_default_size; uint32_t i_trex_default_size;
/* edit list */
unsigned int i_edits_count;
mp4mux_edit_t *p_edits;
} mp4mux_trackinfo_t; } mp4mux_trackinfo_t;
bool mp4mux_trackinfo_Init( mp4mux_trackinfo_t * ); bool mp4mux_trackinfo_Init( mp4mux_trackinfo_t * );
......
...@@ -131,12 +131,10 @@ typedef struct ...@@ -131,12 +131,10 @@ typedef struct
/* index */ /* index */
int64_t i_length_neg; int64_t i_length_neg;
/* stats */ /* applies to current segment only */
int64_t i_dts_start; /* applies to current segment only */ int64_t i_first_dts;
int64_t i_last_dts;
/* for spu */ int64_t i_last_pts;
int64_t i_last_dts; /* applies to current segment only */
int64_t i_last_length;
/*** mp4frag ***/ /*** mp4frag ***/
bool b_hasiframes; bool b_hasiframes;
...@@ -162,6 +160,7 @@ struct sout_mux_sys_t ...@@ -162,6 +160,7 @@ struct sout_mux_sys_t
uint64_t i_mdat_pos; uint64_t i_mdat_pos;
uint64_t i_pos; uint64_t i_pos;
mtime_t i_read_duration; mtime_t i_read_duration;
mtime_t i_start_dts;
unsigned int i_nb_streams; unsigned int i_nb_streams;
mp4_stream_t **pp_streams; mp4_stream_t **pp_streams;
...@@ -178,6 +177,8 @@ static bo_t *BuildMoov(sout_mux_t *p_mux); ...@@ -178,6 +177,8 @@ static bo_t *BuildMoov(sout_mux_t *p_mux);
static block_t *ConvertSUBT(block_t *); static block_t *ConvertSUBT(block_t *);
static block_t *ConvertFromAnnexB(block_t *); static block_t *ConvertFromAnnexB(block_t *);
static bool CreateCurrentEdit(mp4_stream_t *, mtime_t);
static void DebugEdits(sout_mux_t *, const mp4_stream_t *);
static const char avc1_short_start_code[3] = { 0, 0, 1 }; static const char avc1_short_start_code[3] = { 0, 0, 1 };
static const char avc1_start_code[4] = { 0, 0, 0, 1 }; static const char avc1_start_code[4] = { 0, 0, 0, 1 };
...@@ -208,6 +209,7 @@ static int Open(vlc_object_t *p_this) ...@@ -208,6 +209,7 @@ static int Open(vlc_object_t *p_this)
p_sys->b_mov = p_mux->psz_mux && !strcmp(p_mux->psz_mux, "mov"); p_sys->b_mov = p_mux->psz_mux && !strcmp(p_mux->psz_mux, "mov");
p_sys->b_3gp = p_mux->psz_mux && !strcmp(p_mux->psz_mux, "3gp"); p_sys->b_3gp = p_mux->psz_mux && !strcmp(p_mux->psz_mux, "3gp");
p_sys->i_read_duration = 0; p_sys->i_read_duration = 0;
p_sys->i_start_dts = VLC_TS_INVALID;
p_sys->b_fragmented = false; p_sys->b_fragmented = false;
if (!p_sys->b_mov) { if (!p_sys->b_mov) {
...@@ -413,7 +415,7 @@ static int AddStream(sout_mux_t *p_mux, sout_input_t *p_input) ...@@ -413,7 +415,7 @@ static int AddStream(sout_mux_t *p_mux, sout_input_t *p_input)
es_format_Copy(&p_stream->mux.fmt, p_input->p_fmt); es_format_Copy(&p_stream->mux.fmt, p_input->p_fmt);
p_stream->mux.i_track_id = p_sys->i_nb_streams + 1; p_stream->mux.i_track_id = p_sys->i_nb_streams + 1;
p_stream->i_length_neg = 0; p_stream->i_length_neg = 0;
p_stream->i_dts_start = 0; p_stream->i_first_dts = VLC_TS_INVALID;
switch( p_stream->mux.fmt.i_cat ) switch( p_stream->mux.fmt.i_cat )
{ {
case AUDIO_ES: case AUDIO_ES:
...@@ -447,10 +449,11 @@ static int AddStream(sout_mux_t *p_mux, sout_input_t *p_input) ...@@ -447,10 +449,11 @@ static int AddStream(sout_mux_t *p_mux, sout_input_t *p_input)
break; break;
} }
p_stream->mux.i_starttime = p_sys->i_read_duration; p_stream->mux.p_edits = NULL;
p_stream->mux.i_edits_count = 0;
p_stream->i_last_dts = 0; p_stream->mux.i_firstdts = VLC_TS_INVALID;
p_stream->i_last_length = 0; p_stream->i_last_dts = VLC_TS_INVALID;
p_stream->i_last_pts = VLC_TS_INVALID;
p_stream->b_hasiframes = false; p_stream->b_hasiframes = false;
...@@ -479,13 +482,64 @@ static int AddStream(sout_mux_t *p_mux, sout_input_t *p_input) ...@@ -479,13 +482,64 @@ static int AddStream(sout_mux_t *p_mux, sout_input_t *p_input)
*****************************************************************************/ *****************************************************************************/
static void DelStream(sout_mux_t *p_mux, sout_input_t *p_input) static void DelStream(sout_mux_t *p_mux, sout_input_t *p_input)
{ {
VLC_UNUSED(p_input); sout_mux_sys_t *p_sys = p_mux->p_sys;
mp4_stream_t *p_stream = (mp4_stream_t*)p_input->p_sys;
if(CreateCurrentEdit(p_stream, p_sys->i_start_dts))
DebugEdits(p_mux, p_stream);
msg_Dbg(p_mux, "removing input"); msg_Dbg(p_mux, "removing input");
} }
/***************************************************************************** /*****************************************************************************
* Mux: * Mux:
*****************************************************************************/ *****************************************************************************/
static void DebugEdits(sout_mux_t *p_mux, const mp4_stream_t *p_stream)
{
for( unsigned i=0; i<p_stream->mux.i_edits_count; i++ )
{
msg_Dbg(p_mux, "tk %d elst media time %" PRId64 " duration %" PRIu64 " offset %" PRId64 ,
p_stream->mux.i_track_id,
p_stream->mux.p_edits[i].i_start_time,
p_stream->mux.p_edits[i].i_duration,
p_stream->mux.p_edits[i].i_start_offset);
}
}
static bool CreateCurrentEdit(mp4_stream_t *p_stream, mtime_t i_mux_start_dts)
{
if(p_stream->mux.i_entry_count == 0)
return true;
mp4mux_edit_t *p_realloc = realloc( p_stream->mux.p_edits, sizeof(mp4mux_edit_t) *
(p_stream->mux.i_edits_count + 1) );
if(unlikely(!p_realloc))
return false;
mp4mux_edit_t *p_newedit = &p_realloc[p_stream->mux.i_edits_count];
if(p_stream->mux.i_edits_count == 0)
{
p_newedit->i_start_time = 0;
p_newedit->i_start_offset = p_stream->i_first_dts - i_mux_start_dts;
}
else
{
const mp4mux_edit_t *p_lastedit = &p_realloc[p_stream->mux.i_edits_count - 1];
p_newedit->i_start_time = p_lastedit->i_start_time + p_lastedit->i_duration;
p_newedit->i_start_offset = 0;
}
if(p_stream->i_last_pts > VLC_TS_INVALID)
p_newedit->i_duration = p_stream->i_last_pts - p_stream->i_first_dts;
else
p_newedit->i_duration = p_stream->i_last_dts - p_stream->i_first_dts;
p_newedit->i_duration += p_stream->mux.entry[p_stream->mux.i_entry_count - 1].i_length;
p_stream->mux.p_edits = p_realloc;
p_stream->mux.i_edits_count++;
return true;
}
static int Mux(sout_mux_t *p_mux) static int Mux(sout_mux_t *p_mux)
{ {
sout_mux_sys_t *p_sys = p_mux->p_sys; sout_mux_sys_t *p_sys = p_mux->p_sys;
...@@ -514,11 +568,33 @@ static int Mux(sout_mux_t *p_mux) ...@@ -514,11 +568,33 @@ static int Mux(sout_mux_t *p_mux)
} while (!p_data); } while (!p_data);
/* Reset reference dts in case of discontinuity (ex: gather sout) */ /* Reset reference dts in case of discontinuity (ex: gather sout) */
if ( p_stream->mux.i_entry_count == 0 || p_data->i_flags & BLOCK_FLAG_DISCONTINUITY ) if (p_data->i_flags & BLOCK_FLAG_DISCONTINUITY && p_stream->mux.i_entry_count)
{ {
p_stream->i_dts_start = p_data->i_dts; if(!CreateCurrentEdit(p_stream, p_sys->i_start_dts))
p_stream->i_last_dts = p_data->i_dts; {
block_Release( p_data );
return VLC_ENOMEM;
}
p_stream->i_length_neg = 0; p_stream->i_length_neg = 0;
p_stream->i_first_dts = VLC_TS_INVALID;
p_stream->i_last_dts = VLC_TS_INVALID;
p_stream->i_last_pts = VLC_TS_INVALID;
}
/* XXX: -1 to always have 2 entry for easy adding of empty SPU */
if (p_stream->mux.i_entry_count >= p_stream->mux.i_entry_max - 2) {
p_stream->mux.i_entry_max += 1000;
p_stream->mux.entry = xrealloc(p_stream->mux.entry,
p_stream->mux.i_entry_max * sizeof(mp4mux_entry_t));
}
/* Set current segment ranges */
if( p_stream->i_first_dts == VLC_TS_INVALID )
{
p_stream->i_first_dts = p_data->i_dts;
if( p_sys->i_start_dts == VLC_TS_INVALID )
p_sys->i_start_dts = p_data->i_dts;
} }
if (p_stream->mux.fmt.i_cat != SPU_ES) { if (p_stream->mux.fmt.i_cat != SPU_ES) {
...@@ -570,19 +646,26 @@ static int Mux(sout_mux_t *p_mux) ...@@ -570,19 +646,26 @@ static int Mux(sout_mux_t *p_mux)
} }
} }
if (p_stream->mux.fmt.i_cat == SPU_ES && p_stream->mux.i_entry_count > 0) { if (p_stream->mux.fmt.i_cat == SPU_ES && p_stream->mux.i_entry_count > 0)
{
/* length of previous spu, stored in spu clearer */
int64_t i_length = p_data->i_dts - p_stream->i_last_dts; int64_t i_length = p_data->i_dts - p_stream->i_last_dts;
if(i_length < 0)
if (i_length <= 0) /* FIXME handle this broken case */ i_length = 0;
i_length = 1; assert( p_stream->mux.entry[p_stream->mux.i_entry_count-1].i_length == 0 );
assert( p_stream->mux.entry[p_stream->mux.i_entry_count-1].i_size == 3 );
/* Fix last entry */ /* Fix entry */
if (p_stream->mux.entry[p_stream->mux.i_entry_count-1].i_length <= 0) p_stream->mux.entry[p_stream->mux.i_entry_count-1].i_length = i_length;
p_stream->mux.entry[p_stream->mux.i_entry_count-1].i_length = i_length; p_stream->mux.i_read_duration += i_length;
} }
/* Update (Not earlier for SPU!) */
p_stream->i_last_dts = p_data->i_dts;
if( p_data->i_pts > p_stream->i_last_pts )
p_stream->i_last_pts = p_data->i_pts;
/* add index entry */ /* add index entry */
mp4mux_entry_t *e = &p_stream->mux.entry[p_stream->mux.i_entry_count]; mp4mux_entry_t *e = &p_stream->mux.entry[p_stream->mux.i_entry_count++];
e->i_pos = p_sys->i_pos; e->i_pos = p_sys->i_pos;
e->i_size = p_data->i_buffer; e->i_size = p_data->i_buffer;
...@@ -597,70 +680,45 @@ static int Mux(sout_mux_t *p_mux) ...@@ -597,70 +680,45 @@ static int Mux(sout_mux_t *p_mux)
e->i_length = p_data->i_length; e->i_length = p_data->i_length;
e->i_flags = p_data->i_flags; e->i_flags = p_data->i_flags;
p_stream->mux.i_entry_count++;
/* XXX: -1 to always have 2 entry for easy adding of empty SPU */
if (p_stream->mux.i_entry_count >= p_stream->mux.i_entry_max - 1) {
p_stream->mux.i_entry_max += 1000;
p_stream->mux.entry = xrealloc(p_stream->mux.entry,
p_stream->mux.i_entry_max * sizeof(mp4mux_entry_t));
}
/* update */ /* update */
p_stream->mux.i_read_duration += __MAX( 0, p_data->i_length ); p_stream->mux.i_read_duration += __MAX( 0, p_data->i_length );
p_stream->i_last_length = p_data->i_length;
p_sys->i_pos += p_data->i_buffer;
/* Save the DTS for SPU */
p_stream->i_last_dts = p_data->i_dts; p_stream->i_last_dts = p_data->i_dts;
/* write data */ /* write data */
p_sys->i_pos += p_data->i_buffer;
sout_AccessOutWrite(p_mux->p_access, p_data); sout_AccessOutWrite(p_mux->p_access, p_data);
/* close subtitle with empty frame */ /* Add SPU clearing tag (duration tb fixed on next SPU or stream end )*/
if (p_stream->mux.fmt.i_cat == SPU_ES) { if (p_stream->mux.fmt.i_cat == SPU_ES)
int64_t i_length = p_stream->mux.entry[p_stream->mux.i_entry_count-1].i_length; {
block_t *p_empty = block_Alloc(3);
if (p_empty)
{
/* point to start of our empty */
p_stream->i_last_dts += e->i_length;
if ( i_length != 0 && (p_data = block_Alloc(3)) ) { /* Write a " " */
/* TODO */ p_empty->p_buffer[0] = 0;
msg_Dbg(p_mux, "writing an empty sub") ; p_empty->p_buffer[1] = 1;
p_empty->p_buffer[2] = ' ';
/* Append a idx entry */ /* Append a idx entry */
mp4mux_entry_t *e = &p_stream->mux.entry[p_stream->mux.i_entry_count];
e->i_pos = p_sys->i_pos;
e->i_size = 3;
e->i_pts_dts= 0;
e->i_length = 0;
e->i_flags = 0;
/* XXX: No need to grow the entry here */ /* XXX: No need to grow the entry here */
p_stream->mux.i_entry_count++; mp4mux_entry_t *e_empty = &p_stream->mux.entry[p_stream->mux.i_entry_count++];
e_empty->i_pos = p_sys->i_pos;
/* Fix last dts */ e_empty->i_size = 3;
p_stream->i_last_dts += i_length; e_empty->i_pts_dts= 0;
e_empty->i_length = 0; /* will add dts diff later*/
/* Write a " " */ e_empty->i_flags = 0;
p_data->i_dts = p_stream->i_last_dts;
p_data->i_dts = p_data->i_pts; p_sys->i_pos += p_empty->i_buffer;
p_data->p_buffer[0] = 0; sout_AccessOutWrite(p_mux->p_access, p_empty);
p_data->p_buffer[1] = 1;
p_data->p_buffer[2] = ' ';
p_sys->i_pos += p_data->i_buffer;
sout_AccessOutWrite(p_mux->p_access, p_data);
} }
/* Fix duration = current segment starttime + duration within */
p_stream->mux.i_read_duration =
p_stream->mux.i_starttime + ( p_stream->i_last_dts - p_stream->i_dts_start );
} }
}
/* Update the global segment/media duration */ /* Update the global segment/media duration */
for ( unsigned int i=0; i<p_sys->i_nb_streams; i++ ) if( p_stream->mux.i_read_duration > p_sys->i_read_duration )
{ p_sys->i_read_duration = p_stream->mux.i_read_duration;
if ( p_sys->pp_streams[i]->mux.i_read_duration > p_sys->i_read_duration )
p_sys->i_read_duration = p_sys->pp_streams[i]->mux.i_read_duration;
} }
return(VLC_SUCCESS); return(VLC_SUCCESS);
......
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