Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc-2-2
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
videolan
vlc-2-2
Commits
86e8c9d5
Commit
86e8c9d5
authored
Jul 18, 2012
by
Frédéric Yhuel
Committed by
Jean-Baptiste Kempf
Sep 21, 2012
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
demux/mp4: Add fragmented MP4 support
Signed-off-by:
Jean-Baptiste Kempf
<
jb@videolan.org
>
parent
2f1fe72f
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
1014 additions
and
127 deletions
+1014
-127
modules/demux/mp4/libmp4.c
modules/demux/mp4/libmp4.c
+78
-92
modules/demux/mp4/libmp4.h
modules/demux/mp4/libmp4.h
+5
-9
modules/demux/mp4/mp4.c
modules/demux/mp4/mp4.c
+931
-26
No files found.
modules/demux/mp4/libmp4.c
View file @
86e8c9d5
...
...
@@ -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_BoxGet
InitFrag
(
s
);
return
MP4_BoxGet
Root
(
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
;
}
...
...
modules/demux/mp4/libmp4.h
View file @
86e8c9d5
...
...
@@ -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.
...
...
modules/demux/mp4/mp4.c
View file @
86e8c9d5
...
...
@@ -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_
FAST
SEEK
,
&
b_seekable
);
stream_Control
(
p_demux
->
s
,
STREAM_CAN_SEEK
,
&
b_seekable
);
if
(
!
b_seekable
)
{
msg_Warn
(
p_demux
,
"MP4 plugin discarded (not
fast
seekable)"
);
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
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment