list: Decide the entry eliminator of list at its initialization.
[L-SMASH.git] / core / fragment.c
blob9bdf31e7d4c4e0f736e2a4968a3d15d35154182d
1 /*****************************************************************************
2 * fragment.c
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 */
25 #include <string.h>
26 #include "box.h"
27 #include "box_default.h"
28 #include "file.h"
29 #include "write.h"
30 #include "fragment.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 )
42 return sidx;
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;
56 if( !file->bs
57 || !file->fragment )
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 );
61 if( ret < 0 )
62 return ret;
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 )
77 return 0;
78 /* Remove the previous movie fragment. */
79 if( file->moof_list.head )
80 isom_remove_box_by_itself( file->moof_list.head->data );
82 return 0;
85 static inline uint64_t isom_fragment_get_implicit_segment_duration
87 isom_cache_t *cache
90 return cache->fragment->largest_cts != LSMASH_TIMESTAMP_UNDEFINED
91 ? cache->fragment->largest_cts + cache->fragment->last_duration
92 : 0;
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 )
105 || !trak->cache
106 || !trak->cache->fragment
107 || trak->mdia->mdhd->timescale == 0 )
108 return LSMASH_ERR_NAMELESS;
109 uint64_t duration;
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);
113 else
115 duration = 0;
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;
119 if( !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);
123 else
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;
131 mehd->version = 1;
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 );
140 return ret;
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 ) )
147 return 0;
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 )
168 continue;
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 ) )
174 continue;
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;
183 if( !rap )
185 /* Irregular case. Drop this entry. */
186 lsmash_entry_t *next = rap_entry->next;
187 lsmash_list_remove_entry_direct( tfra->list, rap_entry );
188 rap_entry = next;
189 continue;
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. */
194 while( edit )
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;
208 if( !edit_entry )
210 /* No more presentation. */
211 edit = NULL;
212 break;
214 edit = edit_entry->data;
216 if( !edit )
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. */
222 while( rap_entry )
224 lsmash_entry_t *next = rap_entry->next;
225 lsmash_list_remove_entry_direct( tfra->list, rap_entry );
226 rap_entry = next;
228 break;
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
248 lsmash_file_t *file,
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 ) )
257 continue;
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;
267 a->first_offset = 0;
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;
274 return 0;
277 static int isom_write_segment_indexes
279 lsmash_file_t *file,
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. */
284 int ret;
285 if( (ret = isom_update_indexed_material_offset( file, (isom_sidx_t *)file->sidx_list.tail->data )) < 0 )
286 return ret;
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 ) )
293 continue;
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;
308 int64_t ret64;
309 if( (ret64 = lsmash_bs_write_seek( bs, file->fragment->first_moof_pos, SEEK_SET )) < 0 )
311 ret = ret64;
312 goto fail;
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 )
320 ret = ret64;
321 goto fail;
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 ) )
327 continue;
328 if( (ret = isom_write_box( file->bs, (isom_box_t *)sidx )) < 0 )
329 goto fail;
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 )
335 goto fail;
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. */
339 if( file->mfra )
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 ) )
344 continue;
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;
348 if( !rap )
349 continue;
350 rap->moof_offset += total_sidx_size;
353 return 0;
354 fail:
355 lsmash_free( buf[0] );
356 return ret;
359 int isom_finish_final_fragment_movie
361 lsmash_file_t *file,
362 lsmash_adhoc_remux_t *remux
365 /* Output the final movie fragment. */
366 int ret;
367 if( (ret = isom_finish_fragment_movie( file )) < 0 )
368 return ret;
369 if( file->bs->unseekable )
370 return 0;
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) )
381 if( !remux )
382 return LSMASH_ERR_FUNCTION_PARAM;
383 if( (ret = isom_write_segment_indexes( file, remux )) < 0 )
384 return ret;
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 )
388 return ret;
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 )
416 || !trak->cache )
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
432 : 1;
433 trex->default_sample_duration = stbl->stts->list->tail
434 ? ((isom_stts_entry_t *)stbl->stts->list->tail->data)->sample_delta
435 : 1;
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;
449 if( !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;
467 return 0;
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) )
479 return 0;
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;
483 return 0;
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 );
496 if( ret < 0 )
497 return ret;
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;
505 return 0;
508 static inline void isom_fragment_reset_sample_counts
510 isom_cache_t *cache
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 );
520 if( !file->moov )
521 return LSMASH_ERR_NAMELESS;
522 isom_moov_t *moov = file->moov;
523 int ret;
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 )
531 || !trak->cache )
532 return LSMASH_ERR_NAMELESS;
533 if( (ret = isom_complement_data_reference( trak->mdia->minf )) < 0 )
534 return ret;
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 )
541 return ret;
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 )
548 return ret;
550 else
551 trak->tkhd->duration = 0;
552 if( (ret = isom_update_bitrate_description( trak->mdia )) < 0 )
553 return ret;
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 )
561 return ret;
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 )
570 return ret;
573 if( file->mp4_version1 == 1 && (ret = isom_setup_iods( moov )) < 0 )
574 return ret;
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 )
578 return ret;
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 )
582 return ret;
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 )
590 return ret;
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 )
596 return ret;
597 file->size += preceding_size;
598 /* Output samples. */
599 if( (ret = isom_output_fragment_media_data( file )) < 0 )
600 return ret;
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 );
609 return 0;
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
627 lsmash_file_t *file,
628 isom_moof_t *moof
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;
650 sidx->reserved = 0;
651 sidx->reference_count = 0;
652 int ret = isom_update_indexed_material_offset( file, sidx );
653 if( ret < 0 )
654 return ret;
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) );
658 if( !data )
659 return LSMASH_ERR_NAMELESS;
660 if( lsmash_list_add_entry( sidx->list, data ) < 0 )
662 lsmash_free( data );
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;
668 /* presentation */
669 uint64_t TSAP;
670 uint64_t TDEC;
671 uint64_t TEPT;
672 uint64_t TPTF;
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;
691 /* */
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;
695 if( !edit )
696 continue;
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;
706 else
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 )
714 pts = edit_end_pts;
715 continue;
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);
732 else
733 TEPT = pts;
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);
745 else
746 TSAP = pts;
747 TDEC = TSAP;
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;
761 else
762 /* This subsegment is not present in this edit. */
763 pts = edit_end_pts;
766 else
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);
780 data->SAP_type = 0;
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 )
789 data->SAP_type = 1;
790 else if( TEPT == TDEC && TDEC == TSAP && TSAP < TPTF )
791 data->SAP_type = 2;
792 else if( TEPT < TDEC && TDEC == TSAP && TSAP <= TPTF )
793 data->SAP_type = 3;
794 else if( TEPT <= TPTF && TPTF < TDEC && TDEC == TSAP )
795 data->SAP_type = 4;
797 if( data->SAP_type == 0 )
799 if( TEPT == TDEC && TDEC < TSAP )
800 data->SAP_type = 5;
801 else if( TEPT < TDEC && TDEC < TSAP )
802 data->SAP_type = 6;
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;
819 return 0;
822 static int isom_finish_fragment_movie
824 lsmash_file_t *file
827 if( !file->fragment
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 );
835 else
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 )
842 return 0;
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;
877 if( !row )
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 ];
887 else
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 );
904 if( i < 2 )
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;
944 break;
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;
953 else
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;
963 else
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;
967 else
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. */
975 int ret;
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 )
986 return ret;
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 )
995 return ret;
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
1008 * | | |
1009 * | m | s i z e |
1010 * | |_________|
1011 * | o | |
1012 * | | t y p e |
1013 * | o |_________|
1014 * | | |
1015 * | f | d a t a |
1016 * |___|_________|_ offset := the size of the Movie Fragment Box
1017 * | | |
1018 * | m | s i z e |
1019 * | |_________|
1020 * | d | |
1021 * | | t y p e |
1022 * | a |_________|_ offset := the data_offset of the first track run
1023 * | | |
1024 * | t | d a t a |
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;
1065 else
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 )
1088 return ret;
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 )
1094 return ret;
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 )
1104 return 0;
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 )
1117 return NULL;
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) );
1124 if( !row )
1125 return NULL;
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 )
1133 lsmash_free( row );
1134 return NULL;
1137 return row;
1139 uint32_t i = 0;
1140 for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next )
1142 row = (isom_trun_optional_row_t *)entry->data;
1143 if( !row )
1144 return NULL;
1145 if( ++i == sample_number )
1146 return row;
1148 return NULL;
1151 int lsmash_create_fragment_empty_duration
1153 lsmash_root_t *root,
1154 uint32_t track_ID,
1155 uint32_t duration
1158 if( isom_check_initializer_present( root ) < 0 )
1159 return LSMASH_ERR_FUNCTION_PARAM;
1160 lsmash_file_t *file = root->file;
1161 if( !file->fragment
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. */
1187 return 0;
1190 int isom_set_fragment_last_duration
1192 isom_traf_t *traf,
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;
1209 return 0;
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;
1225 if( trun->flags )
1227 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count );
1228 if( !row )
1229 return LSMASH_ERR_NAMELESS;
1230 row->sample_duration = last_duration;
1232 traf->cache->fragment->last_duration = last_duration;
1233 return 0;
1236 int isom_append_fragment_track_run
1238 lsmash_file_t *file,
1239 isom_chunk_t *chunk
1242 if( !chunk->pool || chunk->pool->size == 0 )
1243 return 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 );
1259 if( ret < 0 )
1260 return ret;
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;
1271 if( !group )
1273 if( traf->file->fragment )
1274 continue;
1275 else
1276 return LSMASH_ERR_NAMELESS;
1278 if( !group->random_access )
1279 continue;
1280 group->random_access->num_leading_samples_known = 1;
1281 break;
1283 case ISOM_GROUP_TYPE_ROLL :
1284 case ISOM_GROUP_TYPE_PROL :
1285 if( !cache->roll.pool )
1287 if( traf->file->fragment )
1288 continue;
1289 else
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 )
1296 return ret;
1297 break;
1298 default :
1299 break;
1302 return 0;
1305 int isom_flush_fragment_pooled_samples
1307 lsmash_file_t *file,
1308 uint32_t track_ID,
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. */
1316 return 0;
1317 if( !traf->cache
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 );
1332 if( ret < 0 )
1333 return ret;
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;
1367 if( trun->flags )
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 );
1371 if( !row )
1372 return LSMASH_ERR_NAMELESS;
1373 row->sample_duration = duration;
1375 return 0;
1378 static isom_sample_flags_t isom_generate_fragment_sample_flags( lsmash_sample_t *sample )
1380 isom_sample_flags_t flags;
1381 flags.reserved = 0;
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;
1389 return flags;
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;
1406 return 0;
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 );
1432 else
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 );
1476 else
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,
1487 lsmash_file_t *file
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));
1521 isom_trun_t *trun;
1522 if( traf->trun_list.entry_count == 0 || delimit )
1524 if( 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;
1540 else
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
1554 = sample->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;
1574 if( !tfra->list )
1576 tfra->list = lsmash_list_create_simple();
1577 if( !tfra->list )
1578 return LSMASH_ERR_MEMORY_ALLOC;
1580 isom_tfra_location_time_entry_t *rap = lsmash_malloc( sizeof(isom_tfra_location_time_entry_t) );
1581 if( !rap )
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 )
1591 lsmash_free( rap );
1592 return LSMASH_ERR_MEMORY_ALLOC;
1594 tfra->number_of_entry = tfra->list->entry_count;
1595 int length;
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 );
1604 if( err < 0 )
1605 return err;
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;
1630 trun->version = 1;
1633 else
1635 if( file->max_isom_version < 6 )
1636 return LSMASH_ERR_INVALID_DATA; /* Negative composition time offset is invalid. */
1637 trun->version = 1;
1640 if( trun->flags )
1642 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count );
1643 if( !row )
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. */
1650 int ret;
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 )
1653 return ret;
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 )
1666 return ret;
1668 else
1669 sample_duration = cache->fragment->last_duration;
1670 isom_update_cache_timestamp( cache, sample->dts, sample->cts, ctd_shift, sample_duration, non_output_sample );
1671 return delimit;
1674 static int isom_append_fragment_sample_internal_initial
1676 isom_trak_t *trak,
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 );
1685 if( ret < 0 )
1686 return ret;
1687 else if( ret == 1 )
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 )
1692 return ret;
1693 return 0;
1696 static int isom_append_fragment_sample_internal
1698 isom_traf_t *traf,
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 );
1706 if( ret < 0 )
1707 return ret;
1708 else if( ret == 1 )
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 )
1713 return ret;
1714 return 0;
1717 int isom_append_fragment_sample
1719 lsmash_file_t *file,
1720 isom_trak_t *trak,
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 );
1738 if( ret < 0 )
1739 return ret;
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;
1753 else
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;
1770 int ret;
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) )
1773 return ret;
1775 else if( LSMASH_IS_NON_EXISTING_BOX( traf->file->initializer->moov->mvex )
1776 || LSMASH_IS_NON_EXISTING_BOX( traf->tfhd )
1777 || !traf->cache )
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 );