Commit 86e8c9d5 authored by Frédéric Yhuel's avatar Frédéric Yhuel Committed by Jean-Baptiste Kempf

demux/mp4: Add fragmented MP4 support

Signed-off-by: default avatarJean-Baptiste Kempf <jb@videolan.org>
parent 2f1fe72f
......@@ -159,15 +159,20 @@ static int MP4_NextBox( stream_t *p_stream, MP4_Box_t *p_box )
if( p_box->p_father )
{
const off_t i_box_end = p_box->i_size + p_box->i_pos;
const off_t i_father_end = p_box->p_father->i_size + p_box->p_father->i_pos;
/* check if it's within p-father */
if( i_box_end >= i_father_end )
/* if father's size == 0, it means unknown or infinite size,
* and we skip the followong check */
if( p_box->p_father->i_size > 0 )
{
if( i_box_end > i_father_end )
msg_Dbg( p_stream, "out of bound child" );
return 0; /* out of bound */
const off_t i_box_end = p_box->i_size + p_box->i_pos;
const off_t i_father_end = p_box->p_father->i_size + p_box->p_father->i_pos;
/* check if it's within p-father */
if( i_box_end >= i_father_end )
{
if( i_box_end > i_father_end )
msg_Dbg( p_stream, "out of bound child" );
return 0; /* out of bound */
}
}
}
if( stream_Seek( p_stream, p_box->i_size + p_box->i_pos ) )
......@@ -184,12 +189,17 @@ static int MP4_NextBox( stream_t *p_stream, MP4_Box_t *p_box )
* after called one of theses functions, file position is unknown
* you need to call MP4_GotoBox to go where you want
*****************************************************************************/
static int MP4_ReadBoxContainerRaw( stream_t *p_stream, MP4_Box_t *p_container )
static int MP4_ReadBoxContainerChildren( stream_t *p_stream,
MP4_Box_t *p_container, uint32_t i_last_child )
{
MP4_Box_t *p_box;
if( stream_Tell( p_stream ) + 8 >
/* Size of root container is set to 0 when unknown, for exemple
* with a DASH stream. In that case, we skip the following check */
if( p_container->i_size
&& ( stream_Tell( p_stream ) + 8 >
(off_t)(p_container->i_pos + p_container->i_size) )
)
{
/* there is no box to load */
return 0;
......@@ -204,14 +214,23 @@ static int MP4_ReadBoxContainerRaw( stream_t *p_stream, MP4_Box_t *p_container )
else p_container->p_last->p_next = p_box;
p_container->p_last = p_box;
if( p_box->i_type == i_last_child )
break;
} while( MP4_NextBox( p_stream, p_box ) == 1 );
return 1;
}
static int MP4_ReadBoxContainerRaw( stream_t *p_stream, MP4_Box_t *p_container )
{
return MP4_ReadBoxContainerChildren( p_stream, p_container, 0 );
}
static int MP4_ReadBoxContainer( stream_t *p_stream, MP4_Box_t *p_container )
{
if( p_container->i_size <= (size_t)mp4_box_headersize(p_container ) + 8 )
if( p_container->i_size &&
( p_container->i_size <= (size_t)mp4_box_headersize(p_container ) + 8 ) )
{
/* container is empty, 8 stand for the first header in this box */
return 1;
......@@ -497,22 +516,23 @@ static int MP4_ReadBox_stra( stream_t *p_stream, MP4_Box_t *p_box )
MP4_GET4BYTES( p_stra->BitsPerSample );
MP4_GET4BYTES( p_stra->PacketSize );
MP4_GET4BYTES( p_stra->AudioTag );
MP4_GET4BYTES( p_stra->AvgBytesPerSec );
MP4_GET2BYTES( p_stra->nBlockAlign );
MP4_GET1BYTE( i_reserved );
MP4_GET1BYTE( i_reserved );
MP4_GET1BYTE( i_reserved );
uint8_t codec_data_length;
MP4_GET1BYTE( codec_data_length );
p_stra->CodecPrivateData = malloc( codec_data_length + 1);
MP4_GET1BYTE( p_stra->cpd_len );
if( p_stra->cpd_len > i_read )
goto error;
p_stra->CodecPrivateData = malloc( p_stra->cpd_len );
if( unlikely( p_stra->CodecPrivateData == NULL ) )
goto error;
MP4_GETSTRINGZ( p_stra->CodecPrivateData );
memcpy( p_stra->CodecPrivateData, p_peek, p_stra->cpd_len );
#ifdef MP4_VERBOSE
msg_Dbg( p_stream, "es_cat is %"PRIu8", birate is %"PRIu32", "\
"CodecPrivateData is %s", p_stra->i_es_cat,
p_stra->Bitrate, p_stra->CodecPrivateData );
msg_Dbg( p_stream, "es_cat is %"PRIu8", birate is %"PRIu32,
p_stra->i_es_cat, p_stra->Bitrate );
#endif
MP4_READBOX_EXIT( 1 );
......@@ -3474,57 +3494,7 @@ error:
return NULL;
}
MP4_Box_t *MP4_BoxGetInitFrag( stream_t *s )
{
/* p_chunk is a virtual root container for the ftyp and moov boxes */
MP4_Box_t *p_chunk;
MP4_Box_t *p_ftyp;
MP4_Box_t *p_moov;
p_chunk = calloc( 1, sizeof( MP4_Box_t ) );
if( unlikely( p_chunk == NULL ) )
return NULL;
p_chunk->i_type = ATOM_root;
p_chunk->i_shortsize = 1;
p_ftyp = MP4_ReadBox( s, p_chunk );
if( !p_ftyp )
{
msg_Warn( s, "no ftyp box found!");
goto error;
}
/* there may be some boxes between ftyp and moov,
* we skip them, but put a reasonable limit */
#define MAX_SKIP 8
for( int i = 0 ; i < MAX_SKIP; i++ )
{
p_moov = MP4_ReadBox( s, p_chunk );
if( !p_moov )
goto error;
if( p_moov->i_type != ATOM_moov )
{
if( i == MAX_SKIP - 1 )
return NULL;
stream_Read( s, NULL, p_moov->i_size );
MP4_BoxFree( s, p_moov );
}
else
break;
}
p_chunk->p_first = p_ftyp;
p_ftyp->p_next = p_moov;
p_chunk->p_last = p_moov;
return p_chunk;
error:
free( p_chunk );
return NULL;
}
MP4_Box_t *MP4_BoxGetNextChunk( stream_t *s )
{
/* p_chunk is a virtual root container for the moof and mdat boxes */
......@@ -3546,7 +3516,7 @@ MP4_Box_t *MP4_BoxGetNextChunk( stream_t *s )
}
else if( p_tmp_box->i_type == ATOM_ftyp )
{
return MP4_BoxGetInitFrag( s );
return MP4_BoxGetRoot( s );
}
free( p_tmp_box );
......@@ -3620,7 +3590,8 @@ MP4_Box_t *MP4_BoxGetRoot( stream_t *s )
p_root->i_pos = 0;
p_root->i_type = ATOM_root;
p_root->i_shortsize = 1;
p_root->i_size = stream_Size( s );
/* could be a DASH stream for exemple, 0 means unknown or infinite size */
p_root->i_size = 0;
CreateUUID( &p_root->i_uuid, p_root->i_type );
p_root->data.p_data = NULL;
......@@ -3631,37 +3602,52 @@ MP4_Box_t *MP4_BoxGetRoot( stream_t *s )
p_stream = s;
/* First get the moov */
i_result = MP4_ReadBoxContainerChildren( p_stream, p_root, ATOM_moov );
if( !i_result )
goto error;
/* If there is a mvex box, it means fragmented MP4, and we're done */
else if( MP4_BoxCount( p_root, "moov/mvex" ) > 0 )
return p_root;
p_root->i_size = stream_Size( s );
/* Get the rest of the file */
i_result = MP4_ReadBoxContainerRaw( p_stream, p_root );
if( i_result )
{
MP4_Box_t *p_moov;
MP4_Box_t *p_cmov;
if( !i_result )
goto error;
/* check if there is a cmov, if so replace
compressed moov by uncompressed one */
if( ( ( p_moov = MP4_BoxGet( p_root, "moov" ) ) &&
( p_cmov = MP4_BoxGet( p_root, "moov/cmov" ) ) ) ||
( ( p_moov = MP4_BoxGet( p_root, "foov" ) ) &&
( p_cmov = MP4_BoxGet( p_root, "foov/cmov" ) ) ) )
{
/* rename the compressed moov as a box to skip */
p_moov->i_type = ATOM_skip;
MP4_Box_t *p_moov;
MP4_Box_t *p_cmov;
/* check if there is a cmov, if so replace
compressed moov by uncompressed one */
if( ( ( p_moov = MP4_BoxGet( p_root, "moov" ) ) &&
( p_cmov = MP4_BoxGet( p_root, "moov/cmov" ) ) ) ||
( ( p_moov = MP4_BoxGet( p_root, "foov" ) ) &&
( p_cmov = MP4_BoxGet( p_root, "foov/cmov" ) ) ) )
{
/* rename the compressed moov as a box to skip */
p_moov->i_type = ATOM_skip;
/* get uncompressed p_moov */
p_moov = p_cmov->data.p_cmov->p_moov;
p_cmov->data.p_cmov->p_moov = NULL;
/* get uncompressed p_moov */
p_moov = p_cmov->data.p_cmov->p_moov;
p_cmov->data.p_cmov->p_moov = NULL;
/* make p_root father of this new moov */
p_moov->p_father = p_root;
/* make p_root father of this new moov */
p_moov->p_father = p_root;
/* insert this new moov box as first child of p_root */
p_moov->p_next = p_root->p_first;
p_root->p_first = p_moov;
}
/* insert this new moov box as first child of p_root */
p_moov->p_next = p_root->p_first;
p_root->p_first = p_moov;
}
return p_root;
error:
free( p_root );
return NULL;
}
......
......@@ -1140,12 +1140,14 @@ typedef struct
uint32_t MaxWidth;
uint32_t MaxHeight;
uint32_t SamplingRate;
uint32_t AvgBytesPerSec;
uint32_t Channels;
uint32_t BitsPerSample;
uint32_t PacketSize;
uint32_t AudioTag;
uint16_t nBlockAlign;
char *CodecPrivateData;
uint8_t cpd_len;
uint8_t *CodecPrivateData;
} MP4_Box_data_stra_t;
/*
......@@ -1346,7 +1348,8 @@ typedef struct
MP4_Box_t *p_sample;/* point on actual sdsd */
bool b_drms;
bool b_end_of_chunk;
bool b_has_non_empty_cchunk;
bool b_codec_need_restart;
void *p_drms;
MP4_Box_t *p_skcr;
......@@ -1477,13 +1480,6 @@ static const UUID_t StraBoxUUID = {
0x96, 0xc7, 0xbf, 0x25, 0xf9, 0x7e, 0x24, 0x47 } };
MP4_Box_t *MP4_BoxGetSmooBox( stream_t * );
/*****************************************************************************
* MP4_BoxGetInitFrag : Parse the initialization segment.
*****************************************************************************
* The first box is a virtual box "root", and is the father of the boxes
* 'ftyp' and 'moov'.
*****************************************************************************/
MP4_Box_t *MP4_BoxGetInitFrag( stream_t * );
/*****************************************************************************
* MP4_BoxGetNextChunk : Parse the entire moof box.
......
......@@ -35,6 +35,7 @@
#include <vlc_charset.h> /* EnsureUTF8 */
#include <vlc_meta.h> /* vlc_meta_t, vlc_meta_ */
#include <vlc_input.h>
#include <assert.h>
#include "libmp4.h"
#include "id3genres.h" /* for ATOM_gnre */
......@@ -59,6 +60,7 @@ vlc_module_end ()
*****************************************************************************/
static int Demux ( demux_t * );
static int DemuxRef( demux_t *p_demux ){ (void)p_demux; return 0;}
static int DemuxFrg( demux_t * );
static int Seek ( demux_t *, mtime_t );
static int Control ( demux_t *, int, va_list );
......@@ -76,6 +78,8 @@ struct demux_sys_t
mp4_track_t *track; /* array of track */
float f_fps; /* number of frame per seconds */
bool b_fragmented; /* fMP4 */
/* */
MP4_Box_t *p_tref_chap;
......@@ -87,6 +91,7 @@ struct demux_sys_t
* Declaration of local function
*****************************************************************************/
static void MP4_TrackCreate ( demux_t *, mp4_track_t *, MP4_Box_t *, bool b_force_enable );
static int MP4_frg_TrackCreate( demux_t *, mp4_track_t *, MP4_Box_t *);
static void MP4_TrackDestroy( mp4_track_t * );
static int MP4_TrackSelect ( demux_t *, mp4_track_t *, mtime_t );
......@@ -102,10 +107,15 @@ static void MP4_TrackSetELST( demux_t *, mp4_track_t *, int64_t );
static void MP4_UpdateSeekpoint( demux_t * );
static const char *MP4_ConvertMacCode( uint16_t );
/* Return time in s of a track */
/* Return time in microsecond of a track */
static inline int64_t MP4_TrackGetDTS( demux_t *p_demux, mp4_track_t *p_track )
{
#define chunk p_track->chunk[p_track->i_chunk]
demux_sys_t *p_sys = p_demux->p_sys;
mp4_chunk_t chunk;
if( p_sys->b_fragmented )
chunk = *p_track->cchunk;
else
chunk = p_track->chunk[p_track->i_chunk];
unsigned int i_index = 0;
unsigned int i_sample = p_track->i_sample - chunk.i_sample_first;
......@@ -127,8 +137,6 @@ static inline int64_t MP4_TrackGetDTS( demux_t *p_demux, mp4_track_t *p_track )
}
}
#undef chunk
/* now handle elst */
if( p_track->p_elst )
{
......@@ -153,9 +161,15 @@ static inline int64_t MP4_TrackGetDTS( demux_t *p_demux, mp4_track_t *p_track )
return INT64_C(1000000) * i_dts / p_track->i_timescale;
}
static inline int64_t MP4_TrackGetPTSDelta( mp4_track_t *p_track )
static inline int64_t MP4_TrackGetPTSDelta( demux_t *p_demux, mp4_track_t *p_track )
{
mp4_chunk_t *ck = &p_track->chunk[p_track->i_chunk];
demux_sys_t *p_sys = p_demux->p_sys;
mp4_chunk_t *ck;
if( p_sys->b_fragmented )
ck = p_track->cchunk;
else
ck = &p_track->chunk[p_track->i_chunk];
unsigned int i_index = 0;
unsigned int i_sample = p_track->i_sample - ck->i_sample_first;
......@@ -179,6 +193,98 @@ static inline int64_t MP4_GetMoviePTS(demux_sys_t *p_sys )
static void LoadChapter( demux_t *p_demux );
static int LoadInitFrag( demux_t *p_demux, const bool b_smooth )
{
demux_sys_t *p_sys = p_demux->p_sys;
if( b_smooth ) /* Smooth Streaming */
{
if( ( p_sys->p_root = MP4_BoxGetSmooBox( p_demux->s ) ) == NULL )
{
goto LoadInitFragError;
}
else
{
MP4_Box_t *p_smoo = MP4_BoxGet( p_sys->p_root, "uuid" );
if( !p_smoo || CmpUUID( &p_smoo->i_uuid, &SmooBoxUUID ) )
goto LoadInitFragError;
/* Get number of tracks */
p_sys->i_tracks = 0;
for( int i = 0; i < 3; i++ )
{
MP4_Box_t *p_stra = MP4_BoxGet( p_smoo, "uuid[%d]", i );
if( p_stra && p_stra->data.p_stra->i_track_ID )
p_sys->i_tracks++;
/* Get timescale and duration of the video track; */
if( i == 0 )
{
p_sys->i_timescale = p_stra->data.p_stra->i_timescale;
p_sys->i_duration = p_stra->data.p_stra->i_duration;
}
}
}
}
else
{
/* Load all boxes ( except raw data ) */
if( ( p_sys->p_root = MP4_BoxGetRoot( p_demux->s ) ) == NULL )
{
goto LoadInitFragError;
}
}
return VLC_SUCCESS;
LoadInitFragError:
msg_Warn( p_demux, "MP4 plugin discarded (not a valid initialization chunk)" );
return VLC_EGENERIC;
}
static int InitTracks( demux_t *p_demux )
{
demux_sys_t *p_sys = p_demux->p_sys;
p_sys->track = calloc( p_sys->i_tracks, sizeof( mp4_track_t ) );
if( p_sys->track == NULL )
return VLC_EGENERIC;
if( p_sys->b_fragmented )
{
mp4_track_t *p_track;
for( uint16_t i = 0; i < p_sys->i_tracks; i++ )
{
p_track = &p_sys->track[i];
p_track->cchunk = calloc( 1, sizeof( mp4_chunk_t ) );
if( unlikely( !p_track->cchunk ) )
{
free( p_sys->track );
return VLC_EGENERIC;
}
}
}
return VLC_SUCCESS;
}
static void CreateTracksFromSmooBox( demux_t *p_demux )
{
demux_sys_t *p_sys = p_demux->p_sys;
MP4_Box_t *p_smoo = MP4_BoxGet( p_sys->p_root, "uuid" );
mp4_track_t *p_track;
int j = 0;
for( int i = 0; i < 3; i++ )
{
MP4_Box_t *p_stra = MP4_BoxGet( p_smoo, "uuid[%d]", i );
if( !p_stra || p_stra->data.p_stra->i_track_ID == 0 )
continue;
else
{
p_track = &p_sys->track[j]; j++;
MP4_frg_TrackCreate( p_demux, p_track, p_stra );
p_track->p_es = es_out_Add( p_demux->out, &p_track->fmt );
}
}
}
/*****************************************************************************
* Open: check file and initializes MP4 structures
*****************************************************************************/
......@@ -211,6 +317,7 @@ static int Open( vlc_object_t * p_this )
case ATOM_free:
case ATOM_skip:
case ATOM_wide:
case ATOM_uuid:
case VLC_FOURCC( 'p', 'n', 'o', 't' ):
break;
case ATOM_ftyp:
......@@ -223,10 +330,10 @@ static int Open( vlc_object_t * p_this )
}
/* I need to seek */
stream_Control( p_demux->s, STREAM_CAN_FASTSEEK, &b_seekable );
stream_Control( p_demux->s, STREAM_CAN_SEEK, &b_seekable );
if( !b_seekable )
{
msg_Warn( p_demux, "MP4 plugin discarded (not fastseekable)" );
msg_Warn( p_demux, "MP4 plugin discarded (not seekable)" );
return VLC_EGENERIC;
}
......@@ -237,10 +344,49 @@ static int Open( vlc_object_t * p_this )
/* create our structure that will contains all data */
p_demux->p_sys = p_sys = calloc( 1, sizeof( demux_sys_t ) );
/* Now load all boxes ( except raw data ) */
if( ( p_sys->p_root = MP4_BoxGetRoot( p_demux->s ) ) == NULL )
/* Is it Smooth Streaming? */
bool b_smooth = false;
if( stream_Peek( p_demux->s, &p_peek, 24 ) < 24 ) return VLC_EGENERIC;
if( !CmpUUID( (UUID_t *)(p_peek + 8), &SmooBoxUUID ) )
{
b_smooth = true;
p_sys->b_fragmented = true;
}
if( LoadInitFrag( p_demux, b_smooth ) != VLC_SUCCESS )
goto error;
if( MP4_BoxCount( p_sys->p_root, "/moov/mvex" ) > 0 )
{
p_sys->b_fragmented = true;
}
if( p_sys->b_fragmented )
{
p_demux->pf_demux = DemuxFrg;
}
stream_Control( p_demux->s, STREAM_CAN_FASTSEEK, &b_seekable );
if( b_smooth )
{
if( InitTracks( p_demux ) != VLC_SUCCESS )
goto error;
CreateTracksFromSmooBox( p_demux );
return VLC_SUCCESS;
}
else if( p_sys->b_fragmented && b_seekable )
{
/* We are not yet able to demux a fragmented MP4 file, using the 'mfra'
* box. So if STREAM_CAN_FASTSEEK is true, we're assuming we've got such
* a file, and we let avformat do the job. */
msg_Warn( p_demux, "MP4 plugin discarded "\
"(fast-seekable and fragmented, let avformat demux it)" );
stream_Seek( p_demux->s, 0 ); /* rewind, for other demux */
goto error;
}
else if( !p_sys->b_fragmented && !b_seekable )
{
msg_Warn( p_demux, "MP4 plugin discarded (not a valid file)" );
msg_Warn( p_demux, "MP4 plugin discarded (not fast-seekable)" );
stream_Seek( p_demux->s, 0 ); /* rewind, for other demux */
goto error;
}
......@@ -292,13 +438,8 @@ static int Open( vlc_object_t * p_this )
if( !p_foov )
{
/* search also for moof box used by smoothstreaming */
p_foov = MP4_BoxGet( p_sys->p_root, "/moof" );
if( !p_foov )
{
msg_Err( p_demux, "MP4 plugin discarded (no moov,foov,moof box)" );
goto error;
}
msg_Err( p_demux, "MP4 plugin discarded (no moov,foov,moof box)" );
goto error;
}
/* we have a free box as a moov, rename it */
p_foov->i_type = ATOM_moov;
......@@ -415,9 +556,7 @@ static int Open( vlc_object_t * p_this )
p_sys->i_tracks,
p_sys->i_tracks ? 's':' ' );
/* allocate memory */
p_sys->track = calloc( p_sys->i_tracks, sizeof( mp4_track_t ) );
if( p_sys->track == NULL )
if( InitTracks( p_demux ) != VLC_SUCCESS )
goto error;
/* Search the first chap reference (like quicktime) and
......@@ -640,7 +779,7 @@ static int Demux( demux_t *p_demux )
/* dts */
p_block->i_dts = VLC_TS_0 + MP4_TrackGetDTS( p_demux, tk );
/* pts */
i_delta = MP4_TrackGetPTSDelta( tk );
i_delta = MP4_TrackGetPTSDelta( p_demux, tk );
if( i_delta != -1 )
p_block->i_pts = p_block->i_dts + i_delta;
else if( tk->fmt.i_cat != VIDEO_ES )
......@@ -706,6 +845,40 @@ static int Seek( demux_t *p_demux, mtime_t i_date )
return VLC_SUCCESS;
}
static int MP4_frg_Seek( demux_t *p_demux, double f )
{
demux_sys_t *p_sys = p_demux->p_sys;
int64_t i64 = stream_Size( p_demux->s );
if( stream_Seek( p_demux->s, (int64_t)(i64 * f) ) )
{
return VLC_EGENERIC;
}
else
{
/* update global time */
p_sys->i_time = (uint64_t)(f * (double)p_sys->i_duration);
p_sys->i_pcr = MP4_GetMoviePTS( p_sys );
for( unsigned i_track = 0; i_track < p_sys->i_tracks; i_track++ )
{
mp4_track_t *tk = &p_sys->track[i_track];
/* We don't want the current chunk to be flushed */
tk->cchunk->i_sample = tk->cchunk->i_sample_count;
/* reset/update some values */
tk->i_sample = tk->i_sample_first = 0;
tk->i_first_dts = p_sys->i_time;
/* We want to discard the current chunk and get the next one at once */
tk->b_has_non_empty_cchunk = false;
}
es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, p_sys->i_pcr );
return VLC_SUCCESS;
}
}
/*****************************************************************************
* Control:
*****************************************************************************/
......@@ -732,7 +905,11 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
case DEMUX_SET_POSITION:
f = (double)va_arg( args, double );
if( p_sys->i_timescale > 0 )
if( p_sys->b_fragmented )
{
return MP4_frg_Seek( p_demux, f );
}
else if( p_sys->i_timescale > 0 )
{
i64 = (int64_t)( f * (double)1000000 *
(double)p_sys->i_duration /
......@@ -1024,7 +1201,7 @@ static void LoadChapterApple( demux_t *p_demux, mp4_track_t *tk )
for( tk->i_sample = 0; tk->i_sample < tk->i_sample_count; tk->i_sample++ )
{
const int64_t i_dts = MP4_TrackGetDTS( p_demux, tk );
const int64_t i_pts_delta = MP4_TrackGetPTSDelta( tk );
const int64_t i_pts_delta = MP4_TrackGetPTSDelta( p_demux, tk );
const unsigned int i_size = MP4_TrackSampleSize( tk );
if( i_size > 0 && !stream_Seek( p_demux->s, MP4_TrackGetPos( tk ) ) )
......@@ -1096,6 +1273,10 @@ static void LoadChapter( demux_t *p_demux )
static int TrackCreateChunksIndex( demux_t *p_demux,
mp4_track_t *p_demux_track )
{
demux_sys_t *p_sys = p_demux->p_sys;
if( p_sys->b_fragmented )
return VLC_SUCCESS;
MP4_Box_t *p_co64; /* give offset for each chunk, same for stco and co64 */
MP4_Box_t *p_stsc;
......@@ -1183,6 +1364,10 @@ static int TrackCreateChunksIndex( demux_t *p_demux,
static int TrackCreateSamplesIndex( demux_t *p_demux,
mp4_track_t *p_demux_track )
{
demux_sys_t *p_sys = p_demux->p_sys;
if( p_sys->b_fragmented )
return VLC_SUCCESS;
MP4_Box_t *p_box;
MP4_Box_data_stsz_t *stsz;
MP4_Box_data_stts_t *stts;
......@@ -1432,8 +1617,15 @@ static void TrackGetESSampleRate( unsigned *pi_num, unsigned *pi_den,
static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
unsigned int i_chunk, es_out_id_t **pp_es )
{
const unsigned i_sample_description_index =
p_track->chunk[i_chunk].i_sample_description_index;
demux_sys_t *p_sys = p_demux->p_sys;
unsigned int i_sample_description_index;
if( p_sys->b_fragmented )
i_sample_description_index = 1; /* XXX */
else
i_sample_description_index =
p_track->chunk[i_chunk].i_sample_description_index;
MP4_Box_t *p_sample;
MP4_Box_t *p_esds;
MP4_Box_t *p_frma;
......@@ -2825,3 +3017,716 @@ static const char *MP4_ConvertMacCode( uint16_t i_code )
}
return "";
}
/******************************************************************************
* Here are the functions used for fragmented MP4
*****************************************************************************/
/**
* It computes the sample rate for a video track using current video chunk
*/
static void ChunkGetESSampleRate( unsigned *pi_num, unsigned *pi_den,
const mp4_track_t *p_track )
{
if( p_track->cchunk->i_last_dts == 0 )
return;
*pi_num = 0;
*pi_den = 0;
/* */
const mp4_chunk_t *p_chunk = p_track->cchunk;
uint32_t i_sample = p_chunk->i_sample_count;
uint64_t i_first_dts = p_chunk->i_first_dts;
uint64_t i_last_dts = p_chunk->i_last_dts;
if( i_sample > 1 && i_first_dts < i_last_dts )
vlc_ureduce( pi_num, pi_den,
( i_sample - 1) * p_track->i_timescale,
i_last_dts - i_first_dts,
UINT16_MAX);
}
/**
* Build raw avcC box (without the 8 bytes header)
* \return The size of the box.
*/
static int build_raw_avcC( uint8_t **p_extra, const uint8_t *CodecPrivateData,
const unsigned cpd_len )
{
uint8_t *avcC;
unsigned sps_len = 0, pps_len = 0;
const uint32_t mark = 0x00000001;
assert( CodecPrivateData[0] == 0 );
assert( CodecPrivateData[1] == 0 );
assert( CodecPrivateData[2] == 0 );
assert( CodecPrivateData[3] == 1 );
uint32_t length = cpd_len + 3;
avcC = calloc( length, 1 );
if( unlikely( avcC == NULL ) )
return 0;
uint8_t *sps = avcC + 8;
uint32_t candidate = ~mark;
CodecPrivateData += 4;
for( unsigned i = 0; i < cpd_len - 4; i++ )
{
sps[i] = CodecPrivateData[i];
candidate = (candidate << 8) | CodecPrivateData[i];
if( candidate == mark )
{
sps_len = i - 3;
break;
}
}
if( sps_len == 0 )
return 0;
uint8_t *pps = sps + sps_len + 3;
pps_len = cpd_len - sps_len - 4 * 2;
memcpy( pps, CodecPrivateData + sps_len + 4, pps_len );
/* XXX */
uint8_t AVCProfileIndication = 0x64;
uint8_t profile_compatibility = 0x40;
uint8_t AVCLevelIndication = 0x1f;
uint8_t lengthSizeMinusOne = 0x03;
avcC[0] = 1;
avcC[1] = AVCProfileIndication;
avcC[2] = profile_compatibility;
avcC[3] = AVCLevelIndication;
avcC[4] = 0xfc + lengthSizeMinusOne;
avcC[5] = 0xe0 + 1;
avcC[6] = (sps_len & 0xff00)>>8;
avcC[7] = sps_len & 0xff;
avcC[8+sps_len] = 1;
avcC[9+sps_len] = (pps_len & 0xff00) >> 8;
avcC[10+sps_len] = pps_len & 0xff;
*p_extra = avcC;
return length;
}
/**
* Build a mp4_track_t from a StraBox
*/
static inline int MP4_SetCodecExtraData( es_format_t *fmt, MP4_Box_data_stra_t *p_data )
{
fmt->i_extra = p_data->cpd_len;
fmt->p_extra = malloc( p_data->cpd_len );
if( unlikely( !fmt->p_extra ) )
return VLC_ENOMEM;
memcpy( fmt->p_extra, p_data->CodecPrivateData, p_data->cpd_len );
return VLC_SUCCESS;
}
static int MP4_frg_TrackCreate( demux_t *p_demux, mp4_track_t *p_track, MP4_Box_t *p_stra )
{
demux_sys_t *p_sys = p_demux->p_sys;
int ret;
MP4_Box_data_stra_t *p_data = p_stra->data.p_stra;
if( !p_data )
return VLC_EGENERIC;
p_track->b_ok = true;
p_track->b_selected = false;
p_track->i_sample_count = UINT32_MAX;
p_track->i_timescale = p_sys->i_timescale;
p_track->i_width = p_data->MaxWidth;
p_track->i_height = p_data->MaxHeight;
p_track->i_track_ID = p_data->i_track_ID;
es_format_t *fmt = &p_track->fmt;
if( fmt == NULL )
return VLC_EGENERIC;
es_format_Init( fmt, p_data->i_es_cat, 0 );
/* Set language FIXME */
fmt->psz_language = strdup( "en" );
uint8_t **p_extra = (uint8_t **)&fmt->p_extra;
/* See http://msdn.microsoft.com/en-us/library/ff728116%28v=vs.90%29.aspx
* for MS weird use of FourCC*/
switch( fmt->i_cat )
{
case VIDEO_ES:
if( p_data->FourCC == VLC_FOURCC( 'A', 'V', 'C', '1' ) ||
p_data->FourCC == VLC_FOURCC( 'A', 'V', 'C', 'B' ) ||
p_data->FourCC == VLC_FOURCC( 'H', '2', '6', '4' ) )
{
fmt->i_extra = build_raw_avcC( p_extra,
p_data->CodecPrivateData, p_data->cpd_len );
fmt->i_codec = VLC_CODEC_H264;
}
else
{
fmt->i_codec = p_data->FourCC;
ret = MP4_SetCodecExtraData( fmt, p_data );
if( ret != VLC_SUCCESS )
return ret;
}
fmt->video.i_width = p_data->MaxWidth;
fmt->video.i_height = p_data->MaxHeight;
fmt->video.i_bits_per_pixel = 0x18;
fmt->video.i_visible_width = p_data->MaxWidth;
fmt->video.i_visible_height = p_data->MaxHeight;
/* Frame rate */
ChunkGetESSampleRate( &fmt->video.i_frame_rate,
&fmt->video.i_frame_rate_base, p_track );
if( fmt->video.i_frame_rate_base != 0 )
{
p_demux->p_sys->f_fps = (float)fmt->video.i_frame_rate /
(float)fmt->video.i_frame_rate_base;
}
else
p_demux->p_sys->f_fps = 24;
break;
case AUDIO_ES:
if( p_data->FourCC == VLC_FOURCC( 'A', 'A', 'C', 'H' ) ||
p_data->FourCC == VLC_FOURCC( 'A', 'A', 'C', 'L' ) )
fmt->i_codec = VLC_CODEC_MP4A;
else
fmt->i_codec = p_data->FourCC;
fmt->audio.i_channels = p_data->Channels;
fmt->audio.i_rate = p_data->SamplingRate;
fmt->audio.i_bitspersample = p_data->BitsPerSample;
fmt->audio.i_blockalign = p_data->nBlockAlign;
fmt->i_bitrate = p_data->AvgBytesPerSec * 8;
ret = MP4_SetCodecExtraData( fmt, p_data );
if( ret != VLC_SUCCESS )
return ret;
break;
default:
break;
}
return VLC_SUCCESS;
}
/**
* Return the track identified by tid
*/
static mp4_track_t *MP4_frg_GetTrack( demux_t *p_demux, const uint16_t tid )
{
demux_sys_t *p_sys = p_demux->p_sys;
mp4_track_t *ret = NULL;
for( unsigned i = 0; i < p_sys->i_tracks; i++ )
{
ret = &p_sys->track[i];
if( !ret )
return NULL;
if( ret->i_track_ID == tid )
return ret;
}
msg_Err( p_demux, "MP4_frg_GetTrack: track %"PRIu16" not found!", tid );
return NULL;
}
static int FreeAndResetChunk( mp4_chunk_t *ck )
{
free( ck->p_sample_count_dts );
free( ck->p_sample_delta_dts );
free( ck->p_sample_count_pts );
free( ck->p_sample_offset_pts );
free( ck->p_sample_size );
for( uint32_t i = 0; i < ck->i_sample_count; i++ )
free( ck->p_sample_data[i] );
free( ck->p_sample_data );
memset( ck, 0, sizeof( mp4_chunk_t ) );
return VLC_SUCCESS;
}
static void FlushChunk( demux_t *p_demux, mp4_track_t *tk )
{
msg_Dbg( p_demux, "Flushing chunk for track id %u", tk->i_track_ID );
mp4_chunk_t *ck = tk->cchunk;
while( ck->i_sample < ck->i_sample_count )
{
block_t *p_block;
int64_t i_delta;
if( ck->p_sample_size == NULL || ck->p_sample_data == NULL )
return;
uint32_t sample_size = ck->p_sample_size[ck->i_sample];
assert( sample_size > 0 );
p_block = block_Alloc( sample_size );
if( unlikely( !p_block ) )
return;
uint8_t *src = ck->p_sample_data[ck->i_sample];
memcpy( p_block->p_buffer, src, sample_size );
ck->i_sample++;
/* dts */
p_block->i_dts = VLC_TS_0 + MP4_TrackGetDTS( p_demux, tk );
/* pts */
i_delta = MP4_TrackGetPTSDelta( p_demux, tk );
if( i_delta != -1 )
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;
es_out_Send( p_demux->out, tk->p_es, p_block );
tk->i_sample++;
}
}
/**
* Re-init decoder.
* \Note If we call that function too soon,
* before the track has been selected by MP4_TrackSelect
* (during the first execution of Demux), then the track gets disabled
*/
static int ReInitDecoder( demux_t *p_demux, mp4_track_t *p_track )
{
demux_sys_t *p_sys = p_demux->p_sys;
uint32_t i_sample = 0;
bool b_smooth = false;
MP4_Box_t *p_stra = NULL, *p_trak = NULL;
if( !CmpUUID( &p_sys->p_root->p_first->i_uuid, &SmooBoxUUID ) )
b_smooth = true;
if( b_smooth )
{
p_stra = MP4_BoxGet( p_sys->p_root, "uuid/uuid[0]" );
if( !p_stra || CmpUUID( &p_stra->i_uuid, &StraBoxUUID ) )
return VLC_EGENERIC;
}
else /* DASH */
{
p_trak = MP4_BoxGet( p_sys->p_root, "/moov/trak[0]" );
if( !p_trak )
return VLC_EGENERIC;
}
i_sample = p_track->i_sample;
es_out_Del( p_demux->out, p_track->p_es );
es_format_Clean( &p_track->fmt );
if( b_smooth )
MP4_frg_TrackCreate( p_demux, p_track, p_stra );
else /* DASH */
MP4_TrackCreate( p_demux, p_track, p_trak, true );
p_track->i_sample = i_sample;
/* Temporary hack until we support track selection */
p_track->b_selected = true;
p_track->b_ok = true;
p_track->b_enable = true;
p_track->p_es = es_out_Add( p_demux->out, &p_track->fmt );
p_track->b_codec_need_restart = false;
return VLC_SUCCESS;
}
/**
* This function fills a mp4_chunk_t structure from a MP4_Box_t (p_chunk).
* The 'i_tk_id' argument returns the ID of the track the chunk belongs to.
* \note p_chunk usually contains a 'moof' and a 'mdat', and might contain a 'sidx'.
* \return VLC_SUCCESS, VLC_EGENERIC or VLC_ENOMEM.
*/
static int MP4_frg_GetChunk( demux_t *p_demux, MP4_Box_t *p_chunk, unsigned *i_tk_id )
{
MP4_Box_t *p_sidx = MP4_BoxGet( p_chunk, "sidx" );
MP4_Box_t *p_moof = MP4_BoxGet( p_chunk, "moof" );
if( p_moof == NULL)
{
msg_Warn( p_demux, "no moof box found!" );
return VLC_EGENERIC;
}
MP4_Box_t *p_traf = MP4_BoxGet( p_moof, "traf" );
if( p_traf == NULL)
{
msg_Warn( p_demux, "no traf box found!" );
return VLC_EGENERIC;
}
MP4_Box_t *p_tfhd = MP4_BoxGet( p_traf, "tfhd" );
if( p_tfhd == NULL)
{
msg_Warn( p_demux, "no tfhd box found!" );
return VLC_EGENERIC;
}
uint32_t i_track_ID = p_tfhd->data.p_tfhd->i_track_ID;
*i_tk_id = i_track_ID;
assert( i_track_ID > 0 );
msg_Dbg( p_demux, "GetChunk: track ID is %"PRIu32"", i_track_ID );
mp4_track_t *p_track = MP4_frg_GetTrack( p_demux, i_track_ID );
if( !p_track )
return VLC_EGENERIC;
mp4_chunk_t *ret = p_track->cchunk;
if( p_tfhd->data.p_tfhd->b_empty )
msg_Warn( p_demux, "No samples in this chunk!" );
/* Usually we read 100 ms of each track. However, suppose we have two tracks,
* Ta and Tv (audio and video). Suppose also that Ta is the first track to be
* read, i.e. we read 100 ms of Ta, then 100 ms of Tv, then 100 ms of Ta,
* and so on. Finally, suppose that we get the chunks the other way around,
* i.e. first a chunk of Tv, then a chunk of Ta, then a chunk of Tv, and so on.
* In that case, it is very likely that at some point, Ta->cchunk or Tv->cchunk
* is not emptied when MP4_frg_GetChunks is called. It is therefore necessary to
* flush it, i.e. send to the decoder the samples not yet sent.
* Note that all the samples to be flushed should worth less than 100 ms,
* (though I did not do the formal proof) and thus this flushing mechanism
* should not cause A/V sync issues, or delays or whatever.
*/
if( ret->i_sample < ret->i_sample_count )
FlushChunk( p_demux, p_track );
if( ret->i_sample_count )
FreeAndResetChunk( ret );
uint32_t default_duration = 0;
if( p_tfhd->data.p_tfhd->i_flags & MP4_TFHD_DFLT_SAMPLE_DURATION )
default_duration = p_tfhd->data.p_tfhd->i_default_sample_duration;
uint32_t default_size = 0;
if( p_tfhd->data.p_tfhd->i_flags & MP4_TFHD_DFLT_SAMPLE_SIZE )
default_size = p_tfhd->data.p_tfhd->i_default_sample_size;
MP4_Box_t *p_trun = MP4_BoxGet( p_traf, "trun");
if( p_trun == NULL)
{
msg_Warn( p_demux, "no trun box found!" );
return VLC_EGENERIC;
}
MP4_Box_data_trun_t *p_trun_data = p_trun->data.p_trun;
ret->i_sample_count = p_trun_data->i_sample_count;
assert( ret->i_sample_count > 0 );
ret->i_sample_description_index = 1; /* seems to be always 1, is it? */
ret->i_sample_first = p_track->i_sample_first;
p_track->i_sample_first += ret->i_sample_count;
ret->i_first_dts = p_track->i_first_dts;
/* XXX I already saw DASH content with no default_duration and no
* MP4_TRUN_SAMPLE_DURATION flag, but a sidx box was present.
* This chunk of code might be buggy with segments having
* more than one subsegment. */
if( !default_duration )
{
if( p_sidx )
{
MP4_Box_data_sidx_t *p_sidx_data = p_sidx->data.p_sidx;
assert( p_sidx_data->i_reference_count == 1 );
unsigned i_chunk_duration = p_sidx_data->p_items[0].i_subsegment_duration /
p_sidx_data->i_timescale;
default_duration = i_chunk_duration *
p_track->i_timescale / ret->i_sample_count;
}
}
msg_Dbg( p_demux, "Default sample duration is %"PRIu32, default_duration );
ret->p_sample_count_dts = calloc( ret->i_sample_count, sizeof( uint32_t ) );
ret->p_sample_delta_dts = calloc( ret->i_sample_count, sizeof( uint32_t ) );
if( !ret->p_sample_count_dts || !ret->p_sample_delta_dts )
return VLC_ENOMEM;
ret->p_sample_count_pts = calloc( ret->i_sample_count, sizeof( uint32_t ) );
ret->p_sample_offset_pts = calloc( ret->i_sample_count, sizeof( int32_t ) );
if( !ret->p_sample_count_pts || !ret->p_sample_offset_pts )
return VLC_ENOMEM;
ret->p_sample_size = calloc( ret->i_sample_count, sizeof( uint32_t ) );
if( !ret->p_sample_size )
return VLC_ENOMEM;
ret->p_sample_data = calloc( ret->i_sample_count, sizeof( uint8_t * ) );
if( !ret->p_sample_data )
return VLC_ENOMEM;
uint32_t dur = 0, len;
uint32_t chunk_duration = 0, chunk_size = 0;
/* Skip header of mdat */
stream_Read( p_demux->s, NULL, 8 );
for( uint32_t i = 0; i < ret->i_sample_count; i++)
{
if( p_trun_data->i_flags & MP4_TRUN_SAMPLE_DURATION )
dur = p_trun_data->p_samples[i].i_duration;
else
dur = default_duration;
ret->p_sample_delta_dts[i] = dur;
chunk_duration += dur;
ret->p_sample_count_dts[i] = ret->p_sample_count_pts[i] = 1;
if( p_trun_data->i_flags & MP4_TRUN_SAMPLE_TIME_OFFSET )
{
ret->p_sample_offset_pts[i] =
p_trun_data->p_samples[i].i_composition_time_offset;
}
else
ret->p_sample_offset_pts[i] = 0;
if( p_trun_data->i_flags & MP4_TRUN_SAMPLE_SIZE )
len = ret->p_sample_size[i] = p_trun_data->p_samples[i].i_size;
else
len = ret->p_sample_size[i] = default_size;
ret->p_sample_data[i] = malloc( len );
if( ret->p_sample_data[i] == NULL )
return VLC_ENOMEM;
int read = stream_Read( p_demux->s, ret->p_sample_data[i], len );
if( read < (int)len )
return VLC_EGENERIC;
chunk_size += len;
}
ret->i_last_dts = ret->i_first_dts + chunk_duration - dur;
p_track->i_first_dts = chunk_duration + ret->i_first_dts;
if( p_track->b_codec_need_restart &&
p_track->fmt.i_cat == VIDEO_ES )
ReInitDecoder( p_demux, p_track );
p_track->b_has_non_empty_cchunk = true;
return VLC_SUCCESS;
}
/**
* Get the next chunk of the track identified by i_tk_id.
* \Note We don't want to seek all the time, so if the first chunk given by the
* input method doesn't belong to the right track, we don't throw it away,
* and so, in general, this function fetch more than one chunk.
* Not to mention that a new init segment might be put everywhere
* between two chunks by the input method.
*/
static int MP4_frg_GetChunks( demux_t *p_demux, const unsigned i_tk_id )
{
demux_sys_t *p_sys = p_demux->p_sys;
mp4_track_t *p_track;
for( unsigned i = 0; i < p_sys->i_tracks; i++ )
{
MP4_Box_t *p_chunk = MP4_BoxGetNextChunk( p_demux->s );
if( !p_chunk )
return VLC_EGENERIC;
uint32_t i_type = p_chunk->p_first->i_type;
uint16_t tid = 0;
if( i_type == ATOM_uuid || i_type == ATOM_ftyp )
{
free( p_sys->p_root );
p_sys->p_root = p_chunk;
if( i_type == ATOM_ftyp ) /* DASH */
{
MP4_Box_t *p_tkhd = MP4_BoxGet( p_chunk, "/moov/trak[0]/tkhd" );
if( !p_tkhd )
{
msg_Warn( p_demux, "No tkhd found!" );
free( p_chunk );
return VLC_EGENERIC;
}
tid = p_tkhd->data.p_tkhd->i_track_ID;
}
else /* Smooth Streaming */
{
assert( !CmpUUID( &p_chunk->p_first->i_uuid, &SmooBoxUUID ) );
MP4_Box_t *p_stra = MP4_BoxGet( p_chunk, "/uuid/uuid[0]" );
if( !p_stra || CmpUUID( &p_stra->i_uuid, &StraBoxUUID ) )
{
msg_Warn( p_demux, "No StraBox found!" );
free( p_chunk );
return VLC_EGENERIC;
}
tid = p_stra->data.p_stra->i_track_ID;
}
p_track = MP4_frg_GetTrack( p_demux, tid );
if( !p_track )
return VLC_EGENERIC;
p_track->b_codec_need_restart = true;
return MP4_frg_GetChunks( p_demux, i_tk_id );
}
if( MP4_frg_GetChunk( p_demux, p_chunk, (unsigned *)&tid ) != VLC_SUCCESS )
return VLC_EGENERIC;
if( tid == i_tk_id )
break;
}
return VLC_SUCCESS;
}
static int MP4_frg_TrackSelect( demux_t *p_demux, mp4_track_t *p_track )
{
if( !p_track->b_ok || p_track->b_chapter )
{
return VLC_EGENERIC;
}
if( p_track->b_selected )
{
msg_Warn( p_demux, "track[Id 0x%x] already selected", p_track->i_track_ID );
return VLC_SUCCESS;
}
msg_Dbg( p_demux, "Select track id %u", p_track->i_track_ID );
p_track->b_selected = true;
return VLC_SUCCESS;
}
/**
* DemuxFrg: read packet and send them to decoders
* \return 1 on success, 0 on error.
* TODO check for newly selected track
*/
int DemuxFrg( demux_t *p_demux )
{
demux_sys_t *p_sys = p_demux->p_sys;
unsigned i_track;
unsigned 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++ )
{
mp4_track_t *tk = &p_sys->track[i_track];
bool b;
if( !tk->b_ok || tk->b_chapter )
continue;
es_out_Control( p_demux->out, ES_OUT_GET_ES_STATE, tk->p_es, &b );
msg_Dbg( p_demux, "track %u %s!", tk->i_track_ID, b ? "enabled" : "disabled" );
if( tk->b_selected && !b )
{
MP4_TrackUnselect( p_demux, tk );
}
else if( !tk->b_selected && b)
{
MP4_frg_TrackSelect( p_demux, tk );
}
if( tk->b_selected )
i_track_selected++;
}
if( i_track_selected <= 0 )
{
p_sys->i_time += __MAX( p_sys->i_timescale / 10 , 1 );
if( p_sys->i_timescale > 0 )
{
int64_t i_length = (mtime_t)1000000 *
(mtime_t)p_sys->i_duration /
(mtime_t)p_sys->i_timescale;
if( MP4_GetMoviePTS( p_sys ) >= i_length )
return 0;
return 1;
}
msg_Warn( p_demux, "no track selected, exiting..." );
return 0;
}
/* first wait for the good time to read a packet */
es_out_Control( p_demux->out, ES_OUT_SET_PCR, VLC_TS_0 + p_sys->i_pcr );
p_sys->i_pcr = MP4_GetMoviePTS( p_sys );
/* we will read 100ms for each stream so ...*/
p_sys->i_time += __MAX( p_sys->i_timescale / 10, 1 );
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_chapter || !tk->b_selected )
{
msg_Warn( p_demux, "Skipping track id %u", tk->i_track_ID );
continue;
}
if( !tk->b_has_non_empty_cchunk )
{
if( MP4_frg_GetChunks( p_demux, tk->i_track_ID ) != VLC_SUCCESS )
{
msg_Info( p_demux, "MP4_frg_GetChunks returned error. End of stream?" );
return 0;
}
}
while( MP4_TrackGetDTS( p_demux, tk ) < MP4_GetMoviePTS( p_sys ) )
{
block_t *p_block;
int64_t i_delta;
mp4_chunk_t *ck = tk->cchunk;
if( ck->i_sample >= ck->i_sample_count )
{
msg_Err( p_demux, "sample %"PRIu32" of %"PRIu32"",
ck->i_sample, ck->i_sample_count );
return 0;
}
uint32_t sample_size = ck->p_sample_size[ck->i_sample];
p_block = block_Alloc( sample_size );
uint8_t *src = ck->p_sample_data[ck->i_sample];
memcpy( p_block->p_buffer, src, sample_size );
ck->i_sample++;
if( ck->i_sample == ck->i_sample_count )
tk->b_has_non_empty_cchunk = false;
/* dts */
p_block->i_dts = VLC_TS_0 + MP4_TrackGetDTS( p_demux, tk );
/* pts */
i_delta = MP4_TrackGetPTSDelta( p_demux, tk );
if( i_delta != -1 )
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;
es_out_Send( p_demux->out, tk->p_es, p_block );
tk->i_sample++;
if( !tk->b_has_non_empty_cchunk )
break;
}
}
return 1;
}
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