1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2001, 2002, 2003, 2006 VLC authors and VideoLAN
7 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8 * Gildas Bazin <gbazin at videolan dot org>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
28 /*****************************************************************************
30 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
35 #include <vlc_block.h>
40 #include <vlc_iso_lang.h>
43 #include "../demux/mp4/libmp4.h"
44 #include "libmp4mux.h"
45 #include "../packetizer/hxxx_nal.h"
46 #include "../av1_pack.h"
48 /*****************************************************************************
50 *****************************************************************************/
51 #define FASTSTART_TEXT N_("Create \"Fast Start\" files")
52 #define FASTSTART_LONGTEXT N_(\
53 "Create \"Fast Start\" files. " \
54 "\"Fast Start\" files are optimized for downloads and allow the user " \
55 "to start previewing the file while it is downloading.")
57 static int Open (vlc_object_t
*);
58 static void Close (vlc_object_t
*);
59 static void CloseFrag (vlc_object_t
*);
61 #define SOUT_CFG_PREFIX "sout-mp4-"
64 set_description(N_("MP4/MOV muxer"))
65 set_category(CAT_SOUT
)
66 set_subcategory(SUBCAT_SOUT_MUX
)
69 add_bool(SOUT_CFG_PREFIX
"faststart", true,
70 FASTSTART_TEXT
, FASTSTART_LONGTEXT
,
72 set_capability("sout mux", 5)
73 add_shortcut("mp4", "mov", "3gp")
74 set_callbacks(Open
, Close
)
77 set_description(N_("Fragmented and streamable MP4 muxer"))
78 set_category(CAT_SOUT
)
79 set_subcategory(SUBCAT_SOUT_MUX
)
80 set_shortname("MP4 Frag")
81 add_shortcut("mp4frag", "mp4stream")
82 set_capability("sout mux", 0)
83 set_callbacks(Open
, CloseFrag
)
87 /*****************************************************************************
89 *****************************************************************************/
90 static const char *const ppsz_sout_options
[] = {
94 static int Control(sout_mux_t
*, int, va_list);
95 static int AddStream(sout_mux_t
*, sout_input_t
*);
96 static void DelStream(sout_mux_t
*, sout_input_t
*);
97 static int Mux (sout_mux_t
*);
98 static int MuxFrag (sout_mux_t
*);
100 /*****************************************************************************
102 *****************************************************************************/
104 typedef struct mp4_fragentry_t mp4_fragentry_t
;
106 struct mp4_fragentry_t
110 mp4_fragentry_t
*p_next
;
113 typedef struct mp4_fragindex_t
115 uint64_t i_moofoffset
;
122 typedef struct mp4_fragqueue_t
124 mp4_fragentry_t
*p_first
;
125 mp4_fragentry_t
*p_last
;
130 mp4mux_trackinfo_t
*tinfo
;
133 vlc_tick_t i_length_neg
;
135 /* applies to current segment only */
143 uint32_t i_current_run
;
144 mp4_fragentry_t
*p_held_entry
;
145 mp4_fragqueue_t read
;
146 mp4_fragqueue_t towrite
;
147 vlc_tick_t i_last_iframe_time
;
148 vlc_tick_t i_written_duration
;
149 mp4_fragindex_t
*p_indexentries
;
150 uint32_t i_indexentriesmax
;
151 uint32_t i_indexentries
;
156 mp4mux_handle_t
*muxh
;
165 vlc_tick_t i_read_duration
;
166 vlc_tick_t i_start_dts
;
168 unsigned int i_nb_streams
;
169 mp4_stream_t
**pp_streams
;
173 vlc_tick_t i_written_duration
;
174 uint32_t i_mfhd_sequence
;
177 static void box_send(sout_mux_t
*p_mux
, bo_t
*box
);
179 static block_t
*ConvertSUBT(block_t
*);
180 static bool CreateCurrentEdit(mp4_stream_t
*, vlc_tick_t
, bool);
181 static int MuxStream(sout_mux_t
*p_mux
, sout_input_t
*p_input
, mp4_stream_t
*p_stream
);
183 static int WriteSlowStartHeader(sout_mux_t
*p_mux
)
185 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
188 if (!mp4mux_Is(p_sys
->muxh
, QUICKTIME
)) {
189 /* Now add ftyp header */
192 vlc_fourcc_t extra
[] = {MAJOR_3gp4
, MAJOR_avc1
};
193 box
= mp4mux_GetFtyp(MAJOR_3gp6
, 0, extra
, ARRAY_SIZE(extra
));
197 vlc_fourcc_t extra
[] = {MAJOR_mp41
, MAJOR_avc1
};
198 box
= mp4mux_GetFtyp(MAJOR_isom
, 0, extra
, ARRAY_SIZE(extra
));
204 p_sys
->i_pos
+= bo_size(box
);
205 p_sys
->i_mdat_pos
= p_sys
->i_pos
;
206 box_send(p_mux
, box
);
209 /* Now add mdat header */
210 box
= box_new("mdat");
214 bo_add_64be(box
, 0); // enough to store an extended size
217 p_sys
->i_pos
+= bo_size(box
);
219 box_send(p_mux
, box
);
224 /*****************************************************************************
226 *****************************************************************************/
227 static int Open(vlc_object_t
*p_this
)
229 sout_mux_t
*p_mux
= (sout_mux_t
*)p_this
;
230 sout_mux_sys_t
*p_sys
= malloc(sizeof(sout_mux_sys_t
));
234 msg_Dbg(p_mux
, "Mp4 muxer opened");
235 config_ChainParse(p_mux
, SOUT_CFG_PREFIX
, ppsz_sout_options
, p_mux
->p_cfg
);
237 enum mp4mux_options options
= 0;
240 if(!strcmp(p_mux
->psz_mux
, "mov"))
241 options
|= QUICKTIME
;
242 if(!strcmp(p_mux
->psz_mux
, "mp4frag") || !strcmp(p_mux
->psz_mux
, "mp4stream"))
243 options
|= FRAGMENTED
;
246 p_sys
->b_3gp
= p_mux
->psz_mux
&& !strcmp(p_mux
->psz_mux
, "3gp");
248 p_sys
->muxh
= mp4mux_New(options
);
251 p_sys
->i_nb_streams
= 0;
252 p_sys
->pp_streams
= NULL
;
253 p_sys
->i_mdat_pos
= 0;
254 p_sys
->b_header_sent
= false;
256 p_sys
->i_read_duration
= 0;
257 p_sys
->i_written_duration
= 0;
258 p_sys
->i_start_dts
= VLC_TICK_INVALID
;
259 p_sys
->i_mfhd_sequence
= 1;
261 p_mux
->p_sys
= p_sys
;
262 p_mux
->pf_control
= Control
;
263 p_mux
->pf_addstream
= AddStream
;
264 p_mux
->pf_delstream
= DelStream
;
265 p_mux
->pf_mux
= (options
& FRAGMENTED
) ? MuxFrag
: Mux
;
270 /*****************************************************************************
272 *****************************************************************************/
273 static void Close(vlc_object_t
*p_this
)
275 sout_mux_t
*p_mux
= (sout_mux_t
*)p_this
;
276 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
278 msg_Dbg(p_mux
, "Close");
280 /* Update mdat size */
282 if (!bo_init(&bo
, 16))
284 if (p_sys
->i_pos
- p_sys
->i_mdat_pos
>= (((uint64_t)1)<<32)) {
286 bo_add_32be (&bo
, 1);
287 bo_add_fourcc(&bo
, "mdat");
288 bo_add_64be (&bo
, p_sys
->i_pos
- p_sys
->i_mdat_pos
);
290 bo_add_32be (&bo
, 8);
291 bo_add_fourcc(&bo
, "wide");
292 bo_add_32be (&bo
, p_sys
->i_pos
- p_sys
->i_mdat_pos
- 8);
293 bo_add_fourcc(&bo
, "mdat");
296 sout_AccessOutSeek(p_mux
->p_access
, p_sys
->i_mdat_pos
);
297 sout_AccessOutWrite(p_mux
->p_access
, bo
.b
);
299 /* Create MOOV header */
300 const bool b_64bitext
= (p_sys
->i_pos
>= (((uint64_t)0x1) << 32));
302 mp4mux_Set64BitExt(p_sys
->muxh
);
303 uint64_t i_moov_pos
= p_sys
->i_pos
;
304 bo_t
*moov
= mp4mux_GetMoov(p_sys
->muxh
, VLC_OBJECT(p_mux
), 0);
306 /* Check we need to create "fast start" files */
307 p_sys
->b_fast_start
= var_GetBool(p_this
, SOUT_CFG_PREFIX
"faststart");
308 while (p_sys
->b_fast_start
&& moov
&& moov
->b
) {
309 /* Move data to the end of the file so we can fit the moov header
311 int64_t i_size
= p_sys
->i_pos
- p_sys
->i_mdat_pos
;
312 int i_moov_size
= bo_size(moov
);
315 int64_t i_chunk
= __MIN(32768, i_size
);
316 block_t
*p_buf
= block_Alloc(i_chunk
);
317 sout_AccessOutSeek(p_mux
->p_access
,
318 p_sys
->i_mdat_pos
+ i_size
- i_chunk
);
319 if (sout_AccessOutRead(p_mux
->p_access
, p_buf
) < i_chunk
) {
320 msg_Warn(p_this
, "read() not supported by access output, "
321 "won't create a fast start file");
322 p_sys
->b_fast_start
= false;
323 block_Release(p_buf
);
326 sout_AccessOutSeek(p_mux
->p_access
, p_sys
->i_mdat_pos
+ i_size
+
327 i_moov_size
- i_chunk
);
328 sout_AccessOutWrite(p_mux
->p_access
, p_buf
);
332 if (!p_sys
->b_fast_start
)
335 /* Update pos pointers */
336 i_moov_pos
= p_sys
->i_mdat_pos
;
337 p_sys
->i_mdat_pos
+= bo_size(moov
);
339 /* Fix-up samples to chunks table in MOOV header */
340 for (unsigned int i_trak
= 0; i_trak
< p_sys
->i_nb_streams
; i_trak
++) {
341 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i_trak
];
342 unsigned i_written
= 0;
343 for (unsigned i
= 0; i
< p_stream
->tinfo
->i_samples_count
; ) {
344 mp4mux_sample_t
*entry
= p_stream
->tinfo
->samples
;
346 bo_set_64be(moov
, p_stream
->tinfo
->i_stco_pos
+ i_written
++ * 8, entry
[i
].i_pos
+ p_sys
->i_mdat_pos
- i_moov_pos
);
348 bo_set_32be(moov
, p_stream
->tinfo
->i_stco_pos
+ i_written
++ * 4, entry
[i
].i_pos
+ p_sys
->i_mdat_pos
- i_moov_pos
);
350 for (; i
< p_stream
->tinfo
->i_samples_count
; i
++)
351 if (i
>= p_stream
->tinfo
->i_samples_count
- 1 ||
352 entry
[i
].i_pos
+ entry
[i
].i_size
!= entry
[i
+1].i_pos
) {
359 p_sys
->b_fast_start
= false;
362 /* Write MOOV header */
363 sout_AccessOutSeek(p_mux
->p_access
, i_moov_pos
);
365 box_send(p_mux
, moov
);
369 for (unsigned int i_trak
= 0; i_trak
< p_sys
->i_nb_streams
; i_trak
++)
370 free(p_sys
->pp_streams
[i_trak
]);
371 TAB_CLEAN(p_sys
->i_nb_streams
, p_sys
->pp_streams
);
372 mp4mux_Delete(p_sys
->muxh
);
376 /*****************************************************************************
378 *****************************************************************************/
379 static int Control(sout_mux_t
*p_mux
, int i_query
, va_list args
)
386 case MUX_CAN_ADD_STREAM_WHILE_MUXING
:
387 pb_bool
= va_arg(args
, bool *);
391 case MUX_GET_ADD_STREAM_WAIT
:
392 pb_bool
= va_arg(args
, bool *);
396 case MUX_GET_MIME
: /* Not needed, as not streamable */
402 /*****************************************************************************
404 *****************************************************************************/
405 static int AddStream(sout_mux_t
*p_mux
, sout_input_t
*p_input
)
407 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
408 mp4_stream_t
*p_stream
;
410 if(!mp4mux_CanMux(VLC_OBJECT(p_mux
), p_input
->p_fmt
,
411 mp4mux_Is(p_sys
->muxh
, QUICKTIME
) ? MAJOR_qt__
: MAJOR_isom
,
412 mp4mux_Is(p_sys
->muxh
, FRAGMENTED
)))
414 msg_Err(p_mux
, "unsupported codec %4.4s in mp4",
415 (char*)&p_input
->p_fmt
->i_codec
);
419 p_stream
= malloc(sizeof(mp4_stream_t
));
423 uint32_t i_track_timescale
= CLOCK_FREQ
;
424 es_format_t trackfmt
;
425 es_format_Init(&trackfmt
, p_input
->p_fmt
->i_cat
, p_input
->p_fmt
->i_codec
);
426 es_format_Copy(&trackfmt
, p_input
->p_fmt
);
428 switch( p_input
->p_fmt
->i_cat
)
431 if(!trackfmt
.audio
.i_rate
)
433 msg_Warn( p_mux
, "no audio rate given for stream %d, assuming 48KHz",
434 p_sys
->i_nb_streams
);
435 trackfmt
.audio
.i_rate
= 48000;
437 i_track_timescale
= trackfmt
.audio
.i_rate
;
440 if( !trackfmt
.video
.i_frame_rate
||
441 !trackfmt
.video
.i_frame_rate_base
)
443 msg_Warn( p_mux
, "Missing frame rate for stream %d, assuming 25fps",
444 p_sys
->i_nb_streams
);
445 trackfmt
.video
.i_frame_rate
= 25;
446 trackfmt
.video
.i_frame_rate_base
= 1;
449 i_track_timescale
= trackfmt
.video
.i_frame_rate
*
450 trackfmt
.video
.i_frame_rate_base
;
452 if( i_track_timescale
> CLOCK_FREQ
)
453 i_track_timescale
= CLOCK_FREQ
;
454 else if( i_track_timescale
< 90000 )
455 i_track_timescale
= 90000;
461 p_stream
->tinfo
= mp4mux_track_Add(p_sys
->muxh
, p_sys
->i_nb_streams
+ 1,
462 &trackfmt
, i_track_timescale
);
463 es_format_Clean(&trackfmt
);
470 p_stream
->i_length_neg
= 0;
471 p_stream
->i_last_dts
= VLC_TICK_INVALID
;
472 p_stream
->i_last_pts
= VLC_TICK_INVALID
;
474 p_stream
->b_hasiframes
= false;
476 p_stream
->i_current_run
= 0;
477 p_stream
->read
.p_first
= NULL
;
478 p_stream
->read
.p_last
= NULL
;
479 p_stream
->towrite
.p_first
= NULL
;
480 p_stream
->towrite
.p_last
= NULL
;
481 p_stream
->p_held_entry
= NULL
;
482 p_stream
->i_last_iframe_time
= 0;
483 p_stream
->i_written_duration
= 0;
484 p_stream
->p_indexentries
= NULL
;
485 p_stream
->i_indexentriesmax
= 0;
486 p_stream
->i_indexentries
= 0;
488 p_input
->p_sys
= p_stream
;
490 msg_Dbg(p_mux
, "adding input");
492 TAB_APPEND(p_sys
->i_nb_streams
, p_sys
->pp_streams
, p_stream
);
496 /*****************************************************************************
498 *****************************************************************************/
499 static void DelStream(sout_mux_t
*p_mux
, sout_input_t
*p_input
)
501 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
502 mp4_stream_t
*p_stream
= (mp4_stream_t
*)p_input
->p_sys
;
504 if(!mp4mux_Is(p_sys
->muxh
, FRAGMENTED
))
506 while(block_FifoCount(p_input
->p_fifo
) > 0 &&
507 MuxStream(p_mux
, p_input
, p_stream
) == VLC_SUCCESS
) {};
509 if(CreateCurrentEdit(p_stream
, p_sys
->i_start_dts
, false))
510 mp4mux_track_DebugEdits(VLC_OBJECT(p_mux
), p_stream
->tinfo
);
513 msg_Dbg(p_mux
, "removing input");
516 /*****************************************************************************
518 *****************************************************************************/
519 static bool CreateCurrentEdit(mp4_stream_t
*p_stream
, vlc_tick_t i_mux_start_dts
,
522 const mp4mux_edit_t
*p_lastedit
= mp4mux_track_GetLastEdit(p_stream
->tinfo
);
524 /* Never more than first empty edit for fragmented */
525 if(p_lastedit
!= NULL
&& b_fragmented
)
528 if(p_stream
->tinfo
->i_samples_count
== 0)
531 mp4mux_edit_t newedit
;
533 if(p_lastedit
== NULL
)
535 newedit
.i_start_time
= 0;
536 newedit
.i_start_offset
= __MAX(0, p_stream
->i_first_dts
- i_mux_start_dts
);
540 newedit
.i_start_time
= __MAX(0, p_lastedit
->i_start_time
+ p_lastedit
->i_duration
);
541 newedit
.i_start_offset
= 0;
546 newedit
.i_duration
= 0;
550 if(p_stream
->i_last_pts
!= VLC_TICK_INVALID
)
551 newedit
.i_duration
= p_stream
->i_last_pts
- p_stream
->i_first_dts
;
553 newedit
.i_duration
= p_stream
->i_last_dts
- p_stream
->i_first_dts
;
554 if(p_stream
->tinfo
->i_samples_count
)
555 newedit
.i_duration
+= p_stream
->tinfo
->samples
[p_stream
->tinfo
->i_samples_count
- 1].i_length
;
558 return mp4mux_track_AddEdit(p_stream
->tinfo
, &newedit
);
561 static block_t
* BlockDequeue(sout_input_t
*p_input
, mp4_stream_t
*p_stream
)
563 block_t
*p_block
= block_FifoGet(p_input
->p_fifo
);
564 if(unlikely(!p_block
))
567 switch(p_stream
->tinfo
->fmt
.i_codec
)
570 p_block
= AV1_Pack_Sample(p_block
);
574 p_block
= hxxx_AnnexB_to_xVC(p_block
, 4);
577 p_block
= ConvertSUBT(p_block
);
581 if (p_stream
->tinfo
->a52_frame
== NULL
&& p_block
->i_buffer
>= 8)
582 p_stream
->tinfo
->a52_frame
= block_Duplicate(p_block
);
591 static inline vlc_tick_t
dts_fb_pts( const block_t
*p_data
)
593 return p_data
->i_dts
!= VLC_TICK_INVALID
? p_data
->i_dts
: p_data
->i_pts
;
596 static int MuxStream(sout_mux_t
*p_mux
, sout_input_t
*p_input
, mp4_stream_t
*p_stream
)
598 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
600 block_t
*p_data
= BlockDequeue(p_input
, p_stream
);
604 /* Reset reference dts in case of discontinuity (ex: gather sout) */
605 if (p_data
->i_flags
& BLOCK_FLAG_DISCONTINUITY
&& p_stream
->tinfo
->i_samples_count
)
607 if(p_stream
->i_first_dts
!= VLC_TICK_INVALID
)
609 if(!CreateCurrentEdit(p_stream
, p_sys
->i_start_dts
,
610 mp4mux_Is(p_sys
->muxh
, FRAGMENTED
)))
612 block_Release( p_data
);
617 p_stream
->i_length_neg
= 0;
618 p_stream
->i_first_dts
= VLC_TICK_INVALID
;
619 p_stream
->i_last_dts
= VLC_TICK_INVALID
;
620 p_stream
->i_last_pts
= VLC_TICK_INVALID
;
623 /* Set current segment ranges */
624 if( p_stream
->i_first_dts
== VLC_TICK_INVALID
)
626 p_stream
->i_first_dts
= dts_fb_pts( p_data
);
627 if( p_sys
->i_start_dts
== VLC_TICK_INVALID
)
628 p_sys
->i_start_dts
= p_stream
->i_first_dts
;
631 if (p_stream
->tinfo
->fmt
.i_cat
!= SPU_ES
)
633 /* Fix length of the sample */
634 if (block_FifoCount(p_input
->p_fifo
) > 0)
636 block_t
*p_next
= block_FifoShow(p_input
->p_fifo
);
637 if ( p_next
->i_flags
& BLOCK_FLAG_DISCONTINUITY
)
638 { /* we have no way to know real length except by decoding */
639 if ( p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
)
641 p_data
->i_length
= vlc_tick_from_samples(
642 p_stream
->tinfo
->fmt
.video
.i_frame_rate_base
,
643 p_stream
->tinfo
->fmt
.video
.i_frame_rate
);
644 if( p_data
->i_flags
& BLOCK_FLAG_SINGLE_FIELD
)
645 p_data
->i_length
>>= 1;
646 msg_Dbg( p_mux
, "video track %u fixup to %"PRId64
" for sample %u",
647 p_stream
->tinfo
->i_track_id
, p_data
->i_length
, p_stream
->tinfo
->i_samples_count
);
649 else if ( p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
&&
650 p_stream
->tinfo
->fmt
.audio
.i_rate
&&
651 p_data
->i_nb_samples
)
653 p_data
->i_length
= vlc_tick_from_samples(p_data
->i_nb_samples
,
654 p_stream
->tinfo
->fmt
.audio
.i_rate
);
655 msg_Dbg( p_mux
, "audio track %u fixup to %"PRId64
" for sample %u",
656 p_stream
->tinfo
->i_track_id
, p_data
->i_length
, p_stream
->tinfo
->i_samples_count
);
658 else if ( p_data
->i_length
<= 0 )
660 msg_Warn( p_mux
, "unknown length for track %u sample %u",
661 p_stream
->tinfo
->i_track_id
, p_stream
->tinfo
->i_samples_count
);
662 p_data
->i_length
= 1;
667 vlc_tick_t i_diff
= dts_fb_pts( p_next
) - dts_fb_pts( p_data
);
668 if (i_diff
< VLC_TICK_FROM_SEC(1)) /* protection */
669 p_data
->i_length
= i_diff
;
672 if (p_data
->i_length
<= 0) {
673 msg_Warn(p_mux
, "i_length <= 0");
674 p_stream
->i_length_neg
+= p_data
->i_length
- 1;
675 p_data
->i_length
= 1;
676 } else if (p_stream
->i_length_neg
< 0) {
677 int64_t i_recover
= __MIN(p_data
->i_length
/ 4, - p_stream
->i_length_neg
);
679 p_data
->i_length
-= i_recover
;
680 p_stream
->i_length_neg
+= i_recover
;
685 mp4mux_sample_t
*p_lastsample
= mp4mux_track_GetLastSample(p_stream
->tinfo
);
686 if (p_lastsample
!= NULL
&& p_lastsample
->i_length
== 0)
688 /* length of previous spu, stored in spu clearer */
689 int64_t i_length
= dts_fb_pts( p_data
) - p_stream
->i_last_dts
;
693 p_lastsample
->i_length
= i_length
;
694 p_stream
->tinfo
->i_read_duration
+= i_length
;
698 /* Update (Not earlier for SPU!) */
699 p_stream
->i_last_dts
= dts_fb_pts( p_data
);
700 if( p_data
->i_pts
> p_stream
->i_last_pts
)
701 p_stream
->i_last_pts
= p_data
->i_pts
;
703 /* add index entry */
704 mp4mux_sample_t sample
;
705 sample
.i_pos
= p_sys
->i_pos
;
706 sample
.i_size
= p_data
->i_buffer
;
708 if ( p_data
->i_dts
!= VLC_TICK_INVALID
&& p_data
->i_pts
> p_data
->i_dts
)
710 sample
.i_pts_dts
= p_data
->i_pts
- p_data
->i_dts
;
711 if ( !p_stream
->tinfo
->b_hasbframes
)
712 p_stream
->tinfo
->b_hasbframes
= true;
714 else sample
.i_pts_dts
= 0;
716 sample
.i_length
= p_data
->i_length
;
717 sample
.i_flags
= p_data
->i_flags
;
720 p_stream
->tinfo
->i_read_duration
+= __MAX( 0, p_data
->i_length
);
721 p_stream
->i_last_dts
= dts_fb_pts( p_data
);
724 if(mp4mux_track_AddSample(p_stream
->tinfo
, &sample
))
726 p_sys
->i_pos
+= p_data
->i_buffer
;
727 sout_AccessOutWrite(p_mux
->p_access
, p_data
);
730 /* Add SPU clearing tag (duration tb fixed on next SPU or stream end )*/
731 if ( p_stream
->tinfo
->fmt
.i_cat
== SPU_ES
&& sample
.i_length
> 0 )
733 block_t
*p_empty
= NULL
;
734 if(p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_SUBT
||
735 p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_QTXT
||
736 p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_TX3G
)
738 p_empty
= block_Alloc(3);
742 p_empty
->p_buffer
[0] = 0;
743 p_empty
->p_buffer
[1] = 1;
744 p_empty
->p_buffer
[2] = ' ';
747 else if(p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_TTML
)
749 p_empty
= block_Alloc(40);
751 memcpy(p_empty
->p_buffer
, "<tt><body><div><p></p></div></body></tt>", 40);
753 else if(p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_WEBVTT
)
755 p_empty
= block_Alloc(8);
757 memcpy(p_empty
->p_buffer
, "\x00\x00\x00\x08vtte", 8);
760 /* point to start of our empty */
761 p_stream
->i_last_dts
+= sample
.i_length
;
765 /* Append a idx entry */
766 /* XXX: No need to grow the entry here */
767 mp4mux_sample_t closersample
;
768 closersample
.i_pos
= p_sys
->i_pos
;
769 closersample
.i_size
= p_empty
->i_buffer
;
770 closersample
.i_pts_dts
= 0;
771 closersample
.i_length
= 0; /* will add dts diff later*/
772 closersample
.i_flags
= 0;
774 if(mp4mux_track_AddSample(p_stream
->tinfo
, &closersample
))
776 p_sys
->i_pos
+= p_empty
->i_buffer
;
777 sout_AccessOutWrite(p_mux
->p_access
, p_empty
);
782 /* Update the global segment/media duration */
783 if( p_stream
->tinfo
->i_read_duration
> p_sys
->i_read_duration
)
784 p_sys
->i_read_duration
= p_stream
->tinfo
->i_read_duration
;
789 static int Mux(sout_mux_t
*p_mux
)
791 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
792 int i_ret
= VLC_SUCCESS
;
794 if(!p_sys
->b_header_sent
)
796 i_ret
= WriteSlowStartHeader(p_mux
);
797 if(i_ret
!= VLC_SUCCESS
)
799 p_sys
->b_header_sent
= true;
804 int i_stream
= sout_MuxGetStream(p_mux
, 2, NULL
);
808 sout_input_t
*p_input
= p_mux
->pp_inputs
[i_stream
];
809 mp4_stream_t
*p_stream
= (mp4_stream_t
*)p_input
->p_sys
;
811 i_ret
= MuxStream(p_mux
, p_input
, p_stream
);
812 } while( i_ret
== VLC_SUCCESS
);
817 /*****************************************************************************
819 *****************************************************************************/
820 static block_t
*ConvertSUBT(block_t
*p_block
)
822 p_block
= block_Realloc(p_block
, 2, p_block
->i_buffer
);
825 /* No trailling '\0' */
826 if (p_block
->i_buffer
> 2 && p_block
->p_buffer
[p_block
->i_buffer
-1] == '\0')
829 p_block
->p_buffer
[0] = ((p_block
->i_buffer
- 2) >> 8)&0xff;
830 p_block
->p_buffer
[1] = ((p_block
->i_buffer
- 2) )&0xff;
835 static void box_send(sout_mux_t
*p_mux
, bo_t
*box
)
839 sout_AccessOutWrite(p_mux
->p_access
, box
->b
);
843 /***************************************************************************
845 ****************************************************************************/
846 #define FRAGMENT_LENGTH VLC_TICK_FROM_MS(1500)
848 #define ENQUEUE_ENTRY(object, entry) \
851 object.p_last->p_next = entry;\
852 object.p_last = entry;\
853 if (!object.p_first)\
854 object.p_first = entry;\
857 #define DEQUEUE_ENTRY(object, entry) \
859 entry = object.p_first;\
860 if (object.p_last == entry)\
861 object.p_last = NULL;\
862 object.p_first = object.p_first->p_next;\
863 entry->p_next = NULL;\
866 /* Creates mfra/traf index entries */
867 static void AddKeyframeEntry(mp4_stream_t
*p_stream
, const uint64_t i_moof_pos
,
868 const uint8_t i_traf
, const uint32_t i_sample
,
869 const vlc_tick_t i_time
)
871 /* alloc or realloc */
872 mp4_fragindex_t
*p_entries
= p_stream
->p_indexentries
;
873 if (p_stream
->i_indexentries
>= p_stream
->i_indexentriesmax
)
875 p_stream
->i_indexentriesmax
+= 256;
876 p_entries
= xrealloc(p_stream
->p_indexentries
,
877 p_stream
->i_indexentriesmax
* sizeof(mp4_fragindex_t
));
878 if (p_entries
) /* realloc can fail */
879 p_stream
->p_indexentries
= p_entries
;
882 vlc_tick_t i_last_entry_time
;
883 if (p_stream
->i_indexentries
)
884 i_last_entry_time
= p_stream
->p_indexentries
[p_stream
->i_indexentries
- 1].i_time
;
886 i_last_entry_time
= 0;
888 if (p_entries
&& i_time
- i_last_entry_time
>= VLC_TICK_FROM_SEC(2))
890 mp4_fragindex_t
*p_indexentry
= &p_stream
->p_indexentries
[p_stream
->i_indexentries
];
891 p_indexentry
->i_time
= i_time
;
892 p_indexentry
->i_moofoffset
= i_moof_pos
;
893 p_indexentry
->i_sample
= i_sample
;
894 p_indexentry
->i_traf
= i_traf
;
895 p_indexentry
->i_trun
= 1;
896 p_stream
->i_indexentries
++;
900 /* Creates moof box and traf/trun information.
901 * Single run per traf is absolutely not optimal as interleaving should be done
902 * using runs and not limiting moof size, but creating an relative offset only
903 * requires base_offset_is_moof and then comply to late iso brand spec which
905 static bo_t
*GetMoofBox(sout_mux_t
*p_mux
, size_t *pi_mdat_total_size
,
906 vlc_tick_t i_barrier_time
, const uint64_t i_write_pos
)
908 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
911 size_t i_fixupoffset
= 0;
913 *pi_mdat_total_size
= 0;
915 moof
= box_new("moof");
919 /* *** add /moof/mfhd *** */
921 mfhd
= box_full_new("mfhd", 0, 0);
927 bo_add_32be(mfhd
, p_sys
->i_mfhd_sequence
++); // sequence number
929 box_gather(moof
, mfhd
);
931 for (unsigned int i_trak
= 0; i_trak
< p_sys
->i_nb_streams
; i_trak
++)
933 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i_trak
];
935 /* *** add /moof/traf *** */
936 bo_t
*traf
= box_new("traf");
939 uint32_t i_sample
= 0;
940 vlc_tick_t i_time
= p_stream
->i_written_duration
;
941 bool b_allsamesize
= true;
942 bool b_allsamelength
= true;
943 if ( p_stream
->read
.p_first
)
945 mp4_fragentry_t
*p_entry
= p_stream
->read
.p_first
->p_next
;
946 while (p_entry
&& (b_allsamelength
|| b_allsamesize
))
948 /* compare against queue head */
949 b_allsamelength
&= ( p_entry
->p_block
->i_length
== p_stream
->read
.p_first
->p_block
->i_length
);
950 b_allsamesize
&= ( p_entry
->p_block
->i_buffer
== p_stream
->read
.p_first
->p_block
->i_buffer
);
951 p_entry
= p_entry
->p_next
;
955 uint32_t i_tfhd_flags
= 0x0;
956 if (p_stream
->read
.p_first
)
958 /* Current segment have all same duration value, different than trex's default */
959 if (b_allsamelength
&&
960 p_stream
->read
.p_first
->p_block
->i_length
!= p_stream
->tinfo
->i_trex_default_length
&&
961 p_stream
->read
.p_first
->p_block
->i_length
)
962 i_tfhd_flags
|= MP4_TFHD_DFLT_SAMPLE_DURATION
;
964 /* Current segment have all same size value, different than trex's default */
966 p_stream
->read
.p_first
->p_block
->i_buffer
!= p_stream
->tinfo
->i_trex_default_size
&&
967 p_stream
->read
.p_first
->p_block
->i_buffer
)
968 i_tfhd_flags
|= MP4_TFHD_DFLT_SAMPLE_SIZE
;
972 /* We have no samples */
973 i_tfhd_flags
|= MP4_TFHD_DURATION_IS_EMPTY
;
976 /* *** add /moof/traf/tfhd *** */
977 bo_t
*tfhd
= box_full_new("tfhd", 0, i_tfhd_flags
);
983 bo_add_32be(tfhd
, p_stream
->tinfo
->i_track_id
);
985 /* set the local sample duration default */
986 if (i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_DURATION
)
987 bo_add_32be(tfhd
, samples_from_vlc_tick(p_stream
->read
.p_first
->p_block
->i_length
, p_stream
->tinfo
->i_timescale
));
989 /* set the local sample size default */
990 if (i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_SIZE
)
991 bo_add_32be(tfhd
, p_stream
->read
.p_first
->p_block
->i_buffer
);
993 box_gather(traf
, tfhd
);
995 /* *** add /moof/traf/tfdt *** */
996 bo_t
*tfdt
= box_full_new("tfdt", 1, 0);
1002 bo_add_64be(tfdt
, samples_from_vlc_tick(p_stream
->i_written_duration
, p_stream
->tinfo
->i_timescale
) );
1003 box_gather(traf
, tfdt
);
1005 /* *** add /moof/traf/trun *** */
1006 if (p_stream
->read
.p_first
)
1008 uint32_t i_trun_flags
= 0x0;
1010 if (p_stream
->b_hasiframes
&& !(p_stream
->read
.p_first
->p_block
->i_flags
& BLOCK_FLAG_TYPE_I
))
1011 i_trun_flags
|= MP4_TRUN_FIRST_FLAGS
;
1013 if (!b_allsamelength
||
1014 ( !(i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_DURATION
) && p_stream
->tinfo
->i_trex_default_length
== 0 ))
1015 i_trun_flags
|= MP4_TRUN_SAMPLE_DURATION
;
1017 if (!b_allsamesize
||
1018 ( !(i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_SIZE
) && p_stream
->tinfo
->i_trex_default_size
== 0 ))
1019 i_trun_flags
|= MP4_TRUN_SAMPLE_SIZE
;
1021 if (p_stream
->tinfo
->b_hasbframes
)
1022 i_trun_flags
|= MP4_TRUN_SAMPLE_TIME_OFFSET
;
1024 if (i_fixupoffset
== 0)
1025 i_trun_flags
|= MP4_TRUN_DATA_OFFSET
;
1027 bo_t
*trun
= box_full_new("trun", 0, i_trun_flags
);
1035 uint32_t i_entry_count
= 0;
1036 vlc_tick_t i_run_time
= p_stream
->i_written_duration
;
1037 mp4_fragentry_t
*p_entry
= p_stream
->read
.p_first
;
1040 if ( i_barrier_time
&& i_run_time
+ p_entry
->p_block
->i_length
> i_barrier_time
)
1043 i_run_time
+= p_entry
->p_block
->i_length
;
1044 p_entry
= p_entry
->p_next
;
1046 bo_add_32be(trun
, i_entry_count
); // sample count
1048 if (i_trun_flags
& MP4_TRUN_DATA_OFFSET
)
1050 i_fixupoffset
= bo_size(moof
) + bo_size(traf
) + bo_size(trun
);
1051 bo_add_32be(trun
, 0xdeadbeef); // data offset
1054 if (i_trun_flags
& MP4_TRUN_FIRST_FLAGS
)
1055 bo_add_32be(trun
, 1<<16); // flag as non keyframe
1057 while(p_stream
->read
.p_first
&& i_entry_count
)
1059 DEQUEUE_ENTRY(p_stream
->read
, p_entry
);
1061 if (i_trun_flags
& MP4_TRUN_SAMPLE_DURATION
)
1062 bo_add_32be(trun
, samples_from_vlc_tick(p_entry
->p_block
->i_length
, p_stream
->tinfo
->i_timescale
)); // sample duration
1064 if (i_trun_flags
& MP4_TRUN_SAMPLE_SIZE
)
1065 bo_add_32be(trun
, p_entry
->p_block
->i_buffer
); // sample size
1067 if (i_trun_flags
& MP4_TRUN_SAMPLE_TIME_OFFSET
)
1069 vlc_tick_t i_diff
= 0;
1070 if ( p_entry
->p_block
->i_dts
!= VLC_TICK_INVALID
&&
1071 p_entry
->p_block
->i_pts
> p_entry
->p_block
->i_dts
)
1073 i_diff
= p_entry
->p_block
->i_pts
- p_entry
->p_block
->i_dts
;
1075 bo_add_32be(trun
, samples_from_vlc_tick(i_diff
, p_stream
->tinfo
->i_timescale
)); // ctts
1078 *pi_mdat_total_size
+= p_entry
->p_block
->i_buffer
;
1080 ENQUEUE_ENTRY(p_stream
->towrite
, p_entry
);
1084 /* Add keyframe entry if needed */
1085 if (p_stream
->b_hasiframes
&& (p_entry
->p_block
->i_flags
& BLOCK_FLAG_TYPE_I
) &&
1086 (p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
|| p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
))
1088 AddKeyframeEntry(p_stream
, i_write_pos
, i_trak
, i_sample
, i_time
);
1091 i_time
+= p_entry
->p_block
->i_length
;
1094 box_gather(traf
, trun
);
1097 box_gather(moof
, traf
);
1106 box_fix(moof
, bo_size(moof
));
1108 /* do tfhd base data offset fixup */
1111 /* mdat will follow moof */
1112 bo_set_32be(moof
, i_fixupoffset
, bo_size(moof
) + 8);
1115 /* set iframe flag, so the streaming server always starts from moof */
1116 moof
->b
->i_flags
|= BLOCK_FLAG_TYPE_I
;
1121 static void WriteFragmentMDAT(sout_mux_t
*p_mux
, size_t i_total_size
)
1123 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
1125 /* Now add mdat header */
1126 bo_t
*mdat
= box_new("mdat");
1129 /* force update of real size */
1130 assert(bo_size(mdat
)==8);
1131 box_fix(mdat
, bo_size(mdat
) + i_total_size
);
1132 p_sys
->i_pos
+= bo_size(mdat
);
1133 /* only write header */
1134 sout_AccessOutWrite(p_mux
->p_access
, mdat
->b
);
1136 /* Header and its size are written and good, now write content */
1137 for (unsigned int i_trak
= 0; i_trak
< p_sys
->i_nb_streams
; i_trak
++)
1139 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i_trak
];
1141 while(p_stream
->towrite
.p_first
)
1143 mp4_fragentry_t
*p_entry
= p_stream
->towrite
.p_first
;
1144 p_sys
->i_pos
+= p_entry
->p_block
->i_buffer
;
1145 p_stream
->i_written_duration
+= p_entry
->p_block
->i_length
;
1147 p_entry
->p_block
->i_flags
&= ~BLOCK_FLAG_TYPE_I
; // clear flag for http stream
1148 sout_AccessOutWrite(p_mux
->p_access
, p_entry
->p_block
);
1150 p_stream
->towrite
.p_first
= p_entry
->p_next
;
1152 if (!p_stream
->towrite
.p_first
)
1153 p_stream
->towrite
.p_last
= NULL
;
1158 static bo_t
*GetMfraBox(sout_mux_t
*p_mux
)
1160 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1162 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1164 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1165 if (p_stream
->i_indexentries
)
1167 bo_t
*tfra
= box_full_new("tfra", 0, 0x0);
1168 if (!tfra
) continue;
1169 bo_add_32be(tfra
, p_stream
->tinfo
->i_track_id
);
1170 bo_add_32be(tfra
, 0x3); // reserved + lengths (1,1,4)=>(0,0,3)
1171 bo_add_32be(tfra
, p_stream
->i_indexentries
);
1172 for(uint32_t i_index
=0; i_index
<p_stream
->i_indexentries
; i_index
++)
1174 const mp4_fragindex_t
*p_indexentry
= &p_stream
->p_indexentries
[i_index
];
1175 bo_add_32be(tfra
, p_indexentry
->i_time
);
1176 bo_add_32be(tfra
, p_indexentry
->i_moofoffset
);
1177 assert(sizeof(p_indexentry
->i_traf
)==1); /* guard against sys changes */
1178 assert(sizeof(p_indexentry
->i_trun
)==1);
1179 assert(sizeof(p_indexentry
->i_sample
)==4);
1180 bo_add_8(tfra
, p_indexentry
->i_traf
);
1181 bo_add_8(tfra
, p_indexentry
->i_trun
);
1182 bo_add_32be(tfra
, p_indexentry
->i_sample
);
1185 if (!mfra
&& !(mfra
= box_new("mfra")))
1191 box_gather(mfra
,tfra
);
1197 static void FlushHeader(sout_mux_t
*p_mux
)
1199 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1201 if(p_sys
->i_pos
>= (((uint64_t)0x1) << 32))
1202 mp4mux_Set64BitExt(p_sys
->muxh
);
1204 /* Now add ftyp header */
1205 bo_t
*ftyp
= mp4mux_GetFtyp(MAJOR_isom
, 0, NULL
, 0);
1209 bo_t
*moov
= mp4mux_GetMoov(p_sys
->muxh
, VLC_OBJECT(p_mux
), 0);
1211 /* merge into a single block */
1212 box_gather(ftyp
, moov
);
1214 /* add header flag for streaming server */
1215 ftyp
->b
->i_flags
|= BLOCK_FLAG_HEADER
;
1216 p_sys
->i_pos
+= bo_size(ftyp
);
1217 box_send(p_mux
, ftyp
);
1218 p_sys
->b_header_sent
= true;
1221 static void WriteFragments(sout_mux_t
*p_mux
, bool b_flush
)
1223 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1225 vlc_tick_t i_barrier_time
= p_sys
->i_written_duration
+ FRAGMENT_LENGTH
;
1226 size_t i_mdat_size
= 0;
1227 bool b_has_samples
= false;
1229 if(!p_sys
->b_header_sent
)
1231 for (unsigned int j
= 0; j
< p_sys
->i_nb_streams
; j
++)
1233 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[j
];
1234 if(CreateCurrentEdit(p_stream
, p_sys
->i_start_dts
, true))
1235 mp4mux_track_DebugEdits(VLC_OBJECT(p_mux
), p_stream
->tinfo
);
1239 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1241 const mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1242 if (p_stream
->read
.p_first
)
1244 b_has_samples
= true;
1246 /* set a barrier so we try to align to keyframe */
1247 if (p_stream
->b_hasiframes
&&
1248 p_stream
->i_last_iframe_time
> p_stream
->i_written_duration
&&
1249 (p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
||
1250 p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
) )
1252 i_barrier_time
= __MIN(i_barrier_time
, p_stream
->i_last_iframe_time
);
1257 if (!p_sys
->b_header_sent
)
1261 moof
= GetMoofBox(p_mux
, &i_mdat_size
, (b_flush
)?0:i_barrier_time
, p_sys
->i_pos
);
1263 if (moof
&& i_mdat_size
== 0)
1265 block_Release(moof
->b
);
1271 msg_Dbg(p_mux
, "writing moof @ %"PRId64
, p_sys
->i_pos
);
1272 p_sys
->i_pos
+= bo_size(moof
);
1273 assert(moof
->b
->i_flags
& BLOCK_FLAG_TYPE_I
); /* http sout */
1274 box_send(p_mux
, moof
);
1275 msg_Dbg(p_mux
, "writing mdat @ %"PRId64
, p_sys
->i_pos
);
1276 WriteFragmentMDAT(p_mux
, i_mdat_size
);
1278 /* update iframe point */
1279 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1281 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1282 p_stream
->i_last_iframe_time
= 0;
1287 /* Do an entry length fixup using only its own info.
1288 * This is the end boundary case. */
1289 static void LengthLocalFixup(sout_mux_t
*p_mux
, const mp4_stream_t
*p_stream
, block_t
*p_entrydata
)
1291 if ( p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
&& p_stream
->tinfo
->fmt
.video
.i_frame_rate
)
1293 p_entrydata
->i_length
= vlc_tick_from_samples(
1294 p_stream
->tinfo
->fmt
.video
.i_frame_rate_base
,
1295 p_stream
->tinfo
->fmt
.video
.i_frame_rate
);
1296 msg_Dbg(p_mux
, "video track %d fixup to %"PRId64
" for sample %u",
1297 p_stream
->tinfo
->i_track_id
, p_entrydata
->i_length
, p_stream
->tinfo
->i_samples_count
- 1);
1299 else if (p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
&&
1300 p_stream
->tinfo
->fmt
.audio
.i_rate
&&
1301 p_entrydata
->i_nb_samples
&& p_stream
->tinfo
->fmt
.audio
.i_rate
)
1303 p_entrydata
->i_length
= vlc_tick_from_samples(p_entrydata
->i_nb_samples
,
1304 p_stream
->tinfo
->fmt
.audio
.i_rate
);
1305 msg_Dbg(p_mux
, "audio track %d fixup to %"PRId64
" for sample %u",
1306 p_stream
->tinfo
->i_track_id
, p_entrydata
->i_length
, p_stream
->tinfo
->i_samples_count
- 1);
1310 msg_Warn(p_mux
, "unknown length for track %d sample %u",
1311 p_stream
->tinfo
->i_track_id
, p_stream
->tinfo
->i_samples_count
- 1);
1312 p_entrydata
->i_length
= 1;
1316 static void CleanupFrag(sout_mux_sys_t
*p_sys
)
1318 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1320 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1321 if (p_stream
->p_held_entry
)
1323 block_Release(p_stream
->p_held_entry
->p_block
);
1324 free(p_stream
->p_held_entry
);
1326 while(p_stream
->read
.p_first
)
1328 mp4_fragentry_t
*p_next
= p_stream
->read
.p_first
->p_next
;
1329 block_Release(p_stream
->read
.p_first
->p_block
);
1330 free(p_stream
->read
.p_first
);
1331 p_stream
->read
.p_first
= p_next
;
1333 while(p_stream
->towrite
.p_first
)
1335 mp4_fragentry_t
*p_next
= p_stream
->towrite
.p_first
->p_next
;
1336 block_Release(p_stream
->towrite
.p_first
->p_block
);
1337 free(p_stream
->towrite
.p_first
);
1338 p_stream
->towrite
.p_first
= p_next
;
1340 free(p_stream
->p_indexentries
);
1343 TAB_CLEAN(p_sys
->i_nb_streams
, p_sys
->pp_streams
);
1344 mp4mux_Delete(p_sys
->muxh
);
1348 static void CloseFrag(vlc_object_t
*p_this
)
1350 sout_mux_t
*p_mux
= (sout_mux_t
*) p_this
;
1351 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1353 /* Flush remaining entries */
1354 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1356 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1357 if (p_stream
->p_held_entry
)
1359 if (p_stream
->p_held_entry
->p_block
->i_length
< 1)
1360 LengthLocalFixup(p_mux
, p_stream
, p_stream
->p_held_entry
->p_block
);
1361 ENQUEUE_ENTRY(p_stream
->read
, p_stream
->p_held_entry
);
1362 p_stream
->p_held_entry
= NULL
;
1366 /* and force creating a fragment from it */
1367 WriteFragments(p_mux
, true);
1369 /* Write indexes, but only for non streamed content
1370 as they refer to moof by absolute position */
1371 if (!strcmp(p_mux
->psz_mux
, "mp4frag"))
1373 bo_t
*mfra
= GetMfraBox(p_mux
);
1376 bo_t
*mfro
= box_full_new("mfro", 0, 0x0);
1381 box_fix(mfra
, bo_size(mfra
));
1382 bo_add_32be(mfro
, bo_size(mfra
) + MP4_MFRO_BOXSIZE
);
1384 box_gather(mfra
, mfro
);
1386 box_send(p_mux
, mfra
);
1393 static int MuxFrag(sout_mux_t
*p_mux
)
1395 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1397 int i_stream
= sout_MuxGetStream(p_mux
, 1, NULL
);
1401 sout_input_t
*p_input
= p_mux
->pp_inputs
[i_stream
];
1402 mp4_stream_t
*p_stream
= (mp4_stream_t
*) p_input
->p_sys
;
1403 block_t
*p_currentblock
= BlockDequeue(p_input
, p_stream
);
1404 if( !p_currentblock
)
1407 /* Set time ranges */
1408 if( p_stream
->i_first_dts
== VLC_TICK_INVALID
)
1410 p_stream
->i_first_dts
= p_currentblock
->i_dts
;
1411 if( p_sys
->i_start_dts
== VLC_TICK_INVALID
)
1412 p_sys
->i_start_dts
= p_currentblock
->i_dts
;
1415 /* If we have a previous entry for outgoing queue */
1416 if (p_stream
->p_held_entry
)
1418 block_t
*p_heldblock
= p_stream
->p_held_entry
->p_block
;
1420 /* Fix previous block length from current */
1421 if (p_heldblock
->i_length
< 1)
1424 /* Fix using dts if not on a boundary */
1425 if ((p_currentblock
->i_flags
& BLOCK_FLAG_DISCONTINUITY
) == 0)
1426 p_heldblock
->i_length
= p_currentblock
->i_dts
- p_heldblock
->i_dts
;
1428 if (p_heldblock
->i_length
< 1)
1429 LengthLocalFixup(p_mux
, p_stream
, p_heldblock
);
1433 ENQUEUE_ENTRY(p_stream
->read
, p_stream
->p_held_entry
);
1434 p_stream
->p_held_entry
= NULL
;
1436 if (p_stream
->b_hasiframes
&& (p_heldblock
->i_flags
& BLOCK_FLAG_TYPE_I
) &&
1437 p_stream
->tinfo
->i_read_duration
- p_sys
->i_written_duration
< FRAGMENT_LENGTH
)
1439 /* Flag the last iframe time, we'll use it as boundary so it will start
1441 p_stream
->i_last_iframe_time
= p_stream
->tinfo
->i_read_duration
;
1444 /* update buffered time */
1445 p_stream
->tinfo
->i_read_duration
+= __MAX(0, p_heldblock
->i_length
);
1449 /* set temp entry */
1450 p_stream
->p_held_entry
= malloc(sizeof(mp4_fragentry_t
));
1451 if (unlikely(!p_stream
->p_held_entry
))
1454 p_stream
->p_held_entry
->p_block
= p_currentblock
;
1455 p_stream
->p_held_entry
->i_run
= p_stream
->i_current_run
;
1456 p_stream
->p_held_entry
->p_next
= NULL
;
1458 if (p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
)
1460 if (!p_stream
->b_hasiframes
&& (p_currentblock
->i_flags
& BLOCK_FLAG_TYPE_I
))
1461 p_stream
->b_hasiframes
= true;
1463 if (!p_stream
->tinfo
->b_hasbframes
&& p_currentblock
->i_dts
!= VLC_TICK_INVALID
&&
1464 p_currentblock
->i_pts
> p_currentblock
->i_dts
)
1465 p_stream
->tinfo
->b_hasbframes
= true;
1468 /* Update the global fragment/media duration */
1469 vlc_tick_t i_min_read_duration
= p_stream
->tinfo
->i_read_duration
;
1470 vlc_tick_t i_min_written_duration
= p_stream
->i_written_duration
;
1471 for (unsigned int i
=0; i
<p_sys
->i_nb_streams
; i
++)
1473 const mp4_stream_t
*p_s
= p_sys
->pp_streams
[i
];
1474 if (p_s
->tinfo
->fmt
.i_cat
!= VIDEO_ES
&& p_s
->tinfo
->fmt
.i_cat
!= AUDIO_ES
)
1476 if (p_s
->tinfo
->i_read_duration
< i_min_read_duration
)
1477 i_min_read_duration
= p_s
->tinfo
->i_read_duration
;
1479 if (p_s
->i_written_duration
< i_min_written_duration
)
1480 i_min_written_duration
= p_s
->i_written_duration
;
1482 p_sys
->i_read_duration
= i_min_read_duration
;
1483 p_sys
->i_written_duration
= i_min_written_duration
;
1485 /* we have prerolled enough to know all streams, and have enough date to create a fragment */
1486 if (p_stream
->read
.p_first
&& p_sys
->i_read_duration
- p_sys
->i_written_duration
>= FRAGMENT_LENGTH
)
1487 WriteFragments(p_mux
, false);