1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2011-2017 L-SMASH project
6 * Authors: Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 *****************************************************************************/
21 /* This file is available under an ISC license. */
23 #include "common/internal.h" /* must be placed first */
27 #include "box_default.h"
32 static isom_sidx_t
*isom_get_sidx( lsmash_file_t
*file
, uint32_t reference_ID
)
34 if( reference_ID
== 0 || !file
)
35 return isom_non_existing_sidx();
36 for( lsmash_entry_t
*entry
= file
->sidx_list
.head
; entry
; entry
= entry
->next
)
38 isom_sidx_t
*sidx
= (isom_sidx_t
*)entry
->data
;
39 if( LSMASH_IS_NON_EXISTING_BOX( sidx
) )
40 return isom_non_existing_sidx();
41 if( sidx
->reference_ID
== reference_ID
)
44 return isom_non_existing_sidx();
47 static int isom_finish_fragment_movie( lsmash_file_t
*file
);
49 /* A movie fragment cannot switch a sample description to another.
50 * So you must call this function before switching sample descriptions. */
51 int lsmash_create_fragment_movie( lsmash_root_t
*root
)
53 if( isom_check_initializer_present( root
) < 0 )
54 return LSMASH_ERR_FUNCTION_PARAM
;
55 lsmash_file_t
*file
= root
->file
;
58 return LSMASH_ERR_NAMELESS
;
59 /* Finish and write the current movie fragment before starting a new one. */
60 int ret
= isom_finish_fragment_movie( file
);
63 /* Add a new movie fragment if the current one is not present or not written. */
64 isom_moof_t
*moof
= file
->fragment
->movie
;
65 if( LSMASH_IS_NON_EXISTING_BOX( moof
) || (moof
->manager
& LSMASH_WRITTEN_BOX
) )
67 /* We always hold only one movie fragment except for the initial movie (a pair of moov and mdat). */
68 if( LSMASH_IS_EXISTING_BOX( moof
)
69 && file
->moof_list
.entry_count
!= 1 )
70 return LSMASH_ERR_NAMELESS
;
71 moof
= isom_add_moof( file
);
72 if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mfhd( moof
) ) )
73 return LSMASH_ERR_NAMELESS
;
74 file
->fragment
->movie
= moof
;
75 moof
->mfhd
->sequence_number
= ++ file
->fragment_count
;
76 if( file
->moof_list
.entry_count
== 1 )
78 /* Remove the previous movie fragment. */
79 if( file
->moof_list
.head
)
80 isom_remove_box_by_itself( file
->moof_list
.head
->data
);
85 static inline uint64_t isom_fragment_get_implicit_segment_duration
90 return cache
->fragment
->largest_cts
!= LSMASH_TIMESTAMP_UNDEFINED
91 ? cache
->fragment
->largest_cts
+ cache
->fragment
->last_duration
95 static int isom_set_fragment_overall_duration( lsmash_file_t
*file
)
97 assert( file
== file
->initializer
);
98 /* Get the longest duration of the tracks. */
99 uint64_t longest_duration
= 0;
100 for( lsmash_entry_t
*entry
= file
->moov
->trak_list
.head
; entry
; entry
= entry
->next
)
102 isom_trak_t
*trak
= (isom_trak_t
*)entry
->data
;
103 if( LSMASH_IS_NON_EXISTING_BOX( trak
)
104 || LSMASH_IS_NON_EXISTING_BOX( trak
->mdia
->mdhd
)
106 || !trak
->cache
->fragment
107 || trak
->mdia
->mdhd
->timescale
== 0 )
108 return LSMASH_ERR_NAMELESS
;
110 uint64_t implicit_segment_duration
= isom_fragment_get_implicit_segment_duration( trak
->cache
);
111 if( !trak
->edts
->elst
->list
)
112 duration
= (uint64_t)(((double)implicit_segment_duration
/ trak
->mdia
->mdhd
->timescale
) * file
->moov
->mvhd
->timescale
);
116 for( lsmash_entry_t
*elst_entry
= trak
->edts
->elst
->list
->head
; elst_entry
; elst_entry
= elst_entry
->next
)
118 isom_elst_entry_t
*data
= (isom_elst_entry_t
*)elst_entry
->data
;
120 return LSMASH_ERR_NAMELESS
;
121 if( data
->segment_duration
== ISOM_EDIT_DURATION_IMPLICIT
)
122 duration
+= (uint64_t)(((double)implicit_segment_duration
/ trak
->mdia
->mdhd
->timescale
) * file
->moov
->mvhd
->timescale
);
124 duration
+= data
->segment_duration
;
127 longest_duration
= LSMASH_MAX( duration
, longest_duration
);
129 isom_mehd_t
*mehd
= file
->moov
->mvex
->mehd
;
130 mehd
->fragment_duration
= longest_duration
;
132 mehd
->manager
&= ~(LSMASH_PLACEHOLDER
| LSMASH_WRITTEN_BOX
); /* Update per media segment. */
133 isom_update_box_size( mehd
);
134 /* Write Movie Extends Header Box here. */
135 lsmash_bs_t
*bs
= file
->bs
;
136 uint64_t current_pos
= bs
->offset
;
137 lsmash_bs_write_seek( bs
, mehd
->pos
, SEEK_SET
);
138 int ret
= isom_write_box( bs
, (isom_box_t
*)mehd
);
139 lsmash_bs_write_seek( bs
, current_pos
, SEEK_SET
);
143 static int isom_write_fragment_random_access_info( lsmash_file_t
*file
)
145 assert( file
== file
->initializer
);
146 if( LSMASH_IS_NON_EXISTING_BOX( file
->mfra
) )
148 if( LSMASH_IS_NON_EXISTING_BOX( file
->moov
->mvex
) )
149 return LSMASH_ERR_NAMELESS
;
150 /* Reconstruct the Movie Fragment Random Access Box.
151 * All 'time' field in the Track Fragment Random Access Boxes shall reflect edit list. */
152 uint32_t movie_timescale
= lsmash_get_movie_timescale( file
->root
);
153 if( movie_timescale
== 0 )
154 return LSMASH_ERR_NAMELESS
; /* Division by zero will occur. */
155 for( lsmash_entry_t
*trex_entry
= file
->moov
->mvex
->trex_list
.head
; trex_entry
; trex_entry
= trex_entry
->next
)
157 isom_trex_t
*trex
= (isom_trex_t
*)trex_entry
->data
;
158 if( LSMASH_IS_NON_EXISTING_BOX( trex
) )
159 return LSMASH_ERR_NAMELESS
;
160 /* Get the edit list of the track associated with the trex->track_ID.
161 * If failed or absent, implicit timeline mapping edit is used, and skip this operation for the track. */
162 isom_trak_t
*trak
= isom_get_trak( file
, trex
->track_ID
);
163 if( LSMASH_IS_NON_EXISTING_BOX( trak
) )
164 return LSMASH_ERR_NAMELESS
;
165 if( !trak
->edts
->elst
->list
166 || !trak
->edts
->elst
->list
->head
167 || !trak
->edts
->elst
->list
->head
->data
)
169 isom_elst_t
*elst
= trak
->edts
->elst
;
170 /* Get the Track Fragment Random Access Boxes of the track associated with the trex->track_ID.
171 * If failed or absent, skip reconstructing the Track Fragment Random Access Box of the track. */
172 isom_tfra_t
*tfra
= isom_get_tfra( file
->mfra
, trex
->track_ID
);
173 if( LSMASH_IS_NON_EXISTING_BOX( tfra
) )
175 /* Reconstruct the Track Fragment Random Access Box. */
176 lsmash_entry_t
*edit_entry
= elst
->list
->head
;
177 isom_elst_entry_t
*edit
= edit_entry
->data
;
178 uint64_t edit_offset
= 0; /* units in media timescale */
179 uint32_t media_timescale
= lsmash_get_media_timescale( file
->root
, trex
->track_ID
);
180 for( lsmash_entry_t
*rap_entry
= tfra
->list
->head
; rap_entry
; )
182 isom_tfra_location_time_entry_t
*rap
= (isom_tfra_location_time_entry_t
*)rap_entry
->data
;
185 /* Irregular case. Drop this entry. */
186 lsmash_entry_t
*next
= rap_entry
->next
;
187 lsmash_list_remove_entry_direct( tfra
->list
, rap_entry
);
191 uint64_t composition_time
= rap
->time
;
192 uint64_t implicit_segment_duration
= isom_fragment_get_implicit_segment_duration( trak
->cache
);
193 /* Skip edits that doesn't need the current sync sample indicated in the Track Fragment Random Access Box. */
196 uint64_t segment_duration
= edit
->segment_duration
== ISOM_EDIT_DURATION_IMPLICIT
197 ? implicit_segment_duration
198 : ((edit
->segment_duration
- 1) / movie_timescale
+ 1) * media_timescale
;
199 if( edit
->media_time
!= ISOM_EDIT_MODE_EMPTY
200 && composition_time
< edit
->media_time
+ segment_duration
)
201 break; /* This Timeline Mapping Edit might require the current sync sample.
202 * Note: this condition doesn't cover all cases.
203 * For instance, matching the both following conditions
204 * 1. A sync sample isn't in the presentation.
205 * 2. The other samples, which precede it in the composition timeline, is in the presentation. */
206 edit_offset
+= segment_duration
;
207 edit_entry
= edit_entry
->next
;
210 /* No more presentation. */
214 edit
= edit_entry
->data
;
218 /* No more presentation.
219 * Drop the rest of sync samples since they are generally absent in the whole presentation.
220 * Though the exceptions are sync samples with earlier composition time, we ignore them. (SAP type 2: TEPT = TDEC = TSAP < TPTF)
221 * To support this exception, we need sorting entries of the list by composition times. */
224 lsmash_entry_t
*next
= rap_entry
->next
;
225 lsmash_list_remove_entry_direct( tfra
->list
, rap_entry
);
230 /* If the sync sample isn't in the presentation,
231 * we pick the earliest presentation time of the current edit as its presentation time. */
232 rap
->time
= edit_offset
;
233 if( composition_time
>= edit
->media_time
)
234 rap
->time
+= composition_time
- edit
->media_time
;
235 rap_entry
= rap_entry
->next
;
237 tfra
->number_of_entry
= tfra
->list
->entry_count
;
239 /* Decide the size of the Movie Fragment Random Access Box. */
240 if( isom_update_box_size( file
->mfra
) == 0 )
241 return LSMASH_ERR_NAMELESS
;
242 /* Write the Movie Fragment Random Access Box. */
243 return isom_write_box( file
->bs
, (isom_box_t
*)file
->mfra
);
246 static int isom_update_indexed_material_offset
249 isom_sidx_t
*last_sidx
252 /* Update the size of each Segment Index Box. */
253 for( lsmash_entry_t
*entry
= file
->sidx_list
.head
; entry
; entry
= entry
->next
)
255 isom_sidx_t
*sidx
= (isom_sidx_t
*)entry
->data
;
256 if( LSMASH_IS_NON_EXISTING_BOX( sidx
) )
258 if( isom_update_box_size( sidx
) == 0 )
259 return LSMASH_ERR_NAMELESS
;
261 /* first_offset: the sum of the size of subsequent Segment Index Boxes
262 * Be careful about changing the size of them. */
263 last_sidx
->first_offset
= 0;
264 for( lsmash_entry_t
*a_entry
= file
->sidx_list
.head
; a_entry
&& a_entry
->data
!= last_sidx
; a_entry
= a_entry
->next
)
266 isom_sidx_t
*a
= (isom_sidx_t
*)a_entry
->data
;
268 for( lsmash_entry_t
*b_entry
= a_entry
->next
; b_entry
; b_entry
= b_entry
->next
)
270 isom_sidx_t
*b
= (isom_sidx_t
*)b_entry
->data
;
271 a
->first_offset
+= b
->size
;
277 static int isom_write_segment_indexes
280 lsmash_adhoc_remux_t
*remux
283 /* Update the size of each Segment Index Box and establish the offset from the anchor point to the indexed material. */
285 if( (ret
= isom_update_indexed_material_offset( file
, (isom_sidx_t
*)file
->sidx_list
.tail
->data
)) < 0 )
287 /* Get the total size of all Segment Index Boxes. */
288 uint64_t total_sidx_size
= 0;
289 for( lsmash_entry_t
*entry
= file
->sidx_list
.head
; entry
; entry
= entry
->next
)
291 isom_sidx_t
*sidx
= (isom_sidx_t
*)entry
->data
;
292 if( LSMASH_IS_NON_EXISTING_BOX( sidx
) )
294 total_sidx_size
+= sidx
->size
;
296 /* The buffer size must be at least total_sidx_size * 2. */
297 size_t buffer_size
= total_sidx_size
* 2;
298 if( remux
->buffer_size
> buffer_size
)
299 buffer_size
= remux
->buffer_size
;
300 /* Split to 2 buffers. */
301 uint8_t *buf
[2] = { NULL
, NULL
};
302 if( (buf
[0] = (uint8_t *)lsmash_malloc( buffer_size
)) == NULL
)
303 return LSMASH_ERR_MEMORY_ALLOC
;
304 size_t size
= buffer_size
/ 2;
305 buf
[1] = buf
[0] + size
;
306 /* Seek to the beginning of the first Movie Fragment Box i.e. the first subsegment within this media segment. */
307 lsmash_bs_t
*bs
= file
->bs
;
309 if( (ret64
= lsmash_bs_write_seek( bs
, file
->fragment
->first_moof_pos
, SEEK_SET
)) < 0 )
314 size_t read_num
= size
;
315 lsmash_bs_read_data( bs
, buf
[0], &read_num
);
316 uint64_t read_pos
= bs
->offset
;
317 /* Write the Segment Index Boxes actually here. */
318 if( (ret64
= lsmash_bs_write_seek( bs
, file
->fragment
->first_moof_pos
, SEEK_SET
)) < 0 )
323 for( lsmash_entry_t
*entry
= file
->sidx_list
.head
; entry
; entry
= entry
->next
)
325 isom_sidx_t
*sidx
= (isom_sidx_t
*)entry
->data
;
326 if( LSMASH_IS_NON_EXISTING_BOX( sidx
) )
328 if( (ret
= isom_write_box( file
->bs
, (isom_box_t
*)sidx
)) < 0 )
331 /* Rearrange subsequent data. */
332 uint64_t write_pos
= bs
->offset
;
333 uint64_t total
= file
->size
+ total_sidx_size
;
334 if( (ret
= isom_rearrange_data( file
, remux
, buf
, read_num
, size
, read_pos
, write_pos
, total
)) < 0 )
336 file
->size
+= total_sidx_size
;
337 lsmash_freep( &buf
[0] );
338 /* Update 'moof_offset' of each entry within the Track Fragment Random Access Boxes. */
340 for( lsmash_entry_t
*entry
= file
->mfra
->tfra_list
.head
; entry
; entry
= entry
->next
)
342 isom_tfra_t
*tfra
= (isom_tfra_t
*)entry
->data
;
343 if( LSMASH_IS_NON_EXISTING_BOX( tfra
) )
345 for( lsmash_entry_t
*rap_entry
= tfra
->list
->head
; rap_entry
; rap_entry
= rap_entry
->next
)
347 isom_tfra_location_time_entry_t
*rap
= (isom_tfra_location_time_entry_t
*)rap_entry
->data
;
350 rap
->moof_offset
+= total_sidx_size
;
355 lsmash_free( buf
[0] );
359 int isom_finish_final_fragment_movie
362 lsmash_adhoc_remux_t
*remux
365 /* Output the final movie fragment. */
367 if( (ret
= isom_finish_fragment_movie( file
)) < 0 )
369 if( file
->bs
->unseekable
)
371 /* Write Segment Index Boxes.
372 * This occurs only when the initial movie has no samples.
373 * We don't consider updating of chunk offsets within initial movie sample table here.
374 * This is reasonable since DASH requires no samples in the initial movie.
375 * This implementation is not suitable for live-streaming.
376 + To support live-streaming, it is good to use daisy-chained index. */
377 if( (file
->flags
& LSMASH_FILE_MODE_MEDIA
)
378 && (file
->flags
& LSMASH_FILE_MODE_INDEX
)
379 && (file
->flags
& LSMASH_FILE_MODE_SEGMENT
) )
382 return LSMASH_ERR_FUNCTION_PARAM
;
383 if( (ret
= isom_write_segment_indexes( file
, remux
)) < 0 )
386 /* Write the overall random access information at the tail of the movie if this file is self-contained. */
387 if( (ret
= isom_write_fragment_random_access_info( file
->initializer
)) < 0 )
389 /* Set overall duration of the movie. */
390 return isom_set_fragment_overall_duration( file
->initializer
);
393 #define GET_MOST_USED( box_name, index, flag_name ) \
394 if( most_used[index] < stats.flag_name[i] ) \
396 most_used[index] = stats.flag_name[i]; \
397 box_name->default_sample_flags.flag_name = i; \
400 static int isom_create_fragment_overall_default_settings( lsmash_file_t
*file
)
402 assert( file
== file
->initializer
);
403 if( !isom_add_mvex( file
->moov
) )
404 return LSMASH_ERR_NAMELESS
;
405 if( !file
->bs
->unseekable
)
407 if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mehd( file
->moov
->mvex
) ) )
408 return LSMASH_ERR_NAMELESS
;
409 file
->moov
->mvex
->mehd
->manager
|= LSMASH_PLACEHOLDER
;
411 for( lsmash_entry_t
*trak_entry
= file
->moov
->trak_list
.head
; trak_entry
; trak_entry
= trak_entry
->next
)
413 isom_trak_t
*trak
= (isom_trak_t
*)trak_entry
->data
;
414 if( LSMASH_IS_NON_EXISTING_BOX( trak
)
415 || LSMASH_IS_NON_EXISTING_BOX( trak
->tkhd
)
417 return LSMASH_ERR_NAMELESS
;
418 isom_stbl_t
*stbl
= trak
->mdia
->minf
->stbl
;
419 if( !stbl
->stts
->list
420 || (stbl
->stts
->list
->tail
&& !stbl
->stts
->list
->tail
->data
)
421 || (LSMASH_IS_NON_EXISTING_BOX( stbl
->stsz
) && LSMASH_IS_NON_EXISTING_BOX( stbl
->stz2
))
422 || (LSMASH_IS_EXISTING_BOX( stbl
->stsz
) && stbl
->stsz
->list
&& stbl
->stsz
->list
->head
&& !stbl
->stsz
->list
->head
->data
)
423 || (LSMASH_IS_EXISTING_BOX( stbl
->stz2
) && stbl
->stz2
->list
&& stbl
->stz2
->list
->head
&& !stbl
->stz2
->list
->head
->data
))
424 return LSMASH_ERR_NAMELESS
;
425 isom_trex_t
*trex
= isom_add_trex( file
->moov
->mvex
);
426 if( LSMASH_IS_NON_EXISTING_BOX( trex
) )
427 return LSMASH_ERR_NAMELESS
;
428 trex
->track_ID
= trak
->tkhd
->track_ID
;
429 /* Set up defaults. */
430 trex
->default_sample_description_index
= trak
->cache
->chunk
.sample_description_index
431 ? trak
->cache
->chunk
.sample_description_index
433 trex
->default_sample_duration
= stbl
->stts
->list
->tail
434 ? ((isom_stts_entry_t
*)stbl
->stts
->list
->tail
->data
)->sample_delta
436 trex
->default_sample_size
= isom_get_first_sample_size( stbl
);
437 if( stbl
->sdtp
->list
)
439 struct sample_flags_stats_t
441 uint32_t is_leading
[4];
442 uint32_t sample_depends_on
[4];
443 uint32_t sample_is_depended_on
[4];
444 uint32_t sample_has_redundancy
[4];
445 } stats
= { { 0 }, { 0 }, { 0 }, { 0 } };
446 for( lsmash_entry_t
*sdtp_entry
= stbl
->sdtp
->list
->head
; sdtp_entry
; sdtp_entry
= sdtp_entry
->next
)
448 isom_sdtp_entry_t
*data
= (isom_sdtp_entry_t
*)sdtp_entry
->data
;
450 return LSMASH_ERR_NAMELESS
;
451 ++ stats
.is_leading
[ data
->is_leading
];
452 ++ stats
.sample_depends_on
[ data
->sample_depends_on
];
453 ++ stats
.sample_is_depended_on
[ data
->sample_is_depended_on
];
454 ++ stats
.sample_has_redundancy
[ data
->sample_has_redundancy
];
456 uint32_t most_used
[4] = { 0, 0, 0, 0 };
457 for( int i
= 0; i
< 4; i
++ )
459 GET_MOST_USED( trex
, 0, is_leading
);
460 GET_MOST_USED( trex
, 1, sample_depends_on
);
461 GET_MOST_USED( trex
, 2, sample_is_depended_on
);
462 GET_MOST_USED( trex
, 3, sample_has_redundancy
);
465 trex
->default_sample_flags
.sample_is_non_sync_sample
= !trak
->cache
->all_sync
;
470 static int isom_prepare_random_access_info( lsmash_file_t
*file
)
472 assert( file
== file
->initializer
);
473 /* Don't write the random access info at the end of the file if unseekable or not self-contained. */
474 if( file
->bs
->unseekable
475 || !(file
->flags
& LSMASH_FILE_MODE_BOX
)
476 || !(file
->flags
& LSMASH_FILE_MODE_INITIALIZATION
)
477 || !(file
->flags
& LSMASH_FILE_MODE_MEDIA
)
478 || (file
->flags
& LSMASH_FILE_MODE_SEGMENT
) )
480 if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mfra( file
) )
481 || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mfro( file
->mfra
) ) )
482 return LSMASH_ERR_NAMELESS
;
486 static int isom_output_fragment_media_data( lsmash_file_t
*file
)
488 isom_fragment_manager_t
*frag_manager
= file
->fragment
;
489 /* If there is no available Media Data Box to write samples, add and write a new one. */
490 if( frag_manager
->sample_count
)
492 if( LSMASH_IS_NON_EXISTING_BOX( file
->mdat
) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mdat( file
) ) )
493 return LSMASH_ERR_NAMELESS
;
494 file
->mdat
->manager
&= ~(LSMASH_INCOMPLETE_BOX
| LSMASH_WRITTEN_BOX
);
495 int ret
= isom_write_box( file
->bs
, (isom_box_t
*)file
->mdat
);
498 file
->size
+= file
->mdat
->size
;
499 file
->mdat
->size
= 0;
500 file
->mdat
->media_size
= 0;
502 lsmash_list_remove_entries( frag_manager
->pool
);
503 frag_manager
->pool_size
= 0;
504 frag_manager
->sample_count
= 0;
508 static inline void isom_fragment_reset_sample_counts
513 cache
->fragment
->sample_count
= 0;
514 cache
->fragment
->output_sample_count
= 0;
517 static int isom_finish_fragment_initial_movie( lsmash_file_t
*file
)
519 assert( file
== file
->initializer
);
521 return LSMASH_ERR_NAMELESS
;
522 isom_moov_t
*moov
= file
->moov
;
524 for( lsmash_entry_t
*entry
= moov
->trak_list
.head
; entry
; entry
= entry
->next
)
526 isom_trak_t
*trak
= (isom_trak_t
*)entry
->data
;
527 if( LSMASH_IS_NON_EXISTING_BOX( trak
)
528 || LSMASH_IS_NON_EXISTING_BOX( trak
->tkhd
)
529 || LSMASH_IS_NON_EXISTING_BOX( trak
->mdia
->mdhd
)
530 || LSMASH_IS_NON_EXISTING_BOX( trak
->mdia
->minf
->stbl
)
532 return LSMASH_ERR_NAMELESS
;
533 if( (ret
= isom_complement_data_reference( trak
->mdia
->minf
)) < 0 )
535 isom_stbl_t
*stbl
= trak
->mdia
->minf
->stbl
;
536 if( isom_get_sample_count( trak
) )
538 /* Compress sample size table. */
539 if( stbl
->compress_sample_size_table
540 && (ret
= stbl
->compress_sample_size_table( stbl
)) < 0 )
542 /* Add stss box if any samples aren't sync sample. */
543 if( !trak
->cache
->all_sync
544 && LSMASH_IS_NON_EXISTING_BOX( stbl
->stss
)
545 && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stss( stbl
) ) )
546 return LSMASH_ERR_NAMELESS
;
547 if( (ret
= isom_update_tkhd_duration( trak
)) < 0 )
551 trak
->tkhd
->duration
= 0;
552 if( (ret
= isom_update_bitrate_description( trak
->mdia
)) < 0 )
554 /* Complete the last sample groups within tracks in the initial movie. */
555 if( trak
->cache
->rap
)
557 isom_sgpd_t
*sgpd
= isom_get_sample_group_description( stbl
, ISOM_GROUP_TYPE_RAP
);
558 if( LSMASH_IS_NON_EXISTING_BOX( sgpd
) )
559 return LSMASH_ERR_NAMELESS
;
560 if( (ret
= isom_rap_grouping_established( trak
->cache
->rap
, 1, sgpd
, 0 )) < 0 )
562 lsmash_freep( &trak
->cache
->rap
);
564 if( trak
->cache
->roll
.pool
)
566 isom_sbgp_t
*sbgp
= isom_get_roll_recovery_sample_to_group( &stbl
->sbgp_list
);
567 if( LSMASH_IS_NON_EXISTING_BOX( sbgp
) )
568 return LSMASH_ERR_NAMELESS
;
569 if( (ret
= isom_all_recovery_completed( sbgp
, trak
->cache
->roll
.pool
)) < 0 )
573 if( file
->mp4_version1
== 1 && (ret
= isom_setup_iods( moov
)) < 0 )
575 if( (ret
= isom_create_fragment_overall_default_settings( file
)) < 0
576 || (ret
= isom_prepare_random_access_info ( file
)) < 0
577 || (ret
= isom_establish_movie ( file
)) < 0 )
579 /* stco->co64 conversion, depending on last chunk's offset */
580 uint64_t meta_size
= LSMASH_IS_EXISTING_BOX( file
->meta
) ? file
->meta
->size
: 0;
581 if( (ret
= isom_check_large_offset_requirement( moov
, meta_size
)) < 0 )
583 /* Now, the amount of the offset is fixed. apply it to stco/co64 */
584 uint64_t preceding_size
= moov
->size
+ meta_size
;
585 isom_add_preceding_box_size( moov
, preceding_size
);
586 /* Write File Type Box here if it was not written yet. */
587 if( LSMASH_IS_EXISTING_BOX( file
->ftyp
) && !(file
->ftyp
->manager
& LSMASH_WRITTEN_BOX
) )
589 if( (ret
= isom_write_box( file
->bs
, (isom_box_t
*)file
->ftyp
)) < 0 )
591 file
->size
+= file
->ftyp
->size
;
593 /* Write Movie Box. */
594 if( (ret
= isom_write_box( file
->bs
, (isom_box_t
*)file
->moov
)) < 0
595 || (ret
= isom_write_box( file
->bs
, (isom_box_t
*)file
->meta
)) < 0 )
597 file
->size
+= preceding_size
;
598 /* Output samples. */
599 if( (ret
= isom_output_fragment_media_data( file
)) < 0 )
601 /* Revert the number of samples in tracks to 0. */
602 for( lsmash_entry_t
*entry
= moov
->trak_list
.head
; entry
; entry
= entry
->next
)
604 isom_trak_t
*trak
= (isom_trak_t
*)entry
->data
;
605 assert( LSMASH_IS_EXISTING_BOX( trak
) );
606 if( trak
->cache
->fragment
)
607 isom_fragment_reset_sample_counts( trak
->cache
);
612 /* Return 1 if there is diffrence, otherwise return 0. */
613 static int isom_compare_sample_flags( isom_sample_flags_t
*a
, isom_sample_flags_t
*b
)
615 return (a
->reserved
!= b
->reserved
)
616 || (a
->is_leading
!= b
->is_leading
)
617 || (a
->sample_depends_on
!= b
->sample_depends_on
)
618 || (a
->sample_is_depended_on
!= b
->sample_is_depended_on
)
619 || (a
->sample_has_redundancy
!= b
->sample_has_redundancy
)
620 || (a
->sample_padding_value
!= b
->sample_padding_value
)
621 || (a
->sample_is_non_sync_sample
!= b
->sample_is_non_sync_sample
)
622 || (a
->sample_degradation_priority
!= b
->sample_degradation_priority
);
625 static int isom_make_segment_index_entry
631 /* Make the index of this subsegment. */
632 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
634 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
635 isom_tfhd_t
*tfhd
= traf
->tfhd
;
636 isom_fragment_t
*track_fragment
= traf
->cache
->fragment
;
637 isom_subsegment_t
*subsegment
= &track_fragment
->subsegment
;
638 isom_sidx_t
*sidx
= isom_get_sidx( file
, tfhd
->track_ID
);
639 isom_trak_t
*trak
= isom_get_trak( file
->initializer
, tfhd
->track_ID
);
640 if( LSMASH_IS_NON_EXISTING_BOX( trak
->mdia
->mdhd
) )
641 return LSMASH_ERR_NAMELESS
;
642 assert( LSMASH_IS_EXISTING_BOX( traf
->tfdt
) );
643 if( LSMASH_IS_NON_EXISTING_BOX( sidx
) )
645 sidx
= isom_add_sidx( file
);
646 if( LSMASH_IS_NON_EXISTING_BOX( sidx
) )
647 return LSMASH_ERR_NAMELESS
;
648 sidx
->reference_ID
= tfhd
->track_ID
;
649 sidx
->timescale
= trak
->mdia
->mdhd
->timescale
;
651 sidx
->reference_count
= 0;
652 int ret
= isom_update_indexed_material_offset( file
, sidx
);
656 /* One pair of a Movie Fragment Box with an associated Media Box per subsegment. */
657 isom_sidx_referenced_item_t
*data
= lsmash_malloc( sizeof(isom_sidx_referenced_item_t
) );
659 return LSMASH_ERR_NAMELESS
;
660 if( lsmash_list_add_entry( sidx
->list
, data
) < 0 )
663 return LSMASH_ERR_MEMORY_ALLOC
;
665 sidx
->reference_count
= sidx
->list
->entry_count
;
666 data
->reference_type
= 0; /* media */
667 data
->reference_size
= file
->size
- moof
->pos
;
673 uint64_t composition_duration
= subsegment
->largest_cts
- subsegment
->smallest_cts
;
674 if( subsegment
->smallest_cts
!= LSMASH_TIMESTAMP_UNDEFINED
675 && subsegment
->largest_cts
!= LSMASH_TIMESTAMP_UNDEFINED
)
676 composition_duration
+= track_fragment
->last_duration
;
677 if( trak
->edts
->elst
->list
)
679 /**-- Explicit edits --**/
680 const isom_elst_t
*elst
= trak
->edts
->elst
;
681 const isom_elst_entry_t
*edit
= NULL
;
682 uint32_t movie_timescale
= file
->initializer
->moov
->mvhd
->timescale
;
683 uint64_t pts
= subsegment
->segment_duration
;
684 int subsegment_in_presentation
= 0; /* If set to 1, TEPT is available. */
685 int first_rp_in_presentation
= 0; /* If set to 1, both TSAP and TDEC are available. */
686 int first_sample_in_presentation
= 0; /* If set to 1, TPTF is available. */
687 TSAP
= LSMASH_TIMESTAMP_UNDEFINED
;
688 TDEC
= LSMASH_TIMESTAMP_UNDEFINED
;
689 TEPT
= LSMASH_TIMESTAMP_UNDEFINED
;
690 TPTF
= LSMASH_TIMESTAMP_UNDEFINED
;
692 for( lsmash_entry_t
*elst_entry
= elst
->list
->head
; elst_entry
; elst_entry
= elst_entry
->next
)
694 edit
= (isom_elst_entry_t
*)elst_entry
->data
;
697 uint64_t edit_end_pts
;
698 uint64_t edit_end_cts
;
699 if( edit
->segment_duration
== ISOM_EDIT_DURATION_IMPLICIT
700 || (elst
->version
== 0 && edit
->segment_duration
== ISOM_EDIT_DURATION_UNKNOWN32
)
701 || (elst
->version
== 1 && edit
->segment_duration
== ISOM_EDIT_DURATION_UNKNOWN64
) )
703 edit_end_cts
= UINT64_MAX
;
704 edit_end_pts
= UINT64_MAX
;
708 double segment_duration
= edit
->segment_duration
* ((double)sidx
->timescale
/ movie_timescale
);
709 edit_end_cts
= edit
->media_time
+ (uint64_t)(segment_duration
* ((double)edit
->media_rate
/ (1 << 16)));
710 edit_end_pts
= pts
+ (uint64_t)segment_duration
;
712 if( edit
->media_time
== ISOM_EDIT_MODE_EMPTY
)
717 if( subsegment
->smallest_cts
!= LSMASH_TIMESTAMP_UNDEFINED
718 && subsegment
->largest_cts
!= LSMASH_TIMESTAMP_UNDEFINED
719 && ((subsegment
->smallest_cts
>= edit
->media_time
&& subsegment
->smallest_cts
< edit_end_cts
)
720 || (subsegment
->largest_cts
>= edit
->media_time
&& subsegment
->largest_cts
< edit_end_cts
)) )
722 /* This subsegment is present in this edit. */
723 double rate
= (double)edit
->media_rate
/ (1 << 16);
724 uint64_t start_time
= LSMASH_MAX( subsegment
->smallest_cts
, edit
->media_time
);
725 if( sidx
->reference_count
== 1 )
726 sidx
->earliest_presentation_time
= pts
;
727 if( subsegment_in_presentation
== 0 )
729 subsegment_in_presentation
= 1;
730 if( subsegment
->smallest_cts
>= edit
->media_time
)
731 TEPT
= pts
+ (uint64_t)((subsegment
->smallest_cts
- start_time
) / rate
);
735 if( first_rp_in_presentation
== 0
736 && subsegment
->first_ed_cts
!= LSMASH_TIMESTAMP_UNDEFINED
737 && subsegment
->first_rp_cts
!= LSMASH_TIMESTAMP_UNDEFINED
738 && ((subsegment
->first_ed_cts
>= edit
->media_time
&& subsegment
->first_ed_cts
< edit_end_cts
)
739 || (subsegment
->first_rp_cts
>= edit
->media_time
&& subsegment
->first_rp_cts
< edit_end_cts
)) )
741 /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */
742 first_rp_in_presentation
= 1;
743 if( subsegment
->first_ed_cts
>= edit
->media_time
&& subsegment
->first_ed_cts
< edit_end_cts
)
744 TSAP
= pts
+ (uint64_t)((subsegment
->first_ed_cts
- start_time
) / rate
);
749 if( first_sample_in_presentation
== 0
750 && subsegment
->first_sample_cts
!= LSMASH_TIMESTAMP_UNDEFINED
751 && subsegment
->first_sample_cts
>= edit
->media_time
&& subsegment
->first_sample_cts
< edit_end_cts
)
753 first_sample_in_presentation
= 1;
754 TPTF
= pts
+ (uint64_t)((subsegment
->first_sample_cts
- start_time
) / rate
);
756 uint64_t subsegment_end_pts
= pts
+ (uint64_t)(composition_duration
/ rate
);
757 pts
= LSMASH_MIN( edit_end_pts
, subsegment_end_pts
);
758 /* Update subsegment_duration. */
759 data
->subsegment_duration
= pts
- subsegment
->segment_duration
;
762 /* This subsegment is not present in this edit. */
768 /**-- Implicit edit --**/
769 if( sidx
->reference_count
== 1 )
770 sidx
->earliest_presentation_time
= subsegment
->smallest_cts
;
771 data
->subsegment_duration
= composition_duration
;
772 /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */
773 TSAP
= subsegment
->first_rp_cts
;
774 TDEC
= subsegment
->first_rp_cts
;
775 TEPT
= subsegment
->smallest_cts
;
776 TPTF
= subsegment
->first_sample_cts
;
778 /* Decide SAP_type. */
779 data
->starts_with_SAP
= (subsegment
->first_ra_number
== 1);
781 data
->SAP_delta_time
= 0;
782 if( TSAP
!= LSMASH_TIMESTAMP_UNDEFINED
783 && TDEC
!= LSMASH_TIMESTAMP_UNDEFINED
784 && TEPT
!= LSMASH_TIMESTAMP_UNDEFINED
)
786 if( TPTF
!= LSMASH_TIMESTAMP_UNDEFINED
)
788 if( TEPT
== TDEC
&& TDEC
== TSAP
&& TSAP
== TPTF
)
790 else if( TEPT
== TDEC
&& TDEC
== TSAP
&& TSAP
< TPTF
)
792 else if( TEPT
< TDEC
&& TDEC
== TSAP
&& TSAP
<= TPTF
)
794 else if( TEPT
<= TPTF
&& TPTF
< TDEC
&& TDEC
== TSAP
)
797 if( data
->SAP_type
== 0 )
799 if( TEPT
== TDEC
&& TDEC
< TSAP
)
801 else if( TEPT
< TDEC
&& TDEC
< TSAP
)
804 if( data
->SAP_type
!= 0 )
805 data
->SAP_delta_time
= TSAP
- TEPT
;
807 /* Prepare for the next subsegment. */
808 subsegment
->segment_duration
+= data
->subsegment_duration
;
809 subsegment
->largest_cts
= LSMASH_TIMESTAMP_UNDEFINED
;
810 subsegment
->smallest_cts
= LSMASH_TIMESTAMP_UNDEFINED
;
811 subsegment
->first_sample_cts
= LSMASH_TIMESTAMP_UNDEFINED
;
812 subsegment
->first_ed_cts
= LSMASH_TIMESTAMP_UNDEFINED
;
813 subsegment
->first_rp_cts
= LSMASH_TIMESTAMP_UNDEFINED
;
814 subsegment
->first_rp_number
= 0;
815 subsegment
->first_ra_number
= 0;
816 subsegment
->first_ra_flags
= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE
;
817 subsegment
->decodable
= 0;
822 static int isom_finish_fragment_movie
828 || !file
->fragment
->pool
)
829 return LSMASH_ERR_NAMELESS
;
830 isom_moof_t
*moof
= file
->fragment
->movie
;
831 if( LSMASH_IS_NON_EXISTING_BOX( moof
) )
833 if( file
== file
->initializer
)
834 return isom_finish_fragment_initial_movie( file
);
836 return 0; /* No movie fragment to be finished. */
838 /* Don't write the current movie fragment if containing no track fragments.
839 * This is a requirement of DASH Media Segment. */
840 if( !moof
->traf_list
.head
841 || !moof
->traf_list
.head
->data
)
843 /* Calculate appropriate default_sample_flags of each Track Fragment Header Box.
844 * And check whether that default_sample_flags is useful or not. */
845 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
847 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
848 if( LSMASH_IS_NON_EXISTING_BOX( traf
)
849 || LSMASH_IS_NON_EXISTING_BOX( traf
->tfhd
)
850 || LSMASH_IS_NON_EXISTING_BOX( traf
->file
->initializer
->moov
->mvex
) )
851 return LSMASH_ERR_NAMELESS
;
852 isom_tfhd_t
*tfhd
= traf
->tfhd
;
853 isom_trex_t
*trex
= isom_get_trex( file
->initializer
->moov
->mvex
, tfhd
->track_ID
);
854 if( LSMASH_IS_NON_EXISTING_BOX( trex
) )
855 return LSMASH_ERR_NAMELESS
;
856 struct sample_flags_stats_t
858 uint32_t is_leading
[4];
859 uint32_t sample_depends_on
[4];
860 uint32_t sample_is_depended_on
[4];
861 uint32_t sample_has_redundancy
[4];
862 uint32_t sample_is_non_sync_sample
[2];
863 } stats
= { { 0 }, { 0 }, { 0 }, { 0 }, { 0 } };
864 for( lsmash_entry_t
*trun_entry
= traf
->trun_list
.head
; trun_entry
; trun_entry
= trun_entry
->next
)
866 isom_trun_t
*trun
= (isom_trun_t
*)trun_entry
->data
;
867 if( LSMASH_IS_NON_EXISTING_BOX( trun
) || trun
->sample_count
== 0 )
868 return LSMASH_ERR_NAMELESS
;
869 isom_sample_flags_t
*sample_flags
;
870 if( trun
->flags
& ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT
)
872 if( !trun
->optional
)
873 return LSMASH_ERR_NAMELESS
;
874 for( lsmash_entry_t
*optional_entry
= trun
->optional
->head
; optional_entry
; optional_entry
= optional_entry
->next
)
876 isom_trun_optional_row_t
*row
= (isom_trun_optional_row_t
*)optional_entry
->data
;
878 return LSMASH_ERR_NAMELESS
;
879 sample_flags
= &row
->sample_flags
;
880 ++ stats
.is_leading
[ sample_flags
->is_leading
];
881 ++ stats
.sample_depends_on
[ sample_flags
->sample_depends_on
];
882 ++ stats
.sample_is_depended_on
[ sample_flags
->sample_is_depended_on
];
883 ++ stats
.sample_has_redundancy
[ sample_flags
->sample_has_redundancy
];
884 ++ stats
.sample_is_non_sync_sample
[ sample_flags
->sample_is_non_sync_sample
];
889 sample_flags
= &tfhd
->default_sample_flags
;
890 stats
.is_leading
[ sample_flags
->is_leading
] += trun
->sample_count
;
891 stats
.sample_depends_on
[ sample_flags
->sample_depends_on
] += trun
->sample_count
;
892 stats
.sample_is_depended_on
[ sample_flags
->sample_is_depended_on
] += trun
->sample_count
;
893 stats
.sample_has_redundancy
[ sample_flags
->sample_has_redundancy
] += trun
->sample_count
;
894 stats
.sample_is_non_sync_sample
[ sample_flags
->sample_is_non_sync_sample
] += trun
->sample_count
;
897 uint32_t most_used
[5] = { 0, 0, 0, 0, 0 };
898 for( int i
= 0; i
< 4; i
++ )
900 GET_MOST_USED( tfhd
, 0, is_leading
);
901 GET_MOST_USED( tfhd
, 1, sample_depends_on
);
902 GET_MOST_USED( tfhd
, 2, sample_is_depended_on
);
903 GET_MOST_USED( tfhd
, 3, sample_has_redundancy
);
905 GET_MOST_USED( tfhd
, 4, sample_is_non_sync_sample
);
907 int useful_default_sample_duration
= 0;
908 int useful_default_sample_size
= 0;
909 for( lsmash_entry_t
*trun_entry
= traf
->trun_list
.head
; trun_entry
; trun_entry
= trun_entry
->next
)
911 isom_trun_t
*trun
= (isom_trun_t
*)trun_entry
->data
;
912 assert( LSMASH_IS_EXISTING_BOX( trun
) );
913 if( !(trun
->flags
& ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT
) )
914 useful_default_sample_duration
= 1;
915 if( !(trun
->flags
& ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT
) )
916 useful_default_sample_size
= 1;
917 int useful_first_sample_flags
= 1;
918 int useful_default_sample_flags
= 1;
919 if( trun
->sample_count
== 1 )
921 /* It is enough to check only if first_sample_flags equals default_sample_flags or not.
922 * If it is equal, just use default_sample_flags.
923 * If not, just use first_sample_flags of this run. */
924 if( !isom_compare_sample_flags( &trun
->first_sample_flags
, &tfhd
->default_sample_flags
) )
925 useful_first_sample_flags
= 0;
927 else if( trun
->optional
928 && trun
->optional
->head
)
930 lsmash_entry_t
*optional_entry
= trun
->optional
->head
->next
;
931 isom_trun_optional_row_t
*row
= (isom_trun_optional_row_t
*)optional_entry
->data
;
932 isom_sample_flags_t representative_sample_flags
= row
->sample_flags
;
933 if( isom_compare_sample_flags( &tfhd
->default_sample_flags
, &representative_sample_flags
) )
934 useful_default_sample_flags
= 0;
935 if( !isom_compare_sample_flags( &trun
->first_sample_flags
, &representative_sample_flags
) )
936 useful_first_sample_flags
= 0;
937 if( useful_default_sample_flags
)
938 for( optional_entry
= optional_entry
->next
; optional_entry
; optional_entry
= optional_entry
->next
)
940 row
= (isom_trun_optional_row_t
*)optional_entry
->data
;
941 if( isom_compare_sample_flags( &representative_sample_flags
, &row
->sample_flags
) )
943 useful_default_sample_flags
= 0;
948 if( useful_default_sample_flags
)
950 tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT
;
951 trun
->flags
&= ~ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT
;
955 useful_first_sample_flags
= 0;
956 trun
->flags
|= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT
;
958 if( useful_first_sample_flags
)
959 trun
->flags
|= ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT
;
961 if( useful_default_sample_duration
&& tfhd
->default_sample_duration
!= trex
->default_sample_duration
)
962 tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT
;
964 tfhd
->default_sample_duration
= trex
->default_sample_duration
; /* This might be redundant, but is to be more natural. */
965 if( useful_default_sample_size
&& tfhd
->default_sample_size
!= trex
->default_sample_size
)
966 tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT
;
968 tfhd
->default_sample_size
= trex
->default_sample_size
; /* This might be redundant, but is to be more natural. */
969 if( !(tfhd
->flags
& ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT
) )
970 tfhd
->default_sample_flags
= trex
->default_sample_flags
; /* This might be redundant, but is to be more natural. */
971 else if( !isom_compare_sample_flags( &tfhd
->default_sample_flags
, &trex
->default_sample_flags
) )
972 tfhd
->flags
&= ~ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT
;
974 /* Complete the last sample groups in the previous track fragments. */
976 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
978 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
979 assert( LSMASH_IS_EXISTING_BOX( traf
) );
980 if( traf
->cache
->rap
)
982 isom_sgpd_t
*sgpd
= isom_get_fragment_sample_group_description( traf
, ISOM_GROUP_TYPE_RAP
);
983 if( LSMASH_IS_NON_EXISTING_BOX( sgpd
) )
984 return LSMASH_ERR_NAMELESS
;
985 if( (ret
= isom_rap_grouping_established( traf
->cache
->rap
, 1, sgpd
, 1 )) < 0 )
987 lsmash_freep( &traf
->cache
->rap
);
989 if( traf
->cache
->roll
.pool
)
991 isom_sbgp_t
*sbgp
= isom_get_roll_recovery_sample_to_group( &traf
->sbgp_list
);
992 if( LSMASH_IS_NON_EXISTING_BOX( sbgp
) )
993 return LSMASH_ERR_NAMELESS
;
994 if( (ret
= isom_all_recovery_completed( sbgp
, traf
->cache
->roll
.pool
)) < 0 )
998 /* Establish Movie Fragment Box.
999 * We write exactly one Media Data Box starting immediately after the corresponding Movie Fragment Box. */
1000 if( file
->allow_moof_base
)
1002 /* In this branch, we use default-base-is-moof flag, which indicates implicit base_data_offsets originate in the
1003 * first byte of each enclosing Movie Fragment Box.
1004 * We use the sum of the size of the Movie Fragment Box and the offset from the size field of the Media Data Box to
1005 * the type field of it as the data_offset of the first track run like the following.
1007 * _____________ _ offset := 0
1016 * |___|_________|_ offset := the size of the Movie Fragment Box
1022 * | a |_________|_ offset := the data_offset of the first track run
1025 * |___|_________|_ offset := the size of a subsegment containing exactly one movie fragment
1027 * For a pair of one Movie Fragment Box and one Media Data Box, placed in this order, implicit base_data_offsets
1028 * indicated by the absence of both base-data-offset-present and default-base-is-moof are somewhat complicated
1029 * since the implicit base_data_offset of the current track fragment is defined by the end of the data of the
1030 * previous track fragment and the data_offset of the track runs could be negative value because of interleaving
1031 * track runs or something other reasons.
1032 * In contrast, implicit base_data_offsets indicated by default-base-is-moof are simple since the base_data_offset
1033 * of each track fragment is always constant for that pair and has no dependency on other track fragments.
1035 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
1037 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
1038 assert( LSMASH_IS_EXISTING_BOX( traf
) );
1039 traf
->tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_BASE_IS_MOOF
;
1040 traf
->tfhd
->base_data_offset
= file
->size
; /* not written actually though */
1041 for( lsmash_entry_t
*trun_entry
= traf
->trun_list
.head
; trun_entry
; trun_entry
= trun_entry
->next
)
1043 /* Here, data_offset is always greater than zero. */
1044 isom_trun_t
*trun
= trun_entry
->data
;
1045 assert( LSMASH_IS_EXISTING_BOX( trun
) );
1046 trun
->flags
|= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT
;
1049 /* Consider the update of tr_flags here. */
1050 if( isom_update_box_size( moof
) == 0 )
1051 return LSMASH_ERR_NAMELESS
;
1052 /* Now, we can calculate offsets in the current movie fragment, so do it. */
1053 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
1055 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
1056 assert( LSMASH_IS_EXISTING_BOX( traf
) );
1057 for( lsmash_entry_t
*trun_entry
= traf
->trun_list
.head
; trun_entry
; trun_entry
= trun_entry
->next
)
1059 isom_trun_t
*trun
= trun_entry
->data
;
1060 assert( LSMASH_IS_EXISTING_BOX( trun
) );
1061 trun
->data_offset
+= moof
->size
+ ISOM_BASEBOX_COMMON_SIZE
;
1067 /* In this branch, we use explicit base_data_offset. */
1068 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
1070 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
1071 assert( LSMASH_IS_EXISTING_BOX( traf
) );
1072 traf
->tfhd
->flags
|= ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT
;
1074 /* Consider the update of tf_flags here. */
1075 if( isom_update_box_size( moof
) == 0 )
1076 return LSMASH_ERR_NAMELESS
;
1077 /* Now, we can calculate offsets in the current movie fragment, so do it. */
1078 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
1080 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
1081 assert( LSMASH_IS_EXISTING_BOX( traf
) );
1082 traf
->tfhd
->base_data_offset
= file
->size
+ moof
->size
+ ISOM_BASEBOX_COMMON_SIZE
;
1085 /* Write Movie Fragment Box and its children. */
1086 moof
->pos
= file
->size
;
1087 if( (ret
= isom_write_box( file
->bs
, (isom_box_t
*)moof
)) < 0 )
1089 if( file
->fragment
->first_moof_pos
== FIRST_MOOF_POS_UNDETERMINED
)
1090 file
->fragment
->first_moof_pos
= moof
->pos
;
1091 file
->size
+= moof
->size
;
1092 /* Output samples. */
1093 if( (ret
= isom_output_fragment_media_data( file
)) < 0 )
1095 /* Revert the number of samples in track fragments to 0. */
1096 for( lsmash_entry_t
*entry
= moof
->traf_list
.head
; entry
; entry
= entry
->next
)
1098 isom_traf_t
*traf
= (isom_traf_t
*)entry
->data
;
1099 assert( LSMASH_IS_EXISTING_BOX( traf
) );
1100 if( traf
->cache
->fragment
)
1101 isom_fragment_reset_sample_counts( traf
->cache
);
1103 if( !(file
->flags
& LSMASH_FILE_MODE_INDEX
) || file
->max_isom_version
< 6 )
1105 return isom_make_segment_index_entry( file
, moof
);
1108 #undef GET_MOST_USED
1110 static isom_trun_optional_row_t
*isom_request_trun_optional_row( isom_trun_t
*trun
, isom_tfhd_t
*tfhd
, uint32_t sample_number
)
1112 isom_trun_optional_row_t
*row
= NULL
;
1113 if( !trun
->optional
)
1115 trun
->optional
= lsmash_list_create_simple();
1116 if( !trun
->optional
)
1119 if( trun
->optional
->entry_count
< sample_number
)
1121 while( trun
->optional
->entry_count
< sample_number
)
1123 row
= lsmash_malloc( sizeof(isom_trun_optional_row_t
) );
1126 /* Copy from default. */
1127 row
->sample_duration
= tfhd
->default_sample_duration
;
1128 row
->sample_size
= tfhd
->default_sample_size
;
1129 row
->sample_flags
= tfhd
->default_sample_flags
;
1130 row
->sample_composition_time_offset
= 0;
1131 if( lsmash_list_add_entry( trun
->optional
, row
) < 0 )
1140 for( lsmash_entry_t
*entry
= trun
->optional
->head
; entry
; entry
= entry
->next
)
1142 row
= (isom_trun_optional_row_t
*)entry
->data
;
1145 if( ++i
== sample_number
)
1151 int lsmash_create_fragment_empty_duration
1153 lsmash_root_t
*root
,
1158 if( isom_check_initializer_present( root
) < 0 )
1159 return LSMASH_ERR_FUNCTION_PARAM
;
1160 lsmash_file_t
*file
= root
->file
;
1162 || !file
->fragment
->movie
1163 || LSMASH_IS_NON_EXISTING_BOX( file
->initializer
->moov
) )
1164 return LSMASH_ERR_NAMELESS
;
1165 isom_trak_t
*trak
= isom_get_trak( file
->initializer
, track_ID
);
1166 if( LSMASH_IS_NON_EXISTING_BOX( trak
->tkhd
) )
1167 return LSMASH_ERR_NAMELESS
;
1168 isom_trex_t
*trex
= isom_get_trex( file
->initializer
->moov
->mvex
, track_ID
);
1169 if( LSMASH_IS_NON_EXISTING_BOX( trex
) )
1170 return LSMASH_ERR_NAMELESS
;
1171 isom_moof_t
*moof
= file
->fragment
->movie
;
1172 isom_traf_t
*traf
= isom_get_traf( moof
, track_ID
);
1173 if( LSMASH_IS_EXISTING_BOX( traf
) )
1174 return LSMASH_ERR_NAMELESS
;
1175 traf
= isom_add_traf( moof
);
1176 if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tfhd( traf
) ) )
1177 return LSMASH_ERR_NAMELESS
;
1178 isom_tfhd_t
*tfhd
= traf
->tfhd
;
1179 tfhd
->flags
= ISOM_TF_FLAGS_DURATION_IS_EMPTY
; /* no samples for this track fragment yet */
1180 tfhd
->track_ID
= trak
->tkhd
->track_ID
;
1181 tfhd
->default_sample_duration
= duration
;
1182 if( duration
!= trex
->default_sample_duration
)
1183 tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT
;
1184 traf
->cache
= trak
->cache
;
1185 traf
->cache
->fragment
->traf_number
= moof
->traf_list
.entry_count
;
1186 traf
->cache
->fragment
->last_duration
+= duration
; /* The duration of the last sample includes this empty-duration. */
1190 int isom_set_fragment_last_duration
1193 uint32_t last_duration
1196 isom_tfhd_t
*tfhd
= traf
->tfhd
;
1197 if( !traf
->trun_list
.tail
1198 || !traf
->trun_list
.tail
->data
)
1200 /* There are no track runs in this track fragment, so it is a empty-duration. */
1201 isom_trex_t
*trex
= isom_get_trex( traf
->file
->initializer
->moov
->mvex
, tfhd
->track_ID
);
1202 if( LSMASH_IS_NON_EXISTING_BOX( trex
) )
1203 return LSMASH_ERR_NAMELESS
;
1204 tfhd
->flags
|= ISOM_TF_FLAGS_DURATION_IS_EMPTY
;
1205 if( last_duration
!= trex
->default_sample_duration
)
1206 tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT
;
1207 tfhd
->default_sample_duration
= last_duration
;
1208 traf
->cache
->fragment
->last_duration
= last_duration
;
1211 /* Update the last sample_duration if needed. */
1212 isom_trun_t
*trun
= (isom_trun_t
*)traf
->trun_list
.tail
->data
;
1213 if( trun
->sample_count
== 1
1214 && traf
->trun_list
.entry_count
== 1 )
1216 isom_trex_t
*trex
= isom_get_trex( traf
->file
->initializer
->moov
->mvex
, tfhd
->track_ID
);
1217 if( LSMASH_IS_NON_EXISTING_BOX( trex
) )
1218 return LSMASH_ERR_NAMELESS
;
1219 if( last_duration
!= trex
->default_sample_duration
)
1220 tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT
;
1221 tfhd
->default_sample_duration
= last_duration
;
1223 else if( last_duration
!= tfhd
->default_sample_duration
)
1224 trun
->flags
|= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT
;
1227 isom_trun_optional_row_t
*row
= isom_request_trun_optional_row( trun
, tfhd
, trun
->sample_count
);
1229 return LSMASH_ERR_NAMELESS
;
1230 row
->sample_duration
= last_duration
;
1232 traf
->cache
->fragment
->last_duration
= last_duration
;
1236 int isom_append_fragment_track_run
1238 lsmash_file_t
*file
,
1242 if( !chunk
->pool
|| chunk
->pool
->size
== 0 )
1244 isom_fragment_manager_t
*frag_manager
= file
->fragment
;
1245 /* Move data in the pool of the current track fragment to the pool of the current movie fragment.
1246 * Empty the pool of current track. We don't delete data of samples here. */
1247 if( lsmash_list_add_entry( frag_manager
->pool
, chunk
->pool
) < 0 )
1248 return LSMASH_ERR_MEMORY_ALLOC
;
1249 frag_manager
->sample_count
+= chunk
->pool
->sample_count
;
1250 frag_manager
->pool_size
+= chunk
->pool
->size
;
1251 chunk
->pool
= isom_create_sample_pool( chunk
->pool
->size
);
1252 return chunk
->pool
? 0 : LSMASH_ERR_MEMORY_ALLOC
;
1255 static int isom_output_fragment_cache( isom_traf_t
*traf
)
1257 isom_cache_t
*cache
= traf
->cache
;
1258 int ret
= isom_append_fragment_track_run( traf
->file
, &cache
->chunk
);
1261 for( lsmash_entry_t
*entry
= traf
->sgpd_list
.head
; entry
; entry
= entry
->next
)
1263 isom_sgpd_t
*sgpd
= (isom_sgpd_t
*)entry
->data
;
1264 if( LSMASH_IS_NON_EXISTING_BOX( sgpd
) )
1265 return LSMASH_ERR_NAMELESS
;
1266 switch( sgpd
->grouping_type
)
1268 case ISOM_GROUP_TYPE_RAP
:
1270 isom_rap_group_t
*group
= cache
->rap
;
1273 if( traf
->file
->fragment
)
1276 return LSMASH_ERR_NAMELESS
;
1278 if( !group
->random_access
)
1280 group
->random_access
->num_leading_samples_known
= 1;
1283 case ISOM_GROUP_TYPE_ROLL
:
1284 case ISOM_GROUP_TYPE_PROL
:
1285 if( !cache
->roll
.pool
)
1287 if( traf
->file
->fragment
)
1290 return LSMASH_ERR_NAMELESS
;
1292 isom_sbgp_t
*sbgp
= isom_get_roll_recovery_sample_to_group( &traf
->sbgp_list
);
1293 if( LSMASH_IS_NON_EXISTING_BOX( sbgp
) )
1294 return LSMASH_ERR_NAMELESS
;
1295 if( (ret
= isom_all_recovery_completed( sbgp
, cache
->roll
.pool
)) < 0 )
1305 int isom_flush_fragment_pooled_samples
1307 lsmash_file_t
*file
,
1309 uint32_t last_sample_duration
1312 isom_traf_t
*traf
= isom_get_traf( file
->fragment
->movie
, track_ID
);
1313 if( LSMASH_IS_NON_EXISTING_BOX( traf
) )
1314 /* No samples. We don't return as an error here since user might call the flushing function even if the
1315 * current movie fragment has no track fragment with this track_ID. */
1318 || !traf
->cache
->fragment
)
1319 return LSMASH_ERR_NAMELESS
;
1320 if( traf
->trun_list
.entry_count
1321 && traf
->trun_list
.tail
1322 && traf
->trun_list
.tail
->data
)
1324 /* Media Data Box preceded by Movie Fragment Box could change base_data_offsets in each track fragments later.
1325 * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */
1326 isom_trun_t
*trun
= (isom_trun_t
*)traf
->trun_list
.tail
->data
;
1327 if( file
->fragment
->pool_size
)
1328 trun
->flags
|= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT
;
1329 trun
->data_offset
= file
->fragment
->pool_size
;
1331 int ret
= isom_output_fragment_cache( traf
);
1334 return isom_set_fragment_last_duration( traf
, last_sample_duration
);
1337 /* This function doesn't update sample_duration of the last sample in the previous movie fragment.
1338 * Instead of this, isom_finish_movie_fragment undertakes this task. */
1339 static int isom_update_fragment_previous_sample_duration( isom_traf_t
*traf
, isom_trex_t
*trex
, uint32_t duration
)
1341 isom_tfhd_t
*tfhd
= traf
->tfhd
;
1342 isom_trun_t
*trun
= (isom_trun_t
*)traf
->trun_list
.tail
->data
;
1343 int previous_run_has_previous_sample
= 0;
1344 if( trun
->sample_count
== 1 )
1346 if( traf
->trun_list
.entry_count
== 1 )
1347 return 0; /* The previous track run belongs to the previous movie fragment if it exists. */
1348 if( !traf
->trun_list
.tail
->prev
1349 || !traf
->trun_list
.tail
->prev
->data
)
1350 return LSMASH_ERR_NAMELESS
;
1351 /* OK. The previous sample exists in the previous track run in the same track fragment. */
1352 trun
= (isom_trun_t
*)traf
->trun_list
.tail
->prev
->data
;
1353 previous_run_has_previous_sample
= 1;
1355 /* Update default_sample_duration of the Track Fragment Header Box
1356 * if this duration is what the first sample in the current track fragment owns. */
1357 if( (trun
->sample_count
== 2 && traf
->trun_list
.entry_count
== 1)
1358 || (trun
->sample_count
== 1 && traf
->trun_list
.entry_count
== 2) )
1360 if( duration
!= trex
->default_sample_duration
)
1361 tfhd
->flags
|= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT
;
1362 tfhd
->default_sample_duration
= duration
;
1364 /* Update the previous sample_duration if needed. */
1365 if( duration
!= tfhd
->default_sample_duration
)
1366 trun
->flags
|= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT
;
1369 uint32_t sample_number
= trun
->sample_count
- !previous_run_has_previous_sample
;
1370 isom_trun_optional_row_t
*row
= isom_request_trun_optional_row( trun
, tfhd
, sample_number
);
1372 return LSMASH_ERR_NAMELESS
;
1373 row
->sample_duration
= duration
;
1378 static isom_sample_flags_t
isom_generate_fragment_sample_flags( lsmash_sample_t
*sample
)
1380 isom_sample_flags_t flags
;
1382 flags
.is_leading
= sample
->prop
.leading
& 0x3;
1383 flags
.sample_depends_on
= sample
->prop
.independent
& 0x3;
1384 flags
.sample_is_depended_on
= sample
->prop
.disposable
& 0x3;
1385 flags
.sample_has_redundancy
= sample
->prop
.redundant
& 0x3;
1386 flags
.sample_padding_value
= 0;
1387 flags
.sample_is_non_sync_sample
= !(sample
->prop
.ra_flags
& ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC
);
1388 flags
.sample_degradation_priority
= 0;
1392 /* Set up the base media decode time of this track fragment.
1393 * This feature is available under ISO Base Media version 6 or later.
1394 * For DASH Media Segment, each Track Fragment Box shall contain a Track Fragment Base Media Decode Time Box. */
1395 static int isom_fragment_set_base_media_decode_time( lsmash_file_t
*file
, isom_traf_t
*traf
, uint64_t dts
)
1397 if( file
->max_isom_version
>= 6 || file
->media_segment
)
1399 assert( LSMASH_IS_NON_EXISTING_BOX( traf
->tfdt
) );
1400 if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tfdt( traf
) ) )
1401 return LSMASH_ERR_NAMELESS
;
1402 if( dts
> UINT32_MAX
)
1403 traf
->tfdt
->version
= 1;
1404 traf
->tfdt
->baseMediaDecodeTime
= dts
;
1409 /* Update the cache of SAP related information. */
1410 static void isom_fragment_update_cache_for_sap
1412 isom_cache_t
*cache
,
1413 lsmash_sample_t
*sample
1416 isom_subsegment_t
*subsegment
= &cache
->fragment
->subsegment
;
1417 int non_output_sample
= (sample
->cts
== LSMASH_TIMESTAMP_UNDEFINED
);
1418 if( !non_output_sample
)
1420 if( cache
->fragment
->sample_count
== 1 )
1422 assert( subsegment
->first_sample_cts
== LSMASH_TIMESTAMP_UNDEFINED
);
1423 subsegment
->first_sample_cts
= sample
->cts
;
1425 if( cache
->fragment
->output_sample_count
> 1 )
1427 assert( subsegment
->largest_cts
!= LSMASH_TIMESTAMP_UNDEFINED
1428 && subsegment
->smallest_cts
!= LSMASH_TIMESTAMP_UNDEFINED
);
1429 subsegment
->largest_cts
= LSMASH_MAX( sample
->cts
, subsegment
->largest_cts
);
1430 subsegment
->smallest_cts
= LSMASH_MIN( sample
->cts
, subsegment
->smallest_cts
);
1434 assert( subsegment
->largest_cts
== LSMASH_TIMESTAMP_UNDEFINED
1435 && subsegment
->smallest_cts
== LSMASH_TIMESTAMP_UNDEFINED
);
1436 if( cache
->fragment
->output_sample_count
== 1 )
1439 subsegment
->largest_cts
= sample
->cts
;
1440 subsegment
->smallest_cts
= sample
->cts
;
1444 if( subsegment
->first_ra_flags
== ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE
1445 && sample
->prop
.ra_flags
!= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE
)
1447 assert( subsegment
->first_ra_number
== 0 );
1448 subsegment
->first_ra_flags
= sample
->prop
.ra_flags
;
1449 subsegment
->first_ra_number
= cache
->fragment
->sample_count
;
1450 if( sample
->prop
.ra_flags
& (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC
| ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP
) )
1451 subsegment
->is_first_recovery_point
= 1;
1453 if( subsegment
->is_first_recovery_point
)
1455 assert( subsegment
->first_rp_number
== 0 );
1456 if( !non_output_sample
)
1458 assert( subsegment
->first_rp_cts
== LSMASH_TIMESTAMP_UNDEFINED
1459 && subsegment
->first_ed_cts
== LSMASH_TIMESTAMP_UNDEFINED
);
1460 subsegment
->first_rp_cts
= sample
->cts
;
1461 subsegment
->first_ed_cts
= sample
->cts
;
1463 subsegment
->first_rp_number
= subsegment
->first_ra_number
;
1464 subsegment
->decodable
= 1;
1465 subsegment
->is_first_recovery_point
= 0;
1467 else if( subsegment
->decodable
)
1469 if( (subsegment
->first_ra_flags
& (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC
| ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP
))
1470 ? (sample
->prop
.leading
== ISOM_SAMPLE_IS_DECODABLE_LEADING
)
1471 : (subsegment
->first_ra_flags
& ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START
) )
1473 if( !non_output_sample
)
1474 subsegment
->first_ed_cts
= LSMASH_MIN( sample
->cts
, subsegment
->first_ed_cts
);
1477 subsegment
->decodable
= 0;
1481 /* Update the cache of fragment related information.
1482 * The cached sample_count is incremented here, so be careful to treat it. */
1483 static void isom_fragment_update_cache
1485 isom_cache_t
*cache
,
1486 lsmash_sample_t
*sample
,
1490 cache
->fragment
->has_samples
= 1;
1491 cache
->fragment
->sample_count
+= 1;
1492 cache
->fragment
->output_sample_count
+= sample
->cts
== LSMASH_TIMESTAMP_UNDEFINED
? 0 : 1;
1493 assert( cache
->fragment
->sample_count
>= cache
->fragment
->output_sample_count
);
1494 if( (file
->flags
& LSMASH_FILE_MODE_INDEX
) && (file
->max_isom_version
>= 6) )
1495 isom_fragment_update_cache_for_sap( cache
, sample
);
1498 static int isom_fragment_update_sample_tables( isom_traf_t
*traf
, lsmash_sample_t
*sample
)
1500 isom_tfhd_t
*tfhd
= traf
->tfhd
;
1501 isom_trex_t
*trex
= isom_get_trex( traf
->file
->initializer
->moov
->mvex
, tfhd
->track_ID
);
1502 if( LSMASH_IS_NON_EXISTING_BOX( trex
) )
1503 return LSMASH_ERR_NAMELESS
;
1504 lsmash_file_t
*file
= traf
->file
;
1505 isom_cache_t
*cache
= traf
->cache
;
1506 isom_chunk_t
*current
= &cache
->chunk
;
1507 if( !current
->pool
)
1509 /* Very initial settings, just once per track */
1510 current
->pool
= isom_create_sample_pool( 0 );
1511 if( !current
->pool
)
1512 return LSMASH_ERR_MEMORY_ALLOC
;
1514 /* Create a new track run if the duration exceeds max_chunk_duration.
1515 * Old one will be appended to the pool of this movie fragment. */
1516 uint32_t media_timescale
= lsmash_get_media_timescale( file
->root
, tfhd
->track_ID
);
1517 if( media_timescale
== 0 )
1518 return LSMASH_ERR_NAMELESS
;
1519 int delimit
= (file
->max_chunk_duration
< ((double)(sample
->dts
- current
->first_dts
) / media_timescale
))
1520 || (file
->max_chunk_size
< (current
->pool
->size
+ sample
->length
));
1522 if( traf
->trun_list
.entry_count
== 0 || delimit
)
1525 && traf
->trun_list
.entry_count
1526 && traf
->trun_list
.tail
1527 && LSMASH_IS_EXISTING_BOX( (isom_trun_t
*)traf
->trun_list
.tail
->data
) )
1529 /* Media Data Box preceded by Movie Fragment Box could change base data offsets in each track fragments later.
1530 * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */
1531 trun
= (isom_trun_t
*)traf
->trun_list
.tail
->data
;
1532 if( file
->fragment
->pool_size
)
1533 trun
->flags
|= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT
;
1534 trun
->data_offset
= file
->fragment
->pool_size
;
1536 trun
= isom_add_trun( traf
);
1537 if( LSMASH_IS_NON_EXISTING_BOX( trun
) )
1538 return LSMASH_ERR_NAMELESS
;
1542 if( !traf
->trun_list
.tail
1543 || LSMASH_IS_NON_EXISTING_BOX( (isom_trun_t
*)traf
->trun_list
.tail
->data
) )
1544 return LSMASH_ERR_NAMELESS
;
1545 trun
= (isom_trun_t
*)traf
->trun_list
.tail
->data
;
1547 isom_sample_flags_t sample_flags
= isom_generate_fragment_sample_flags( sample
);
1548 int non_output_sample
= (sample
->cts
== LSMASH_TIMESTAMP_UNDEFINED
);
1549 if( ++trun
->sample_count
== 1 )
1551 if( traf
->trun_list
.entry_count
== 1 )
1553 tfhd
->sample_description_index
= current
->sample_description_index
1555 tfhd
->default_sample_size
= sample
->length
;
1556 tfhd
->default_sample_flags
= sample_flags
; /* Note: we decide an appropriate default value at the end of this movie fragment. */
1557 tfhd
->flags
&= ~ISOM_TF_FLAGS_DURATION_IS_EMPTY
; /* This track fragment isn't empty-duration-fragment any more. */
1558 if( sample
->index
!= trex
->default_sample_description_index
)
1559 tfhd
->flags
|= ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT
;
1560 /* Set up random access information if this sample is a sync sample.
1561 * We inform only the first sample in each movie fragment. */
1562 if( !non_output_sample
1563 && LSMASH_IS_EXISTING_BOX( file
->mfra
)
1564 && (sample
->prop
.ra_flags
& ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC
) )
1566 isom_tfra_t
*tfra
= isom_get_tfra( file
->mfra
, tfhd
->track_ID
);
1567 if( LSMASH_IS_NON_EXISTING_BOX( tfra
) )
1569 tfra
= isom_add_tfra( file
->mfra
);
1570 if( LSMASH_IS_NON_EXISTING_BOX( tfra
) )
1571 return LSMASH_ERR_NAMELESS
;
1572 tfra
->track_ID
= tfhd
->track_ID
;
1576 tfra
->list
= lsmash_list_create_simple();
1578 return LSMASH_ERR_MEMORY_ALLOC
;
1580 isom_tfra_location_time_entry_t
*rap
= lsmash_malloc( sizeof(isom_tfra_location_time_entry_t
) );
1582 return LSMASH_ERR_MEMORY_ALLOC
;
1583 rap
->time
= sample
->cts
; /* Set composition timestamp temporarily.
1584 * At the end of the whole movie, this will be reset as presentation time. */
1585 rap
->moof_offset
= file
->size
; /* We place Movie Fragment Box in the head of each movie fragment. */
1586 rap
->traf_number
= cache
->fragment
->traf_number
;
1587 rap
->trun_number
= traf
->trun_list
.entry_count
;
1588 rap
->sample_number
= trun
->sample_count
;
1589 if( lsmash_list_add_entry( tfra
->list
, rap
) < 0 )
1592 return LSMASH_ERR_MEMORY_ALLOC
;
1594 tfra
->number_of_entry
= tfra
->list
->entry_count
;
1596 for( length
= 1; rap
->traf_number
>> (length
* 8); length
++ );
1597 tfra
->length_size_of_traf_num
= LSMASH_MAX( length
- 1, tfra
->length_size_of_traf_num
);
1598 for( length
= 1; rap
->traf_number
>> (length
* 8); length
++ );
1599 tfra
->length_size_of_trun_num
= LSMASH_MAX( length
- 1, tfra
->length_size_of_trun_num
);
1600 for( length
= 1; rap
->sample_number
>> (length
* 8); length
++ );
1601 tfra
->length_size_of_sample_num
= LSMASH_MAX( length
- 1, tfra
->length_size_of_sample_num
);
1603 int err
= isom_fragment_set_base_media_decode_time( file
, traf
, sample
->dts
);
1607 trun
->first_sample_flags
= sample_flags
;
1608 current
->first_dts
= sample
->dts
;
1610 /* Update the optional rows in the current track run except for sample_duration if needed. */
1611 if( sample
->length
!= tfhd
->default_sample_size
)
1612 trun
->flags
|= ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT
;
1613 if( isom_compare_sample_flags( &sample_flags
, &tfhd
->default_sample_flags
) )
1614 trun
->flags
|= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT
;
1615 uint32_t sample_composition_time_offset
= !non_output_sample
? sample
->cts
- sample
->dts
: ISOM_NON_OUTPUT_SAMPLE_OFFSET
;
1616 int32_t ctd_shift
= cache
->timestamp
.ctd_shift
;
1617 if( sample_composition_time_offset
)
1619 trun
->flags
|= ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT
;
1620 /* Check if negative composition time offset is present. */
1621 if( !non_output_sample
)
1623 if( (sample
->cts
+ ctd_shift
) < sample
->dts
)
1625 if( file
->max_isom_version
< 6 )
1626 return LSMASH_ERR_INVALID_DATA
; /* Negative composition time offset is invalid. */
1627 if( (sample
->dts
- sample
->cts
) > INT32_MAX
)
1628 return LSMASH_ERR_INVALID_DATA
; /* Overflow */
1629 ctd_shift
= sample
->dts
- sample
->cts
;
1635 if( file
->max_isom_version
< 6 )
1636 return LSMASH_ERR_INVALID_DATA
; /* Negative composition time offset is invalid. */
1642 isom_trun_optional_row_t
*row
= isom_request_trun_optional_row( trun
, tfhd
, trun
->sample_count
);
1644 return LSMASH_ERR_NAMELESS
;
1645 row
->sample_size
= sample
->length
;
1646 row
->sample_flags
= sample_flags
;
1647 row
->sample_composition_time_offset
= sample_composition_time_offset
;
1649 /* Set up the sample groupings for random access. */
1651 if( (ret
= isom_group_random_access( (isom_box_t
*)traf
, cache
, sample
)) < 0
1652 || (ret
= isom_group_roll_recovery( (isom_box_t
*)traf
, cache
, sample
)) < 0 )
1654 /* Set up the previous sample_duration if this sample is not the first sample in the overall movie. */
1655 uint32_t sample_duration
;
1656 if( cache
->fragment
->has_samples
)
1658 /* Note: when using for live streaming, it is not good idea to return error by sample->dts < prev_dts
1659 * since that's trivial for such semi-permanent presentation. */
1660 uint64_t prev_dts
= cache
->timestamp
.dts
;
1661 if( sample
->dts
<= prev_dts
1662 || sample
->dts
> prev_dts
+ UINT32_MAX
)
1663 return LSMASH_ERR_INVALID_DATA
;
1664 sample_duration
= sample
->dts
- prev_dts
;
1665 if( (ret
= isom_update_fragment_previous_sample_duration( traf
, trex
, sample_duration
)) < 0 )
1669 sample_duration
= cache
->fragment
->last_duration
;
1670 isom_update_cache_timestamp( cache
, sample
->dts
, sample
->cts
, ctd_shift
, sample_duration
, non_output_sample
);
1674 static int isom_append_fragment_sample_internal_initial
1677 lsmash_sample_t
*sample
,
1678 isom_sample_entry_t
*sample_entry
1681 /* Update the sample tables of this track fragment enclosing the initial movie.
1682 * If a new chunk was created, append the previous one to the pool of this movie fragment. */
1683 uint32_t samples_per_packet
;
1684 int ret
= isom_update_sample_tables( trak
, sample
, &samples_per_packet
, sample_entry
);
1688 isom_append_fragment_track_run( trak
->file
, &trak
->cache
->chunk
);
1689 isom_fragment_update_cache( trak
->cache
, sample
, trak
->file
);
1690 /* Add a new sample into the pool of this track fragment. */
1691 if( (ret
= isom_pool_sample( trak
->cache
->chunk
.pool
, sample
, samples_per_packet
)) < 0 )
1696 static int isom_append_fragment_sample_internal
1699 lsmash_sample_t
*sample
,
1700 isom_sample_entry_t
*sample_entry
/* unused */
1703 /* Update the sample tables of this track fragment.
1704 * If a new track run was created, append the previous one to the pool of this movie fragment. */
1705 int ret
= isom_fragment_update_sample_tables( traf
, sample
);
1709 isom_append_fragment_track_run( traf
->file
, &traf
->cache
->chunk
);
1710 isom_fragment_update_cache( traf
->cache
, sample
, traf
->file
);
1711 /* Add a new sample into the pool of this track fragment. */
1712 if( (ret
= isom_pool_sample( traf
->cache
->chunk
.pool
, sample
, 1 )) < 0 )
1717 int isom_append_fragment_sample
1719 lsmash_file_t
*file
,
1721 lsmash_sample_t
*sample
,
1722 isom_sample_entry_t
*sample_entry
1725 if( !trak
->cache
->fragment
)
1726 return LSMASH_ERR_NAMELESS
;
1727 isom_fragment_manager_t
*fragment
= file
->fragment
;
1728 assert( fragment
&& fragment
->pool
);
1729 /* Write the Segment Type Box here if required and if it was not written yet. */
1730 if( !(file
->flags
& LSMASH_FILE_MODE_INITIALIZATION
)
1731 && file
->styp_list
.head
1732 && LSMASH_IS_NON_EXISTING_BOX( (isom_styp_t
*)file
->styp_list
.head
->data
) )
1734 isom_styp_t
*styp
= (isom_styp_t
*)file
->styp_list
.head
->data
;
1735 if( !(styp
->manager
& LSMASH_WRITTEN_BOX
) )
1737 int ret
= isom_write_box( file
->bs
, (isom_box_t
*)styp
);
1740 file
->size
+= styp
->size
;
1743 int (*func_append_sample
)( void *, lsmash_sample_t
*, isom_sample_entry_t
* ) = NULL
;
1744 void *track_fragment
;
1745 if( LSMASH_IS_NON_EXISTING_BOX( fragment
->movie
) )
1747 /* Forbid adding a sample into the initial movie if requiring compatibility with Media Segment. */
1748 if( file
->media_segment
)
1749 return LSMASH_ERR_NAMELESS
;
1750 func_append_sample
= (int (*)( void *, lsmash_sample_t
*, isom_sample_entry_t
* ))isom_append_fragment_sample_internal_initial
;
1751 track_fragment
= trak
;
1755 /* NOTE: there are codes to support non-output sample signaling in fragments but we disabled them currently since the
1756 * ISOBMFF spec 5th edition mentions 'ctts' and 'cslg' in 'stbl' which exists in the initial movie only. Therefore,
1757 * as a safety, reject non-output samples here. */
1758 if( sample
->cts
== LSMASH_TIMESTAMP_UNDEFINED
)
1759 return LSMASH_ERR_INVALID_DATA
;
1760 isom_traf_t
*traf
= isom_get_traf( fragment
->movie
, trak
->tkhd
->track_ID
);
1761 if( LSMASH_IS_NON_EXISTING_BOX( traf
) )
1763 traf
= isom_add_traf( fragment
->movie
);
1764 if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tfhd( traf
) ) )
1765 return LSMASH_ERR_NAMELESS
;
1766 traf
->tfhd
->flags
= ISOM_TF_FLAGS_DURATION_IS_EMPTY
; /* no samples for this track fragment yet */
1767 traf
->tfhd
->track_ID
= trak
->tkhd
->track_ID
;
1768 traf
->cache
= trak
->cache
;
1769 traf
->cache
->fragment
->traf_number
= fragment
->movie
->traf_list
.entry_count
;
1771 if( (traf
->cache
->fragment
->rap_grouping
&& (ret
= isom_add_sample_grouping( (isom_box_t
*)traf
, ISOM_GROUP_TYPE_RAP
)) < 0)
1772 || (traf
->cache
->fragment
->roll_grouping
&& (ret
= isom_add_sample_grouping( (isom_box_t
*)traf
, ISOM_GROUP_TYPE_ROLL
)) < 0) )
1775 else if( LSMASH_IS_NON_EXISTING_BOX( traf
->file
->initializer
->moov
->mvex
)
1776 || LSMASH_IS_NON_EXISTING_BOX( traf
->tfhd
)
1778 return LSMASH_ERR_NAMELESS
;
1779 func_append_sample
= (int (*)( void *, lsmash_sample_t
*, isom_sample_entry_t
* ))isom_append_fragment_sample_internal
;
1780 track_fragment
= traf
;
1782 return isom_append_sample_by_type( track_fragment
, sample
, sample_entry
, func_append_sample
);