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 bool b_64bitext
= (p_sys
->i_pos
> UINT32_MAX
);
302 mp4mux_Set64BitExt(p_sys
->muxh
);
304 uint64_t i_moov_pos
= p_sys
->i_pos
;
305 bo_t
*moov
= mp4mux_GetMoov(p_sys
->muxh
, VLC_OBJECT(p_mux
), 0);
307 /* Check we need to create "fast start" files */
308 p_sys
->b_fast_start
= var_GetBool(p_this
, SOUT_CFG_PREFIX
"faststart");
309 while (p_sys
->b_fast_start
&& moov
&& moov
->b
)
311 /* Move data to the end of the file so we can fit the moov header
313 uint64_t i_mdatsize
= p_sys
->i_pos
- p_sys
->i_mdat_pos
;
315 /* moving samples will need new moov with 64bit atoms ? */
316 if(!b_64bitext
&& p_sys
->i_pos
+ bo_size(moov
) > UINT32_MAX
)
318 mp4mux_Set64BitExt(p_sys
->muxh
);
320 /* generate a new moov */
321 bo_t
*moov64
= mp4mux_GetMoov(p_sys
->muxh
, VLC_OBJECT(p_mux
), 0);
328 /* We now know our final MOOV size */
330 /* Fix-up samples to chunks table in MOOV header to they point to next MDAT location */
331 mp4mux_ShiftSamples(p_sys
->muxh
, bo_size(moov
));
332 msg_Dbg(p_this
,"Moving data by %"PRIu64
, (uint64_t)bo_size(moov
));
333 bo_t
*shifted
= mp4mux_GetMoov(p_sys
->muxh
, VLC_OBJECT(p_mux
), 0);
337 p_sys
->b_fast_start
= false;
340 assert(bo_size(shifted
) == bo_size(moov
));
344 /* Make space, move MDAT data by moov size towards the end */
345 while (i_mdatsize
> 0)
347 size_t i_chunk
= __MIN(32768, i_mdatsize
);
348 block_t
*p_buf
= block_Alloc(i_chunk
);
349 sout_AccessOutSeek(p_mux
->p_access
,
350 p_sys
->i_mdat_pos
+ i_mdatsize
- i_chunk
);
351 ssize_t i_read
= sout_AccessOutRead(p_mux
->p_access
, p_buf
);
352 if (i_read
< 0 || (size_t) i_read
< i_chunk
) {
353 msg_Warn(p_this
, "read() not supported by access output, "
354 "won't create a fast start file");
355 p_sys
->b_fast_start
= false;
356 block_Release(p_buf
);
359 sout_AccessOutSeek(p_mux
->p_access
, p_sys
->i_mdat_pos
+ i_mdatsize
+
360 bo_size(moov
) - i_chunk
);
361 sout_AccessOutWrite(p_mux
->p_access
, p_buf
);
362 i_mdatsize
-= i_chunk
;
365 if (!p_sys
->b_fast_start
) /* failed above */
368 /* Update pos pointers */
369 i_moov_pos
= p_sys
->i_mdat_pos
;
370 p_sys
->i_mdat_pos
+= bo_size(moov
);
372 p_sys
->b_fast_start
= false;
375 /* Write MOOV header */
376 sout_AccessOutSeek(p_mux
->p_access
, i_moov_pos
);
378 box_send(p_mux
, moov
);
382 for (unsigned int i_trak
= 0; i_trak
< p_sys
->i_nb_streams
; i_trak
++)
383 free(p_sys
->pp_streams
[i_trak
]);
384 TAB_CLEAN(p_sys
->i_nb_streams
, p_sys
->pp_streams
);
385 mp4mux_Delete(p_sys
->muxh
);
389 /*****************************************************************************
391 *****************************************************************************/
392 static int Control(sout_mux_t
*p_mux
, int i_query
, va_list args
)
399 case MUX_CAN_ADD_STREAM_WHILE_MUXING
:
400 pb_bool
= va_arg(args
, bool *);
404 case MUX_GET_ADD_STREAM_WAIT
:
405 pb_bool
= va_arg(args
, bool *);
409 case MUX_GET_MIME
: /* Not needed, as not streamable */
415 /*****************************************************************************
417 *****************************************************************************/
418 static int AddStream(sout_mux_t
*p_mux
, sout_input_t
*p_input
)
420 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
421 mp4_stream_t
*p_stream
;
423 if(!mp4mux_CanMux(VLC_OBJECT(p_mux
), p_input
->p_fmt
,
424 mp4mux_Is(p_sys
->muxh
, QUICKTIME
) ? MAJOR_qt__
: MAJOR_isom
,
425 mp4mux_Is(p_sys
->muxh
, FRAGMENTED
)))
427 msg_Err(p_mux
, "unsupported codec %4.4s in mp4",
428 (char*)&p_input
->p_fmt
->i_codec
);
432 p_stream
= malloc(sizeof(mp4_stream_t
));
436 uint32_t i_track_timescale
= CLOCK_FREQ
;
437 es_format_t trackfmt
;
438 es_format_Init(&trackfmt
, p_input
->p_fmt
->i_cat
, p_input
->p_fmt
->i_codec
);
439 es_format_Copy(&trackfmt
, p_input
->p_fmt
);
441 switch( p_input
->p_fmt
->i_cat
)
444 if(!trackfmt
.audio
.i_rate
)
446 msg_Warn( p_mux
, "no audio rate given for stream %d, assuming 48KHz",
447 p_sys
->i_nb_streams
);
448 trackfmt
.audio
.i_rate
= 48000;
450 i_track_timescale
= trackfmt
.audio
.i_rate
;
453 if( !trackfmt
.video
.i_frame_rate
||
454 !trackfmt
.video
.i_frame_rate_base
)
456 msg_Warn( p_mux
, "Missing frame rate for stream %d, assuming 25fps",
457 p_sys
->i_nb_streams
);
458 trackfmt
.video
.i_frame_rate
= 25;
459 trackfmt
.video
.i_frame_rate_base
= 1;
462 i_track_timescale
= trackfmt
.video
.i_frame_rate
*
463 trackfmt
.video
.i_frame_rate_base
;
465 if( i_track_timescale
> CLOCK_FREQ
)
466 i_track_timescale
= CLOCK_FREQ
;
467 else if( i_track_timescale
< 90000 )
468 i_track_timescale
= 90000;
474 p_stream
->tinfo
= mp4mux_track_Add(p_sys
->muxh
, p_sys
->i_nb_streams
+ 1,
475 &trackfmt
, i_track_timescale
);
476 es_format_Clean(&trackfmt
);
483 p_stream
->i_length_neg
= 0;
484 p_stream
->i_last_dts
= VLC_TICK_INVALID
;
485 p_stream
->i_last_pts
= VLC_TICK_INVALID
;
487 p_stream
->b_hasiframes
= false;
489 p_stream
->i_current_run
= 0;
490 p_stream
->read
.p_first
= NULL
;
491 p_stream
->read
.p_last
= NULL
;
492 p_stream
->towrite
.p_first
= NULL
;
493 p_stream
->towrite
.p_last
= NULL
;
494 p_stream
->p_held_entry
= NULL
;
495 p_stream
->i_last_iframe_time
= 0;
496 p_stream
->i_written_duration
= 0;
497 p_stream
->p_indexentries
= NULL
;
498 p_stream
->i_indexentriesmax
= 0;
499 p_stream
->i_indexentries
= 0;
501 p_input
->p_sys
= p_stream
;
503 msg_Dbg(p_mux
, "adding input");
505 TAB_APPEND(p_sys
->i_nb_streams
, p_sys
->pp_streams
, p_stream
);
509 /*****************************************************************************
511 *****************************************************************************/
512 static void DelStream(sout_mux_t
*p_mux
, sout_input_t
*p_input
)
514 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
515 mp4_stream_t
*p_stream
= (mp4_stream_t
*)p_input
->p_sys
;
517 if(!mp4mux_Is(p_sys
->muxh
, FRAGMENTED
))
519 while(block_FifoCount(p_input
->p_fifo
) > 0 &&
520 MuxStream(p_mux
, p_input
, p_stream
) == VLC_SUCCESS
) {};
522 if(CreateCurrentEdit(p_stream
, p_sys
->i_start_dts
, false))
523 mp4mux_track_DebugEdits(VLC_OBJECT(p_mux
), p_stream
->tinfo
);
526 msg_Dbg(p_mux
, "removing input");
529 /*****************************************************************************
531 *****************************************************************************/
532 static bool CreateCurrentEdit(mp4_stream_t
*p_stream
, vlc_tick_t i_mux_start_dts
,
535 const mp4mux_edit_t
*p_lastedit
= mp4mux_track_GetLastEdit(p_stream
->tinfo
);
537 /* Never more than first empty edit for fragmented */
538 if(p_lastedit
!= NULL
&& b_fragmented
)
541 if(p_stream
->tinfo
->i_samples_count
== 0)
544 mp4mux_edit_t newedit
;
546 if(p_lastedit
== NULL
)
548 newedit
.i_start_time
= 0;
549 newedit
.i_start_offset
= __MAX(0, p_stream
->i_first_dts
- i_mux_start_dts
);
553 newedit
.i_start_time
= __MAX(0, p_lastedit
->i_start_time
+ p_lastedit
->i_duration
);
554 newedit
.i_start_offset
= 0;
559 newedit
.i_duration
= 0;
563 if(p_stream
->i_last_pts
!= VLC_TICK_INVALID
)
564 newedit
.i_duration
= p_stream
->i_last_pts
- p_stream
->i_first_dts
;
566 newedit
.i_duration
= p_stream
->i_last_dts
- p_stream
->i_first_dts
;
567 if(p_stream
->tinfo
->i_samples_count
)
568 newedit
.i_duration
+= p_stream
->tinfo
->samples
[p_stream
->tinfo
->i_samples_count
- 1].i_length
;
571 return mp4mux_track_AddEdit(p_stream
->tinfo
, &newedit
);
574 static block_t
* BlockDequeue(sout_input_t
*p_input
, mp4_stream_t
*p_stream
)
576 block_t
*p_block
= block_FifoGet(p_input
->p_fifo
);
577 if(unlikely(!p_block
))
580 switch(p_stream
->tinfo
->fmt
.i_codec
)
583 p_block
= AV1_Pack_Sample(p_block
);
587 p_block
= hxxx_AnnexB_to_xVC(p_block
, 4);
590 p_block
= ConvertSUBT(p_block
);
594 if (p_stream
->tinfo
->a52_frame
== NULL
&& p_block
->i_buffer
>= 8)
595 p_stream
->tinfo
->a52_frame
= block_Duplicate(p_block
);
604 static inline vlc_tick_t
dts_fb_pts( const block_t
*p_data
)
606 return p_data
->i_dts
!= VLC_TICK_INVALID
? p_data
->i_dts
: p_data
->i_pts
;
609 static int MuxStream(sout_mux_t
*p_mux
, sout_input_t
*p_input
, mp4_stream_t
*p_stream
)
611 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
613 block_t
*p_data
= BlockDequeue(p_input
, p_stream
);
617 /* Reset reference dts in case of discontinuity (ex: gather sout) */
618 if (p_data
->i_flags
& BLOCK_FLAG_DISCONTINUITY
&& p_stream
->tinfo
->i_samples_count
)
620 if(p_stream
->i_first_dts
!= VLC_TICK_INVALID
)
622 if(!CreateCurrentEdit(p_stream
, p_sys
->i_start_dts
,
623 mp4mux_Is(p_sys
->muxh
, FRAGMENTED
)))
625 block_Release( p_data
);
630 p_stream
->i_length_neg
= 0;
631 p_stream
->i_first_dts
= VLC_TICK_INVALID
;
632 p_stream
->i_last_dts
= VLC_TICK_INVALID
;
633 p_stream
->i_last_pts
= VLC_TICK_INVALID
;
636 /* Set current segment ranges */
637 if( p_stream
->i_first_dts
== VLC_TICK_INVALID
)
639 p_stream
->i_first_dts
= dts_fb_pts( p_data
);
640 if( p_sys
->i_start_dts
== VLC_TICK_INVALID
)
641 p_sys
->i_start_dts
= p_stream
->i_first_dts
;
644 if (p_stream
->tinfo
->fmt
.i_cat
!= SPU_ES
)
646 /* Fix length of the sample */
647 if (block_FifoCount(p_input
->p_fifo
) > 0)
649 block_t
*p_next
= block_FifoShow(p_input
->p_fifo
);
650 if ( p_next
->i_flags
& BLOCK_FLAG_DISCONTINUITY
)
651 { /* we have no way to know real length except by decoding */
652 if ( p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
)
654 p_data
->i_length
= vlc_tick_from_samples(
655 p_stream
->tinfo
->fmt
.video
.i_frame_rate_base
,
656 p_stream
->tinfo
->fmt
.video
.i_frame_rate
);
657 if( p_data
->i_flags
& BLOCK_FLAG_SINGLE_FIELD
)
658 p_data
->i_length
>>= 1;
659 msg_Dbg( p_mux
, "video track %u fixup to %"PRId64
" for sample %u",
660 p_stream
->tinfo
->i_track_id
, p_data
->i_length
, p_stream
->tinfo
->i_samples_count
);
662 else if ( p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
&&
663 p_stream
->tinfo
->fmt
.audio
.i_rate
&&
664 p_data
->i_nb_samples
)
666 p_data
->i_length
= vlc_tick_from_samples(p_data
->i_nb_samples
,
667 p_stream
->tinfo
->fmt
.audio
.i_rate
);
668 msg_Dbg( p_mux
, "audio track %u fixup to %"PRId64
" for sample %u",
669 p_stream
->tinfo
->i_track_id
, p_data
->i_length
, p_stream
->tinfo
->i_samples_count
);
671 else if ( p_data
->i_length
<= 0 )
673 msg_Warn( p_mux
, "unknown length for track %u sample %u",
674 p_stream
->tinfo
->i_track_id
, p_stream
->tinfo
->i_samples_count
);
675 p_data
->i_length
= 1;
680 vlc_tick_t i_diff
= dts_fb_pts( p_next
) - dts_fb_pts( p_data
);
681 if (i_diff
< VLC_TICK_FROM_SEC(1)) /* protection */
682 p_data
->i_length
= i_diff
;
685 if (p_data
->i_length
<= 0) {
686 msg_Warn(p_mux
, "i_length <= 0");
687 p_stream
->i_length_neg
+= p_data
->i_length
- 1;
688 p_data
->i_length
= 1;
689 } else if (p_stream
->i_length_neg
< 0) {
690 int64_t i_recover
= __MIN(p_data
->i_length
/ 4, - p_stream
->i_length_neg
);
692 p_data
->i_length
-= i_recover
;
693 p_stream
->i_length_neg
+= i_recover
;
698 mp4mux_sample_t
*p_lastsample
= mp4mux_track_GetLastSample(p_stream
->tinfo
);
699 if (p_lastsample
!= NULL
&& p_lastsample
->i_length
== 0)
701 /* length of previous spu, stored in spu clearer */
702 int64_t i_length
= dts_fb_pts( p_data
) - p_stream
->i_last_dts
;
706 p_lastsample
->i_length
= i_length
;
707 p_stream
->tinfo
->i_read_duration
+= i_length
;
711 /* Update (Not earlier for SPU!) */
712 p_stream
->i_last_dts
= dts_fb_pts( p_data
);
713 if( p_data
->i_pts
> p_stream
->i_last_pts
)
714 p_stream
->i_last_pts
= p_data
->i_pts
;
716 /* add index entry */
717 mp4mux_sample_t sample
;
718 sample
.i_pos
= p_sys
->i_pos
;
719 sample
.i_size
= p_data
->i_buffer
;
721 if ( p_data
->i_dts
!= VLC_TICK_INVALID
&& p_data
->i_pts
> p_data
->i_dts
)
723 sample
.i_pts_dts
= p_data
->i_pts
- p_data
->i_dts
;
724 if ( !p_stream
->tinfo
->b_hasbframes
)
725 p_stream
->tinfo
->b_hasbframes
= true;
727 else sample
.i_pts_dts
= 0;
729 sample
.i_length
= p_data
->i_length
;
730 sample
.i_flags
= p_data
->i_flags
;
733 p_stream
->tinfo
->i_read_duration
+= __MAX( 0, p_data
->i_length
);
734 p_stream
->i_last_dts
= dts_fb_pts( p_data
);
737 if(mp4mux_track_AddSample(p_stream
->tinfo
, &sample
))
739 p_sys
->i_pos
+= p_data
->i_buffer
;
740 sout_AccessOutWrite(p_mux
->p_access
, p_data
);
743 /* Add SPU clearing tag (duration tb fixed on next SPU or stream end )*/
744 if ( p_stream
->tinfo
->fmt
.i_cat
== SPU_ES
&& sample
.i_length
> 0 )
746 block_t
*p_empty
= NULL
;
747 if(p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_SUBT
||
748 p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_QTXT
||
749 p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_TX3G
)
751 p_empty
= block_Alloc(3);
755 p_empty
->p_buffer
[0] = 0;
756 p_empty
->p_buffer
[1] = 1;
757 p_empty
->p_buffer
[2] = ' ';
760 else if(p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_TTML
)
762 p_empty
= block_Alloc(40);
764 memcpy(p_empty
->p_buffer
, "<tt><body><div><p></p></div></body></tt>", 40);
766 else if(p_stream
->tinfo
->fmt
.i_codec
== VLC_CODEC_WEBVTT
)
768 p_empty
= block_Alloc(8);
770 memcpy(p_empty
->p_buffer
, "\x00\x00\x00\x08vtte", 8);
773 /* point to start of our empty */
774 p_stream
->i_last_dts
+= sample
.i_length
;
778 /* Append a idx entry */
779 /* XXX: No need to grow the entry here */
780 mp4mux_sample_t closersample
;
781 closersample
.i_pos
= p_sys
->i_pos
;
782 closersample
.i_size
= p_empty
->i_buffer
;
783 closersample
.i_pts_dts
= 0;
784 closersample
.i_length
= 0; /* will add dts diff later*/
785 closersample
.i_flags
= 0;
787 if(mp4mux_track_AddSample(p_stream
->tinfo
, &closersample
))
789 p_sys
->i_pos
+= p_empty
->i_buffer
;
790 sout_AccessOutWrite(p_mux
->p_access
, p_empty
);
795 /* Update the global segment/media duration */
796 if( p_stream
->tinfo
->i_read_duration
> p_sys
->i_read_duration
)
797 p_sys
->i_read_duration
= p_stream
->tinfo
->i_read_duration
;
802 static int Mux(sout_mux_t
*p_mux
)
804 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
805 int i_ret
= VLC_SUCCESS
;
807 if(!p_sys
->b_header_sent
)
809 i_ret
= WriteSlowStartHeader(p_mux
);
810 if(i_ret
!= VLC_SUCCESS
)
812 p_sys
->b_header_sent
= true;
817 int i_stream
= sout_MuxGetStream(p_mux
, 2, NULL
);
821 sout_input_t
*p_input
= p_mux
->pp_inputs
[i_stream
];
822 mp4_stream_t
*p_stream
= (mp4_stream_t
*)p_input
->p_sys
;
824 i_ret
= MuxStream(p_mux
, p_input
, p_stream
);
825 } while( i_ret
== VLC_SUCCESS
);
830 /*****************************************************************************
832 *****************************************************************************/
833 static block_t
*ConvertSUBT(block_t
*p_block
)
835 p_block
= block_Realloc(p_block
, 2, p_block
->i_buffer
);
838 /* No trailling '\0' */
839 if (p_block
->i_buffer
> 2 && p_block
->p_buffer
[p_block
->i_buffer
-1] == '\0')
842 p_block
->p_buffer
[0] = ((p_block
->i_buffer
- 2) >> 8)&0xff;
843 p_block
->p_buffer
[1] = ((p_block
->i_buffer
- 2) )&0xff;
848 static void box_send(sout_mux_t
*p_mux
, bo_t
*box
)
852 sout_AccessOutWrite(p_mux
->p_access
, box
->b
);
856 /***************************************************************************
858 ****************************************************************************/
859 #define FRAGMENT_LENGTH VLC_TICK_FROM_MS(1500)
861 #define ENQUEUE_ENTRY(object, entry) \
864 object.p_last->p_next = entry;\
865 object.p_last = entry;\
866 if (!object.p_first)\
867 object.p_first = entry;\
870 #define DEQUEUE_ENTRY(object, entry) \
872 entry = object.p_first;\
873 if (object.p_last == entry)\
874 object.p_last = NULL;\
875 object.p_first = object.p_first->p_next;\
876 entry->p_next = NULL;\
879 /* Creates mfra/traf index entries */
880 static void AddKeyframeEntry(mp4_stream_t
*p_stream
, const uint64_t i_moof_pos
,
881 const uint8_t i_traf
, const uint32_t i_sample
,
882 const vlc_tick_t i_time
)
884 /* alloc or realloc */
885 mp4_fragindex_t
*p_entries
= p_stream
->p_indexentries
;
886 if (p_stream
->i_indexentries
>= p_stream
->i_indexentriesmax
)
888 p_stream
->i_indexentriesmax
+= 256;
889 p_entries
= xrealloc(p_stream
->p_indexentries
,
890 p_stream
->i_indexentriesmax
* sizeof(mp4_fragindex_t
));
891 if (p_entries
) /* realloc can fail */
892 p_stream
->p_indexentries
= p_entries
;
895 vlc_tick_t i_last_entry_time
;
896 if (p_stream
->i_indexentries
)
897 i_last_entry_time
= p_stream
->p_indexentries
[p_stream
->i_indexentries
- 1].i_time
;
899 i_last_entry_time
= 0;
901 if (p_entries
&& i_time
- i_last_entry_time
>= VLC_TICK_FROM_SEC(2))
903 mp4_fragindex_t
*p_indexentry
= &p_stream
->p_indexentries
[p_stream
->i_indexentries
];
904 p_indexentry
->i_time
= i_time
;
905 p_indexentry
->i_moofoffset
= i_moof_pos
;
906 p_indexentry
->i_sample
= i_sample
;
907 p_indexentry
->i_traf
= i_traf
;
908 p_indexentry
->i_trun
= 1;
909 p_stream
->i_indexentries
++;
913 /* Creates moof box and traf/trun information.
914 * Single run per traf is absolutely not optimal as interleaving should be done
915 * using runs and not limiting moof size, but creating an relative offset only
916 * requires base_offset_is_moof and then comply to late iso brand spec which
918 static bo_t
*GetMoofBox(sout_mux_t
*p_mux
, size_t *pi_mdat_total_size
,
919 vlc_tick_t i_barrier_time
, const uint64_t i_write_pos
)
921 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
924 size_t i_fixupoffset
= 0;
926 *pi_mdat_total_size
= 0;
928 moof
= box_new("moof");
932 /* *** add /moof/mfhd *** */
934 mfhd
= box_full_new("mfhd", 0, 0);
940 bo_add_32be(mfhd
, p_sys
->i_mfhd_sequence
++); // sequence number
942 box_gather(moof
, mfhd
);
944 for (unsigned int i_trak
= 0; i_trak
< p_sys
->i_nb_streams
; i_trak
++)
946 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i_trak
];
948 /* *** add /moof/traf *** */
949 bo_t
*traf
= box_new("traf");
952 uint32_t i_sample
= 0;
953 vlc_tick_t i_time
= p_stream
->i_written_duration
;
954 bool b_allsamesize
= true;
955 bool b_allsamelength
= true;
956 if ( p_stream
->read
.p_first
)
958 mp4_fragentry_t
*p_entry
= p_stream
->read
.p_first
->p_next
;
959 while (p_entry
&& (b_allsamelength
|| b_allsamesize
))
961 /* compare against queue head */
962 b_allsamelength
&= ( p_entry
->p_block
->i_length
== p_stream
->read
.p_first
->p_block
->i_length
);
963 b_allsamesize
&= ( p_entry
->p_block
->i_buffer
== p_stream
->read
.p_first
->p_block
->i_buffer
);
964 p_entry
= p_entry
->p_next
;
968 uint32_t i_tfhd_flags
= 0x0;
969 if (p_stream
->read
.p_first
)
971 /* Current segment have all same duration value, different than trex's default */
972 if (b_allsamelength
&&
973 p_stream
->read
.p_first
->p_block
->i_length
!= p_stream
->tinfo
->i_trex_default_length
&&
974 p_stream
->read
.p_first
->p_block
->i_length
)
975 i_tfhd_flags
|= MP4_TFHD_DFLT_SAMPLE_DURATION
;
977 /* Current segment have all same size value, different than trex's default */
979 p_stream
->read
.p_first
->p_block
->i_buffer
!= p_stream
->tinfo
->i_trex_default_size
&&
980 p_stream
->read
.p_first
->p_block
->i_buffer
)
981 i_tfhd_flags
|= MP4_TFHD_DFLT_SAMPLE_SIZE
;
985 /* We have no samples */
986 i_tfhd_flags
|= MP4_TFHD_DURATION_IS_EMPTY
;
989 /* *** add /moof/traf/tfhd *** */
990 bo_t
*tfhd
= box_full_new("tfhd", 0, i_tfhd_flags
);
996 bo_add_32be(tfhd
, p_stream
->tinfo
->i_track_id
);
998 /* set the local sample duration default */
999 if (i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_DURATION
)
1000 bo_add_32be(tfhd
, samples_from_vlc_tick(p_stream
->read
.p_first
->p_block
->i_length
, p_stream
->tinfo
->i_timescale
));
1002 /* set the local sample size default */
1003 if (i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_SIZE
)
1004 bo_add_32be(tfhd
, p_stream
->read
.p_first
->p_block
->i_buffer
);
1006 box_gather(traf
, tfhd
);
1008 /* *** add /moof/traf/tfdt *** */
1009 bo_t
*tfdt
= box_full_new("tfdt", 1, 0);
1015 bo_add_64be(tfdt
, samples_from_vlc_tick(p_stream
->i_written_duration
, p_stream
->tinfo
->i_timescale
) );
1016 box_gather(traf
, tfdt
);
1018 /* *** add /moof/traf/trun *** */
1019 if (p_stream
->read
.p_first
)
1021 uint32_t i_trun_flags
= 0x0;
1023 if (p_stream
->b_hasiframes
&& !(p_stream
->read
.p_first
->p_block
->i_flags
& BLOCK_FLAG_TYPE_I
))
1024 i_trun_flags
|= MP4_TRUN_FIRST_FLAGS
;
1026 if (!b_allsamelength
||
1027 ( !(i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_DURATION
) && p_stream
->tinfo
->i_trex_default_length
== 0 ))
1028 i_trun_flags
|= MP4_TRUN_SAMPLE_DURATION
;
1030 if (!b_allsamesize
||
1031 ( !(i_tfhd_flags
& MP4_TFHD_DFLT_SAMPLE_SIZE
) && p_stream
->tinfo
->i_trex_default_size
== 0 ))
1032 i_trun_flags
|= MP4_TRUN_SAMPLE_SIZE
;
1034 if (p_stream
->tinfo
->b_hasbframes
)
1035 i_trun_flags
|= MP4_TRUN_SAMPLE_TIME_OFFSET
;
1037 if (i_fixupoffset
== 0)
1038 i_trun_flags
|= MP4_TRUN_DATA_OFFSET
;
1040 bo_t
*trun
= box_full_new("trun", 0, i_trun_flags
);
1048 uint32_t i_entry_count
= 0;
1049 vlc_tick_t i_run_time
= p_stream
->i_written_duration
;
1050 mp4_fragentry_t
*p_entry
= p_stream
->read
.p_first
;
1053 if ( i_barrier_time
&& i_run_time
+ p_entry
->p_block
->i_length
> i_barrier_time
)
1056 i_run_time
+= p_entry
->p_block
->i_length
;
1057 p_entry
= p_entry
->p_next
;
1059 bo_add_32be(trun
, i_entry_count
); // sample count
1061 if (i_trun_flags
& MP4_TRUN_DATA_OFFSET
)
1063 i_fixupoffset
= bo_size(moof
) + bo_size(traf
) + bo_size(trun
);
1064 bo_add_32be(trun
, 0xdeadbeef); // data offset
1067 if (i_trun_flags
& MP4_TRUN_FIRST_FLAGS
)
1068 bo_add_32be(trun
, 1<<16); // flag as non keyframe
1070 while(p_stream
->read
.p_first
&& i_entry_count
)
1072 DEQUEUE_ENTRY(p_stream
->read
, p_entry
);
1074 if (i_trun_flags
& MP4_TRUN_SAMPLE_DURATION
)
1075 bo_add_32be(trun
, samples_from_vlc_tick(p_entry
->p_block
->i_length
, p_stream
->tinfo
->i_timescale
)); // sample duration
1077 if (i_trun_flags
& MP4_TRUN_SAMPLE_SIZE
)
1078 bo_add_32be(trun
, p_entry
->p_block
->i_buffer
); // sample size
1080 if (i_trun_flags
& MP4_TRUN_SAMPLE_TIME_OFFSET
)
1082 vlc_tick_t i_diff
= 0;
1083 if ( p_entry
->p_block
->i_dts
!= VLC_TICK_INVALID
&&
1084 p_entry
->p_block
->i_pts
> p_entry
->p_block
->i_dts
)
1086 i_diff
= p_entry
->p_block
->i_pts
- p_entry
->p_block
->i_dts
;
1088 bo_add_32be(trun
, samples_from_vlc_tick(i_diff
, p_stream
->tinfo
->i_timescale
)); // ctts
1091 *pi_mdat_total_size
+= p_entry
->p_block
->i_buffer
;
1093 ENQUEUE_ENTRY(p_stream
->towrite
, p_entry
);
1097 /* Add keyframe entry if needed */
1098 if (p_stream
->b_hasiframes
&& (p_entry
->p_block
->i_flags
& BLOCK_FLAG_TYPE_I
) &&
1099 (p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
|| p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
))
1101 AddKeyframeEntry(p_stream
, i_write_pos
, i_trak
, i_sample
, i_time
);
1104 i_time
+= p_entry
->p_block
->i_length
;
1107 box_gather(traf
, trun
);
1110 box_gather(moof
, traf
);
1119 box_fix(moof
, bo_size(moof
));
1121 /* do tfhd base data offset fixup */
1124 /* mdat will follow moof */
1125 bo_set_32be(moof
, i_fixupoffset
, bo_size(moof
) + 8);
1128 /* set iframe flag, so the streaming server always starts from moof */
1129 moof
->b
->i_flags
|= BLOCK_FLAG_TYPE_I
;
1134 static void WriteFragmentMDAT(sout_mux_t
*p_mux
, size_t i_total_size
)
1136 sout_mux_sys_t
*p_sys
= p_mux
->p_sys
;
1138 /* Now add mdat header */
1139 bo_t
*mdat
= box_new("mdat");
1142 /* force update of real size */
1143 assert(bo_size(mdat
)==8);
1144 box_fix(mdat
, bo_size(mdat
) + i_total_size
);
1145 p_sys
->i_pos
+= bo_size(mdat
);
1146 /* only write header */
1147 sout_AccessOutWrite(p_mux
->p_access
, mdat
->b
);
1149 /* Header and its size are written and good, now write content */
1150 for (unsigned int i_trak
= 0; i_trak
< p_sys
->i_nb_streams
; i_trak
++)
1152 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i_trak
];
1154 while(p_stream
->towrite
.p_first
)
1156 mp4_fragentry_t
*p_entry
= p_stream
->towrite
.p_first
;
1157 p_sys
->i_pos
+= p_entry
->p_block
->i_buffer
;
1158 p_stream
->i_written_duration
+= p_entry
->p_block
->i_length
;
1160 p_entry
->p_block
->i_flags
&= ~BLOCK_FLAG_TYPE_I
; // clear flag for http stream
1161 sout_AccessOutWrite(p_mux
->p_access
, p_entry
->p_block
);
1163 p_stream
->towrite
.p_first
= p_entry
->p_next
;
1165 if (!p_stream
->towrite
.p_first
)
1166 p_stream
->towrite
.p_last
= NULL
;
1171 static bo_t
*GetMfraBox(sout_mux_t
*p_mux
)
1173 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1175 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1177 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1178 if (p_stream
->i_indexentries
)
1180 bo_t
*tfra
= box_full_new("tfra", 0, 0x0);
1181 if (!tfra
) continue;
1182 bo_add_32be(tfra
, p_stream
->tinfo
->i_track_id
);
1183 bo_add_32be(tfra
, 0x3); // reserved + lengths (1,1,4)=>(0,0,3)
1184 bo_add_32be(tfra
, p_stream
->i_indexentries
);
1185 for(uint32_t i_index
=0; i_index
<p_stream
->i_indexentries
; i_index
++)
1187 const mp4_fragindex_t
*p_indexentry
= &p_stream
->p_indexentries
[i_index
];
1188 bo_add_32be(tfra
, p_indexentry
->i_time
);
1189 bo_add_32be(tfra
, p_indexentry
->i_moofoffset
);
1190 assert(sizeof(p_indexentry
->i_traf
)==1); /* guard against sys changes */
1191 assert(sizeof(p_indexentry
->i_trun
)==1);
1192 assert(sizeof(p_indexentry
->i_sample
)==4);
1193 bo_add_8(tfra
, p_indexentry
->i_traf
);
1194 bo_add_8(tfra
, p_indexentry
->i_trun
);
1195 bo_add_32be(tfra
, p_indexentry
->i_sample
);
1198 if (!mfra
&& !(mfra
= box_new("mfra")))
1204 box_gather(mfra
,tfra
);
1210 static void FlushHeader(sout_mux_t
*p_mux
)
1212 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1214 if(p_sys
->i_pos
>= (((uint64_t)0x1) << 32))
1215 mp4mux_Set64BitExt(p_sys
->muxh
);
1217 /* Now add ftyp header */
1218 bo_t
*ftyp
= mp4mux_GetFtyp(MAJOR_isom
, 0, NULL
, 0);
1222 bo_t
*moov
= mp4mux_GetMoov(p_sys
->muxh
, VLC_OBJECT(p_mux
), 0);
1224 /* merge into a single block */
1225 box_gather(ftyp
, moov
);
1227 /* add header flag for streaming server */
1228 ftyp
->b
->i_flags
|= BLOCK_FLAG_HEADER
;
1229 p_sys
->i_pos
+= bo_size(ftyp
);
1230 box_send(p_mux
, ftyp
);
1231 p_sys
->b_header_sent
= true;
1234 static void WriteFragments(sout_mux_t
*p_mux
, bool b_flush
)
1236 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1238 vlc_tick_t i_barrier_time
= p_sys
->i_written_duration
+ FRAGMENT_LENGTH
;
1239 size_t i_mdat_size
= 0;
1240 bool b_has_samples
= false;
1242 if(!p_sys
->b_header_sent
)
1244 for (unsigned int j
= 0; j
< p_sys
->i_nb_streams
; j
++)
1246 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[j
];
1247 if(CreateCurrentEdit(p_stream
, p_sys
->i_start_dts
, true))
1248 mp4mux_track_DebugEdits(VLC_OBJECT(p_mux
), p_stream
->tinfo
);
1252 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1254 const mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1255 if (p_stream
->read
.p_first
)
1257 b_has_samples
= true;
1259 /* set a barrier so we try to align to keyframe */
1260 if (p_stream
->b_hasiframes
&&
1261 p_stream
->i_last_iframe_time
> p_stream
->i_written_duration
&&
1262 (p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
||
1263 p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
) )
1265 i_barrier_time
= __MIN(i_barrier_time
, p_stream
->i_last_iframe_time
);
1270 if (!p_sys
->b_header_sent
)
1274 moof
= GetMoofBox(p_mux
, &i_mdat_size
, (b_flush
)?0:i_barrier_time
, p_sys
->i_pos
);
1276 if (moof
&& i_mdat_size
== 0)
1278 block_Release(moof
->b
);
1284 msg_Dbg(p_mux
, "writing moof @ %"PRId64
, p_sys
->i_pos
);
1285 p_sys
->i_pos
+= bo_size(moof
);
1286 assert(moof
->b
->i_flags
& BLOCK_FLAG_TYPE_I
); /* http sout */
1287 box_send(p_mux
, moof
);
1288 msg_Dbg(p_mux
, "writing mdat @ %"PRId64
, p_sys
->i_pos
);
1289 WriteFragmentMDAT(p_mux
, i_mdat_size
);
1291 /* update iframe point */
1292 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1294 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1295 p_stream
->i_last_iframe_time
= 0;
1300 /* Do an entry length fixup using only its own info.
1301 * This is the end boundary case. */
1302 static void LengthLocalFixup(sout_mux_t
*p_mux
, const mp4_stream_t
*p_stream
, block_t
*p_entrydata
)
1304 if ( p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
&& p_stream
->tinfo
->fmt
.video
.i_frame_rate
)
1306 p_entrydata
->i_length
= vlc_tick_from_samples(
1307 p_stream
->tinfo
->fmt
.video
.i_frame_rate_base
,
1308 p_stream
->tinfo
->fmt
.video
.i_frame_rate
);
1309 msg_Dbg(p_mux
, "video track %d fixup to %"PRId64
" for sample %u",
1310 p_stream
->tinfo
->i_track_id
, p_entrydata
->i_length
, p_stream
->tinfo
->i_samples_count
- 1);
1312 else if (p_stream
->tinfo
->fmt
.i_cat
== AUDIO_ES
&&
1313 p_stream
->tinfo
->fmt
.audio
.i_rate
&&
1314 p_entrydata
->i_nb_samples
&& p_stream
->tinfo
->fmt
.audio
.i_rate
)
1316 p_entrydata
->i_length
= vlc_tick_from_samples(p_entrydata
->i_nb_samples
,
1317 p_stream
->tinfo
->fmt
.audio
.i_rate
);
1318 msg_Dbg(p_mux
, "audio track %d fixup to %"PRId64
" for sample %u",
1319 p_stream
->tinfo
->i_track_id
, p_entrydata
->i_length
, p_stream
->tinfo
->i_samples_count
- 1);
1323 msg_Warn(p_mux
, "unknown length for track %d sample %u",
1324 p_stream
->tinfo
->i_track_id
, p_stream
->tinfo
->i_samples_count
- 1);
1325 p_entrydata
->i_length
= 1;
1329 static void CleanupFrag(sout_mux_sys_t
*p_sys
)
1331 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1333 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1334 if (p_stream
->p_held_entry
)
1336 block_Release(p_stream
->p_held_entry
->p_block
);
1337 free(p_stream
->p_held_entry
);
1339 while(p_stream
->read
.p_first
)
1341 mp4_fragentry_t
*p_next
= p_stream
->read
.p_first
->p_next
;
1342 block_Release(p_stream
->read
.p_first
->p_block
);
1343 free(p_stream
->read
.p_first
);
1344 p_stream
->read
.p_first
= p_next
;
1346 while(p_stream
->towrite
.p_first
)
1348 mp4_fragentry_t
*p_next
= p_stream
->towrite
.p_first
->p_next
;
1349 block_Release(p_stream
->towrite
.p_first
->p_block
);
1350 free(p_stream
->towrite
.p_first
);
1351 p_stream
->towrite
.p_first
= p_next
;
1353 free(p_stream
->p_indexentries
);
1356 TAB_CLEAN(p_sys
->i_nb_streams
, p_sys
->pp_streams
);
1357 mp4mux_Delete(p_sys
->muxh
);
1361 static void CloseFrag(vlc_object_t
*p_this
)
1363 sout_mux_t
*p_mux
= (sout_mux_t
*) p_this
;
1364 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1366 /* Flush remaining entries */
1367 for (unsigned int i
= 0; i
< p_sys
->i_nb_streams
; i
++)
1369 mp4_stream_t
*p_stream
= p_sys
->pp_streams
[i
];
1370 if (p_stream
->p_held_entry
)
1372 if (p_stream
->p_held_entry
->p_block
->i_length
< 1)
1373 LengthLocalFixup(p_mux
, p_stream
, p_stream
->p_held_entry
->p_block
);
1374 ENQUEUE_ENTRY(p_stream
->read
, p_stream
->p_held_entry
);
1375 p_stream
->p_held_entry
= NULL
;
1379 /* and force creating a fragment from it */
1380 WriteFragments(p_mux
, true);
1382 /* Write indexes, but only for non streamed content
1383 as they refer to moof by absolute position */
1384 if (!strcmp(p_mux
->psz_mux
, "mp4frag"))
1386 bo_t
*mfra
= GetMfraBox(p_mux
);
1389 bo_t
*mfro
= box_full_new("mfro", 0, 0x0);
1394 box_fix(mfra
, bo_size(mfra
));
1395 bo_add_32be(mfro
, bo_size(mfra
) + MP4_MFRO_BOXSIZE
);
1397 box_gather(mfra
, mfro
);
1399 box_send(p_mux
, mfra
);
1406 static int MuxFrag(sout_mux_t
*p_mux
)
1408 sout_mux_sys_t
*p_sys
= (sout_mux_sys_t
*) p_mux
->p_sys
;
1410 int i_stream
= sout_MuxGetStream(p_mux
, 1, NULL
);
1414 sout_input_t
*p_input
= p_mux
->pp_inputs
[i_stream
];
1415 mp4_stream_t
*p_stream
= (mp4_stream_t
*) p_input
->p_sys
;
1416 block_t
*p_currentblock
= BlockDequeue(p_input
, p_stream
);
1417 if( !p_currentblock
)
1420 /* Set time ranges */
1421 if( p_stream
->i_first_dts
== VLC_TICK_INVALID
)
1423 p_stream
->i_first_dts
= p_currentblock
->i_dts
;
1424 if( p_sys
->i_start_dts
== VLC_TICK_INVALID
)
1425 p_sys
->i_start_dts
= p_currentblock
->i_dts
;
1428 /* If we have a previous entry for outgoing queue */
1429 if (p_stream
->p_held_entry
)
1431 block_t
*p_heldblock
= p_stream
->p_held_entry
->p_block
;
1433 /* Fix previous block length from current */
1434 if (p_heldblock
->i_length
< 1)
1437 /* Fix using dts if not on a boundary */
1438 if ((p_currentblock
->i_flags
& BLOCK_FLAG_DISCONTINUITY
) == 0)
1439 p_heldblock
->i_length
= p_currentblock
->i_dts
- p_heldblock
->i_dts
;
1441 if (p_heldblock
->i_length
< 1)
1442 LengthLocalFixup(p_mux
, p_stream
, p_heldblock
);
1446 ENQUEUE_ENTRY(p_stream
->read
, p_stream
->p_held_entry
);
1447 p_stream
->p_held_entry
= NULL
;
1449 if (p_stream
->b_hasiframes
&& (p_heldblock
->i_flags
& BLOCK_FLAG_TYPE_I
) &&
1450 p_stream
->tinfo
->i_read_duration
- p_sys
->i_written_duration
< FRAGMENT_LENGTH
)
1452 /* Flag the last iframe time, we'll use it as boundary so it will start
1454 p_stream
->i_last_iframe_time
= p_stream
->tinfo
->i_read_duration
;
1457 /* update buffered time */
1458 p_stream
->tinfo
->i_read_duration
+= __MAX(0, p_heldblock
->i_length
);
1462 /* set temp entry */
1463 p_stream
->p_held_entry
= malloc(sizeof(mp4_fragentry_t
));
1464 if (unlikely(!p_stream
->p_held_entry
))
1467 p_stream
->p_held_entry
->p_block
= p_currentblock
;
1468 p_stream
->p_held_entry
->i_run
= p_stream
->i_current_run
;
1469 p_stream
->p_held_entry
->p_next
= NULL
;
1471 if (p_stream
->tinfo
->fmt
.i_cat
== VIDEO_ES
)
1473 if (!p_stream
->b_hasiframes
&& (p_currentblock
->i_flags
& BLOCK_FLAG_TYPE_I
))
1474 p_stream
->b_hasiframes
= true;
1476 if (!p_stream
->tinfo
->b_hasbframes
&& p_currentblock
->i_dts
!= VLC_TICK_INVALID
&&
1477 p_currentblock
->i_pts
> p_currentblock
->i_dts
)
1478 p_stream
->tinfo
->b_hasbframes
= true;
1481 /* Update the global fragment/media duration */
1482 vlc_tick_t i_min_read_duration
= p_stream
->tinfo
->i_read_duration
;
1483 vlc_tick_t i_min_written_duration
= p_stream
->i_written_duration
;
1484 for (unsigned int i
=0; i
<p_sys
->i_nb_streams
; i
++)
1486 const mp4_stream_t
*p_s
= p_sys
->pp_streams
[i
];
1487 if (p_s
->tinfo
->fmt
.i_cat
!= VIDEO_ES
&& p_s
->tinfo
->fmt
.i_cat
!= AUDIO_ES
)
1489 if (p_s
->tinfo
->i_read_duration
< i_min_read_duration
)
1490 i_min_read_duration
= p_s
->tinfo
->i_read_duration
;
1492 if (p_s
->i_written_duration
< i_min_written_duration
)
1493 i_min_written_duration
= p_s
->i_written_duration
;
1495 p_sys
->i_read_duration
= i_min_read_duration
;
1496 p_sys
->i_written_duration
= i_min_written_duration
;
1498 /* we have prerolled enough to know all streams, and have enough date to create a fragment */
1499 if (p_stream
->read
.p_first
&& p_sys
->i_read_duration
- p_sys
->i_written_duration
>= FRAGMENT_LENGTH
)
1500 WriteFragments(p_mux
, false);