importer: Allocate sample buffer in individual importers.
[L-SMASH.git] / core / fragment.c
blobf39aa48a698f5bbe2b45026b07c728e07363205a
1 /*****************************************************************************
2 * fragment.c
3 *****************************************************************************
4 * Copyright (C) 2011-2014 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 "file.h"
28 #include "write.h"
29 #include "fragment.h"
31 static isom_sidx_t *isom_get_sidx( lsmash_file_t *file, uint32_t reference_ID )
33 if( reference_ID == 0 || !file )
34 return NULL;
35 for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next )
37 isom_sidx_t *sidx = (isom_sidx_t *)entry->data;
38 if( !sidx )
39 return NULL;
40 if( sidx->reference_ID == reference_ID )
41 return sidx;
43 return NULL;
46 static int isom_finish_fragment_movie( lsmash_file_t *file );
48 /* A movie fragment cannot switch a sample description to another.
49 * So you must call this function before switching sample descriptions. */
50 int lsmash_create_fragment_movie( lsmash_root_t *root )
52 if( isom_check_initializer_present( root ) < 0 )
53 return LSMASH_ERR_FUNCTION_PARAM;
54 lsmash_file_t *file = root->file;
55 if( !file
56 || !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 if( !file->fragment->movie || (file->fragment->movie->manager & LSMASH_WRITTEN_BOX) )
66 /* We always hold only one movie fragment except for the initial movie (a pair of moov and mdat). */
67 if( file->fragment->movie && file->moof_list.entry_count != 1 )
68 return LSMASH_ERR_NAMELESS;
69 isom_moof_t *moof = isom_add_moof( file );
70 if( !isom_add_mfhd( moof ) )
71 return LSMASH_ERR_NAMELESS;
72 file->fragment->movie = moof;
73 moof->mfhd->sequence_number = ++ file->fragment_count;
74 if( file->moof_list.entry_count == 1 )
75 return 0;
76 /* Remove the previous movie fragment. */
77 if( file->moof_list.head )
78 isom_remove_box_by_itself( file->moof_list.head->data );
80 return 0;
83 static int isom_set_fragment_overall_duration( lsmash_file_t *file )
85 assert( file == file->initializer );
86 /* Get the longest duration of the tracks. */
87 uint64_t longest_duration = 0;
88 for( lsmash_entry_t *entry = file->moov->trak_list.head; entry; entry = entry->next )
90 isom_trak_t *trak = (isom_trak_t *)entry->data;
91 if( !trak
92 || !trak->cache
93 || !trak->cache->fragment
94 || !trak->mdia
95 || !trak->mdia->mdhd
96 || trak->mdia->mdhd->timescale == 0 )
97 return LSMASH_ERR_NAMELESS;
98 uint64_t duration;
99 if( !trak->edts
100 || !trak->edts->elst
101 || !trak->edts->elst->list )
103 duration = trak->cache->fragment->largest_cts + trak->cache->fragment->last_duration;
104 duration = (uint64_t)(((double)duration / trak->mdia->mdhd->timescale) * file->moov->mvhd->timescale);
106 else
108 duration = 0;
109 for( lsmash_entry_t *elst_entry = trak->edts->elst->list->head; elst_entry; elst_entry = elst_entry->next )
111 isom_elst_entry_t *data = (isom_elst_entry_t *)elst_entry->data;
112 if( !data )
113 return LSMASH_ERR_NAMELESS;
114 if( data->segment_duration == ISOM_EDIT_DURATION_IMPLICIT )
116 uint64_t segment_duration = trak->cache->fragment->largest_cts + trak->cache->fragment->last_duration;
117 duration += (uint64_t)(((double)segment_duration / trak->mdia->mdhd->timescale) * file->moov->mvhd->timescale);
119 else
120 duration += data->segment_duration;
123 longest_duration = LSMASH_MAX( duration, longest_duration );
125 isom_mehd_t *mehd = file->moov->mvex->mehd;
126 mehd->fragment_duration = longest_duration;
127 mehd->version = 1;
128 mehd->manager &= ~(LSMASH_PLACEHOLDER | LSMASH_WRITTEN_BOX); /* Update per media segment. */
129 isom_update_box_size( mehd );
130 /* Write Movie Extends Header Box here. */
131 lsmash_bs_t *bs = file->bs;
132 uint64_t current_pos = bs->offset;
133 lsmash_bs_write_seek( bs, mehd->pos, SEEK_SET );
134 int ret = isom_write_box( bs, (isom_box_t *)mehd );
135 lsmash_bs_write_seek( bs, current_pos, SEEK_SET );
136 return ret;
139 static int isom_write_fragment_random_access_info( lsmash_file_t *file )
141 assert( file == file->initializer );
142 if( !file->mfra )
143 return 0;
144 if( !file->moov->mvex )
145 return LSMASH_ERR_NAMELESS;
146 /* Reconstruct the Movie Fragment Random Access Box.
147 * All 'time' field in the Track Fragment Random Access Boxes shall reflect edit list. */
148 uint32_t movie_timescale = lsmash_get_movie_timescale( file->root );
149 if( movie_timescale == 0 )
150 return LSMASH_ERR_NAMELESS; /* Division by zero will occur. */
151 for( lsmash_entry_t *trex_entry = file->moov->mvex->trex_list.head; trex_entry; trex_entry = trex_entry->next )
153 isom_trex_t *trex = (isom_trex_t *)trex_entry->data;
154 if( !trex )
155 return LSMASH_ERR_NAMELESS;
156 /* Get the edit list of the track associated with the trex->track_ID.
157 * If failed or absent, implicit timeline mapping edit is used, and skip this operation for the track. */
158 isom_trak_t *trak = isom_get_trak( file, trex->track_ID );
159 if( !trak )
160 return LSMASH_ERR_NAMELESS;
161 if( !trak->edts
162 || !trak->edts->elst
163 || !trak->edts->elst->list
164 || !trak->edts->elst->list->head
165 || !trak->edts->elst->list->head->data )
166 continue;
167 isom_elst_t *elst = trak->edts->elst;
168 /* Get the Track Fragment Random Access Boxes of the track associated with the trex->track_ID.
169 * If failed or absent, skip reconstructing the Track Fragment Random Access Box of the track. */
170 isom_tfra_t *tfra = isom_get_tfra( file->mfra, trex->track_ID );
171 if( !tfra )
172 continue;
173 /* Reconstruct the Track Fragment Random Access Box. */
174 lsmash_entry_t *edit_entry = elst->list->head;
175 isom_elst_entry_t *edit = edit_entry->data;
176 uint64_t edit_offset = 0; /* units in media timescale */
177 uint32_t media_timescale = lsmash_get_media_timescale( file->root, trex->track_ID );
178 for( lsmash_entry_t *rap_entry = tfra->list->head; rap_entry; )
180 isom_tfra_location_time_entry_t *rap = (isom_tfra_location_time_entry_t *)rap_entry->data;
181 if( !rap )
183 /* Irregular case. Drop this entry. */
184 lsmash_entry_t *next = rap_entry->next;
185 lsmash_remove_entry_direct( tfra->list, rap_entry, NULL );
186 rap_entry = next;
187 continue;
189 uint64_t composition_time = rap->time;
190 /* Skip edits that doesn't need the current sync sample indicated in the Track Fragment Random Access Box. */
191 while( edit )
193 uint64_t segment_duration = edit->segment_duration == ISOM_EDIT_DURATION_IMPLICIT
194 ? trak->cache->fragment->largest_cts + trak->cache->fragment->last_duration
195 : ((edit->segment_duration - 1) / movie_timescale + 1) * media_timescale;
196 if( edit->media_time != ISOM_EDIT_MODE_EMPTY
197 && composition_time < edit->media_time + segment_duration )
198 break; /* This Timeline Mapping Edit might require the current sync sample.
199 * Note: this condition doesn't cover all cases.
200 * For instance, matching the both following conditions
201 * 1. A sync sample isn't in the presentation.
202 * 2. The other samples, which precede it in the composition timeline, is in the presentation. */
203 edit_offset += segment_duration;
204 edit_entry = edit_entry->next;
205 if( !edit_entry )
207 /* No more presentation. */
208 edit = NULL;
209 break;
211 edit = edit_entry->data;
213 if( !edit )
215 /* No more presentation.
216 * Drop the rest of sync samples since they are generally absent in the whole presentation.
217 * Though the exceptions are sync samples with earlier composition time, we ignore them. (SAP type 2: TEPT = TDEC = TSAP < TPTF)
218 * To support this exception, we need sorting entries of the list by composition times. */
219 while( rap_entry )
221 lsmash_entry_t *next = rap_entry->next;
222 lsmash_remove_entry_direct( tfra->list, rap_entry, NULL );
223 rap_entry = next;
225 break;
227 /* If the sync sample isn't in the presentation,
228 * we pick the earliest presentation time of the current edit as its presentation time. */
229 rap->time = edit_offset;
230 if( composition_time >= edit->media_time )
231 rap->time += composition_time - edit->media_time;
232 rap_entry = rap_entry->next;
234 tfra->number_of_entry = tfra->list->entry_count;
236 /* Decide the size of the Movie Fragment Random Access Box. */
237 if( isom_update_box_size( file->mfra ) == 0 )
238 return LSMASH_ERR_NAMELESS;
239 /* Write the Movie Fragment Random Access Box. */
240 return isom_write_box( file->bs, (isom_box_t *)file->mfra );
243 static int isom_update_indexed_material_offset
245 lsmash_file_t *file,
246 isom_sidx_t *last_sidx
249 /* Update the size of each Segment Index Box. */
250 for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next )
252 isom_sidx_t *sidx = (isom_sidx_t *)entry->data;
253 if( !sidx )
254 continue;
255 if( isom_update_box_size( sidx ) == 0 )
256 return LSMASH_ERR_NAMELESS;
258 /* first_offset: the sum of the size of subsequent Segment Index Boxes
259 * Be careful about changing the size of them. */
260 last_sidx->first_offset = 0;
261 for( lsmash_entry_t *a_entry = file->sidx_list.head; a_entry && a_entry->data != last_sidx; a_entry = a_entry->next )
263 isom_sidx_t *a = (isom_sidx_t *)a_entry->data;
264 a->first_offset = 0;
265 for( lsmash_entry_t *b_entry = a_entry->next; b_entry; b_entry = b_entry->next )
267 isom_sidx_t *b = (isom_sidx_t *)b_entry->data;
268 a->first_offset += b->size;
271 return 0;
274 static int isom_write_segment_indexes
276 lsmash_file_t *file,
277 lsmash_adhoc_remux_t *remux
280 /* Update the size of each Segment Index Box and establish the offset from the anchor point to the indexed material. */
281 int ret;
282 if( (ret = isom_update_indexed_material_offset( file, (isom_sidx_t *)file->sidx_list.tail->data )) < 0 )
283 return ret;
284 /* Get the total size of all Segment Index Boxes. */
285 uint64_t total_sidx_size = 0;
286 for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next )
288 isom_sidx_t *sidx = (isom_sidx_t *)entry->data;
289 if( !sidx )
290 continue;
291 total_sidx_size += sidx->size;
293 /* The buffer size must be at least total_sidx_size * 2. */
294 size_t buffer_size = total_sidx_size * 2;
295 if( remux->buffer_size > buffer_size )
296 buffer_size = remux->buffer_size;
297 /* Split to 2 buffers. */
298 uint8_t *buf[2] = { NULL, NULL };
299 if( (buf[0] = (uint8_t *)lsmash_malloc( buffer_size )) == NULL )
300 return LSMASH_ERR_MEMORY_ALLOC;
301 size_t size = buffer_size / 2;
302 buf[1] = buf[0] + size;
303 /* Seek to the beginning of the first Movie Fragment Box i.e. the first subsegment within this media segment. */
304 lsmash_bs_t *bs = file->bs;
305 int64_t ret64;
306 if( (ret64 = lsmash_bs_write_seek( bs, file->fragment->first_moof_pos, SEEK_SET )) < 0 )
308 ret = ret64;
309 goto fail;
311 size_t read_num = size;
312 lsmash_bs_read_data( bs, buf[0], &read_num );
313 uint64_t read_pos = bs->offset;
314 /* Write the Segment Index Boxes actually here. */
315 if( (ret64 = lsmash_bs_write_seek( bs, file->fragment->first_moof_pos, SEEK_SET )) < 0 )
317 ret = ret64;
318 goto fail;
320 for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next )
322 isom_sidx_t *sidx = (isom_sidx_t *)entry->data;
323 if( !sidx )
324 continue;
325 if( (ret = isom_write_box( file->bs, (isom_box_t *)sidx )) < 0 )
326 goto fail;
328 /* Rearrange subsequent data. */
329 uint64_t write_pos = bs->offset;
330 uint64_t total = file->size + total_sidx_size;
331 if( (ret = isom_rearrange_data( file, remux, buf, read_num, size, read_pos, write_pos, total )) < 0 )
332 goto fail;
333 file->size += total_sidx_size;
334 lsmash_freep( &buf[0] );
335 /* Update 'moof_offset' of each entry within the Track Fragment Random Access Boxes. */
336 if( file->mfra )
337 for( lsmash_entry_t *entry = file->mfra->tfra_list.head; entry; entry = entry->next )
339 isom_tfra_t *tfra = (isom_tfra_t *)entry->data;
340 if( !tfra )
341 continue;
342 for( lsmash_entry_t *rap_entry = tfra->list->head; rap_entry; rap_entry = rap_entry->next )
344 isom_tfra_location_time_entry_t *rap = (isom_tfra_location_time_entry_t *)rap_entry->data;
345 if( !rap )
346 continue;
347 rap->moof_offset += total_sidx_size;
350 return 0;
351 fail:
352 lsmash_free( buf[0] );
353 return ret;
356 int isom_finish_final_fragment_movie
358 lsmash_file_t *file,
359 lsmash_adhoc_remux_t *remux
362 /* Output the final movie fragment. */
363 int ret;
364 if( (ret = isom_finish_fragment_movie( file )) < 0 )
365 return ret;
366 if( file->bs->unseekable )
367 return 0;
368 /* Write Segment Index Boxes.
369 * This occurs only when the initial movie has no samples.
370 * We don't consider updating of chunk offsets within initial movie sample table here.
371 * This is reasonable since DASH requires no samples in the initial movie.
372 * This implementation is not suitable for live-streaming.
373 + To support live-streaming, it is good to use daisy-chained index. */
374 if( (file->flags & LSMASH_FILE_MODE_MEDIA)
375 && (file->flags & LSMASH_FILE_MODE_INDEX)
376 && (file->flags & LSMASH_FILE_MODE_SEGMENT) )
378 if( !remux )
379 return LSMASH_ERR_FUNCTION_PARAM;
380 if( (ret = isom_write_segment_indexes( file, remux )) < 0 )
381 return ret;
383 /* Write the overall random access information at the tail of the movie if this file is self-contained. */
384 if( (ret = isom_write_fragment_random_access_info( file->initializer )) < 0 )
385 return ret;
386 /* Set overall duration of the movie. */
387 return isom_set_fragment_overall_duration( file->initializer );
390 #define GET_MOST_USED( box_name, index, flag_name ) \
391 if( most_used[index] < stats.flag_name[i] ) \
393 most_used[index] = stats.flag_name[i]; \
394 box_name->default_sample_flags.flag_name = i; \
397 static int isom_create_fragment_overall_default_settings( lsmash_file_t *file )
399 assert( file == file->initializer );
400 if( !isom_add_mvex( file->moov ) )
401 return LSMASH_ERR_NAMELESS;
402 if( !file->bs->unseekable )
404 if( !isom_add_mehd( file->moov->mvex ) )
405 return LSMASH_ERR_NAMELESS;
406 file->moov->mvex->mehd->manager |= LSMASH_PLACEHOLDER;
408 for( lsmash_entry_t *trak_entry = file->moov->trak_list.head; trak_entry; trak_entry = trak_entry->next )
410 isom_trak_t *trak = (isom_trak_t *)trak_entry->data;
411 if( !trak
412 || !trak->cache
413 || !trak->tkhd
414 || !trak->mdia
415 || !trak->mdia->minf
416 || !trak->mdia->minf->stbl )
417 return LSMASH_ERR_NAMELESS;
418 isom_stbl_t *stbl = trak->mdia->minf->stbl;
419 if( !stbl->stts || !stbl->stts->list
420 || !stbl->stsz
421 || (stbl->stts->list->tail && !stbl->stts->list->tail->data)
422 || (stbl->stsz->list && stbl->stsz->list->head && !stbl->stsz->list->head->data) )
423 return LSMASH_ERR_NAMELESS;
424 isom_trex_t *trex = isom_add_trex( file->moov->mvex );
425 if( !trex )
426 return LSMASH_ERR_NAMELESS;
427 trex->track_ID = trak->tkhd->track_ID;
428 /* Set up defaults. */
429 trex->default_sample_description_index = trak->cache->chunk.sample_description_index
430 ? trak->cache->chunk.sample_description_index
431 : 1;
432 trex->default_sample_duration = stbl->stts->list->tail
433 ? ((isom_stts_entry_t *)stbl->stts->list->tail->data)->sample_delta
434 : 1;
435 trex->default_sample_size = !stbl->stsz->list
436 ? stbl->stsz->sample_size : stbl->stsz->list->head
437 ? ((isom_stsz_entry_t *)stbl->stsz->list->head->data)->entry_size : 0;
438 if( stbl->sdtp
439 && stbl->sdtp->list )
441 struct sample_flags_stats_t
443 uint32_t is_leading [4];
444 uint32_t sample_depends_on [4];
445 uint32_t sample_is_depended_on[4];
446 uint32_t sample_has_redundancy[4];
447 } stats = { { 0 }, { 0 }, { 0 }, { 0 } };
448 for( lsmash_entry_t *sdtp_entry = stbl->sdtp->list->head; sdtp_entry; sdtp_entry = sdtp_entry->next )
450 isom_sdtp_entry_t *data = (isom_sdtp_entry_t *)sdtp_entry->data;
451 if( !data )
452 return LSMASH_ERR_NAMELESS;
453 ++ stats.is_leading [ data->is_leading ];
454 ++ stats.sample_depends_on [ data->sample_depends_on ];
455 ++ stats.sample_is_depended_on[ data->sample_is_depended_on ];
456 ++ stats.sample_has_redundancy[ data->sample_has_redundancy ];
458 uint32_t most_used[4] = { 0, 0, 0, 0 };
459 for( int i = 0; i < 4; i++ )
461 GET_MOST_USED( trex, 0, is_leading );
462 GET_MOST_USED( trex, 1, sample_depends_on );
463 GET_MOST_USED( trex, 2, sample_is_depended_on );
464 GET_MOST_USED( trex, 3, sample_has_redundancy );
467 trex->default_sample_flags.sample_is_non_sync_sample = !trak->cache->all_sync;
469 return 0;
472 static int isom_prepare_random_access_info( lsmash_file_t *file )
474 assert( file == file->initializer );
475 /* Don't write the random access info at the end of the file if unseekable or not self-contained. */
476 if( file->bs->unseekable
477 || !(file->flags & LSMASH_FILE_MODE_BOX)
478 || !(file->flags & LSMASH_FILE_MODE_INITIALIZATION)
479 || !(file->flags & LSMASH_FILE_MODE_MEDIA)
480 || (file->flags & LSMASH_FILE_MODE_SEGMENT) )
481 return 0;
482 if( !isom_add_mfra( file )
483 || !isom_add_mfro( file->mfra ) )
484 return LSMASH_ERR_NAMELESS;
485 return 0;
488 static int isom_output_fragment_media_data( lsmash_file_t *file )
490 isom_fragment_manager_t *fragment = file->fragment;
491 /* If there is no available Media Data Box to write samples, add and write a new one. */
492 if( fragment->sample_count )
494 if( !file->mdat && !isom_add_mdat( file ) )
495 return LSMASH_ERR_NAMELESS;
496 file->mdat->manager &= ~(LSMASH_INCOMPLETE_BOX | LSMASH_WRITTEN_BOX);
497 int ret = isom_write_box( file->bs, (isom_box_t *)file->mdat );
498 if( ret < 0 )
499 return ret;
500 file->size += file->mdat->size;
501 file->mdat->size = 0;
502 file->mdat->media_size = 0;
504 lsmash_remove_entries( fragment->pool, isom_remove_sample_pool );
505 fragment->pool_size = 0;
506 fragment->sample_count = 0;
507 return 0;
510 static int isom_finish_fragment_initial_movie( lsmash_file_t *file )
512 assert( file == file->initializer );
513 if( !file->moov )
514 return LSMASH_ERR_NAMELESS;
515 isom_moov_t *moov = file->moov;
516 int ret;
517 for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next )
519 isom_trak_t *trak = (isom_trak_t *)entry->data;
520 if( !trak
521 || !trak->cache
522 || !trak->tkhd
523 || !trak->mdia
524 || !trak->mdia->mdhd
525 || !trak->mdia->minf
526 || !trak->mdia->minf->stbl )
527 return LSMASH_ERR_NAMELESS;
528 if( (ret = isom_complement_data_reference( trak->mdia->minf )) < 0 )
529 return ret;
530 isom_stbl_t *stbl = trak->mdia->minf->stbl;
531 if( isom_get_sample_count( trak ) )
533 /* Add stss box if any samples aren't sync sample. */
534 if( !trak->cache->all_sync && !stbl->stss && !isom_add_stss( stbl ) )
535 return -1;
536 if( (ret = isom_update_tkhd_duration( trak )) < 0 )
537 return ret;
539 else
540 trak->tkhd->duration = 0;
541 if( (ret = isom_update_bitrate_description( trak->mdia )) < 0 )
542 return ret;
543 /* Complete the last sample groups within tracks in the initial movie. */
544 if( trak->cache->rap )
546 isom_sgpd_t *sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP );
547 if( !sgpd )
548 return LSMASH_ERR_NAMELESS;
549 if( (ret = isom_rap_grouping_established( trak->cache->rap, 1, sgpd, 0 )) < 0 )
550 return ret;
551 lsmash_freep( &trak->cache->rap );
553 if( trak->cache->roll.pool )
555 isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &stbl->sbgp_list );
556 if( !sbgp )
557 return LSMASH_ERR_NAMELESS;
558 if( (ret = isom_all_recovery_completed( sbgp, trak->cache->roll.pool )) < 0 )
559 return ret;
562 if( file->mp4_version1 == 1 && (ret = isom_setup_iods( moov )) < 0 )
563 return ret;
564 if( (ret = isom_create_fragment_overall_default_settings( file )) < 0
565 || (ret = isom_prepare_random_access_info ( file )) < 0
566 || (ret = isom_establish_movie ( file )) < 0 )
567 return ret;
568 /* stco->co64 conversion, depending on last chunk's offset */
569 uint64_t meta_size = file->meta ? file->meta->size : 0;
570 if( (ret = isom_check_large_offset_requirement( moov, meta_size )) < 0 )
571 return ret;
572 /* Now, the amount of the offset is fixed. apply it to stco/co64 */
573 uint64_t preceding_size = moov->size + meta_size;
574 isom_add_preceding_box_size( moov, preceding_size );
575 /* Write File Type Box here if it was not written yet. */
576 if( file->ftyp && !(file->ftyp->manager & LSMASH_WRITTEN_BOX) )
578 if( isom_write_box( file->bs, (isom_box_t *)file->ftyp ) < 0 )
579 return -1;
580 file->size += file->ftyp->size;
582 /* Write Movie Box. */
583 if( (ret = isom_write_box( file->bs, (isom_box_t *)file->moov )) < 0
584 || (ret = isom_write_box( file->bs, (isom_box_t *)file->meta )) < 0 )
585 return ret;
586 file->size += preceding_size;
587 /* Output samples. */
588 if( (ret = isom_output_fragment_media_data( file )) < 0 )
589 return ret;
590 /* Revert the number of samples in tracks to 0. */
591 for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next )
593 isom_trak_t *trak = (isom_trak_t *)entry->data;
594 if( trak->cache->fragment )
595 trak->cache->fragment->sample_count = 0;
597 return 0;
600 /* Return 1 if there is diffrence, otherwise return 0. */
601 static int isom_compare_sample_flags( isom_sample_flags_t *a, isom_sample_flags_t *b )
603 return (a->reserved != b->reserved)
604 || (a->is_leading != b->is_leading)
605 || (a->sample_depends_on != b->sample_depends_on)
606 || (a->sample_is_depended_on != b->sample_is_depended_on)
607 || (a->sample_has_redundancy != b->sample_has_redundancy)
608 || (a->sample_padding_value != b->sample_padding_value)
609 || (a->sample_is_non_sync_sample != b->sample_is_non_sync_sample)
610 || (a->sample_degradation_priority != b->sample_degradation_priority);
613 static int isom_make_segment_index_entry
615 lsmash_file_t *file,
616 isom_moof_t *moof
619 /* Make the index of this subsegment. */
620 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
622 isom_traf_t *traf = (isom_traf_t *)entry->data;
623 isom_tfhd_t *tfhd = traf->tfhd;
624 isom_fragment_t *track_fragment = traf->cache->fragment;
625 isom_subsegment_t *subsegment = &track_fragment->subsegment;
626 isom_sidx_t *sidx = isom_get_sidx( file, tfhd->track_ID );
627 isom_trak_t *trak = isom_get_trak( file->initializer, tfhd->track_ID );
628 if( !trak
629 || !trak->mdia
630 || !trak->mdia->mdhd )
631 return LSMASH_ERR_NAMELESS;
632 assert( traf->tfdt );
633 if( !sidx )
635 sidx = isom_add_sidx( file );
636 if( !sidx )
637 return LSMASH_ERR_NAMELESS;
638 sidx->reference_ID = tfhd->track_ID;
639 sidx->timescale = trak->mdia->mdhd->timescale;
640 sidx->reserved = 0;
641 sidx->reference_count = 0;
642 int ret = isom_update_indexed_material_offset( file, sidx );
643 if( ret < 0 )
644 return ret;
646 /* One pair of a Movie Fragment Box with an associated Media Box per subsegment. */
647 isom_sidx_referenced_item_t *data = lsmash_malloc( sizeof(isom_sidx_referenced_item_t) );
648 if( !data )
649 return LSMASH_ERR_NAMELESS;
650 if( lsmash_add_entry( sidx->list, data ) < 0 )
652 lsmash_free( data );
653 return LSMASH_ERR_MEMORY_ALLOC;
655 sidx->reference_count = sidx->list->entry_count;
656 data->reference_type = 0; /* media */
657 data->reference_size = file->size - moof->pos;
658 /* presentation */
659 uint64_t TSAP;
660 uint64_t TDEC;
661 uint64_t TEPT;
662 uint64_t TPTF;
663 uint64_t composition_duration = subsegment->largest_cts - subsegment->smallest_cts + track_fragment->last_duration;
664 int subsegment_in_presentation; /* If set to 1, TEPT is available. */
665 int first_rp_in_presentation; /* If set to 1, both TSAP and TDEC are available. */
666 int first_sample_in_presentation; /* If set to 1, TPTF is available. */
667 if( trak->edts && trak->edts->elst && trak->edts->elst->list )
669 /**-- Explicit edits --**/
670 const isom_elst_t *elst = trak->edts->elst;
671 const isom_elst_entry_t *edit = NULL;
672 uint32_t movie_timescale = file->initializer->moov->mvhd->timescale;
673 uint64_t pts = subsegment->segment_duration;
674 /* This initialization is redundant since these are unused when uninitialized
675 * and are initialized always in used cases, but unclever compilers may
676 * complain that these variables may be uninitialized. */
677 TSAP = 0;
678 TDEC = 0;
679 TEPT = 0;
680 TPTF = 0;
681 /* */
682 subsegment_in_presentation = 0;
683 first_rp_in_presentation = 0;
684 first_sample_in_presentation = 0;
685 for( lsmash_entry_t *elst_entry = elst->list->head; elst_entry; elst_entry = elst_entry->next )
687 edit = (isom_elst_entry_t *)elst_entry->data;
688 if( !edit )
689 continue;
690 uint64_t edit_end_pts;
691 uint64_t edit_end_cts;
692 if( edit->segment_duration == ISOM_EDIT_DURATION_IMPLICIT
693 || (elst->version == 0 && edit->segment_duration == ISOM_EDIT_DURATION_UNKNOWN32)
694 || (elst->version == 1 && edit->segment_duration == ISOM_EDIT_DURATION_UNKNOWN64) )
696 edit_end_cts = UINT64_MAX;
697 edit_end_pts = UINT64_MAX;
699 else
701 if( edit->segment_duration )
703 double segment_duration = edit->segment_duration * ((double)sidx->timescale / movie_timescale);
704 edit_end_cts = edit->media_time + (uint64_t)(segment_duration * ((double)edit->media_rate / (1 << 16)));
705 edit_end_pts = pts + (uint64_t)segment_duration;
707 else
709 uint64_t segment_duration = composition_duration;
710 if( edit->media_time > subsegment->smallest_cts )
712 if( subsegment->largest_cts + track_fragment->last_duration > edit->media_time )
713 segment_duration -= edit->media_time - subsegment->smallest_cts;
714 else
715 segment_duration = 0;
717 edit_end_cts = edit->media_time + (uint64_t)(segment_duration * ((double)edit->media_rate / (1 << 16)));
718 edit_end_pts = pts + (uint64_t)segment_duration;
721 if( edit->media_time == ISOM_EDIT_MODE_EMPTY )
723 pts = edit_end_pts;
724 continue;
726 if( (subsegment->smallest_cts >= edit->media_time && subsegment->smallest_cts < edit_end_cts)
727 || (subsegment->largest_cts >= edit->media_time && subsegment->largest_cts < edit_end_cts) )
729 /* This subsegment is present in this edit. */
730 double rate = (double)edit->media_rate / (1 << 16);
731 uint64_t start_time = LSMASH_MAX( subsegment->smallest_cts, edit->media_time );
732 if( sidx->reference_count == 1 )
733 sidx->earliest_presentation_time = pts;
734 if( subsegment_in_presentation == 0 )
736 subsegment_in_presentation = 1;
737 if( subsegment->smallest_cts >= edit->media_time )
738 TEPT = pts + (uint64_t)((subsegment->smallest_cts - start_time) / rate);
739 else
740 TEPT = pts;
742 if( first_rp_in_presentation == 0
743 && ((subsegment->first_ed_cts >= edit->media_time && subsegment->first_ed_cts < edit_end_cts)
744 || (subsegment->first_rp_cts >= edit->media_time && subsegment->first_rp_cts < edit_end_cts)) )
746 /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */
747 first_rp_in_presentation = 1;
748 if( subsegment->first_ed_cts >= edit->media_time && subsegment->first_ed_cts < edit_end_cts )
749 TSAP = pts + (uint64_t)((subsegment->first_ed_cts - start_time) / rate);
750 else
751 TSAP = pts;
752 TDEC = TSAP;
754 if( first_sample_in_presentation == 0
755 && subsegment->first_cts >= edit->media_time && subsegment->first_cts < edit_end_cts )
757 first_sample_in_presentation = 1;
758 TPTF = pts + (uint64_t)((subsegment->first_cts - start_time) / rate);
760 uint64_t subsegment_end_pts = pts + (uint64_t)(composition_duration / rate);
761 pts = LSMASH_MIN( edit_end_pts, subsegment_end_pts );
762 /* Update subsegment_duration. */
763 data->subsegment_duration = pts - subsegment->segment_duration;
765 else
766 /* This subsegment is not present in this edit. */
767 pts = edit_end_pts;
770 else
772 /**-- Implicit edit --**/
773 if( sidx->reference_count == 1 )
774 sidx->earliest_presentation_time = subsegment->smallest_cts;
775 data->subsegment_duration = composition_duration;
776 /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */
777 TSAP = subsegment->first_rp_cts;
778 TDEC = subsegment->first_rp_cts;
779 TEPT = subsegment->smallest_cts;
780 TPTF = subsegment->first_cts;
781 subsegment_in_presentation = 1;
782 first_rp_in_presentation = 1;
783 first_sample_in_presentation = 1;
785 if( subsegment->first_ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE
786 || subsegment->first_ra_number == 0
787 || subsegment->first_rp_number == 0
788 || subsegment_in_presentation == 0
789 || first_rp_in_presentation == 0 )
791 /* No SAP in this subsegment. */
792 data->starts_with_SAP = 0;
793 data->SAP_type = 0;
794 data->SAP_delta_time = 0;
796 else
798 data->starts_with_SAP = (subsegment->first_ra_number == 1);
799 data->SAP_type = 0;
800 data->SAP_delta_time = TSAP - TEPT;
801 /* Decide SAP_type. */
802 if( first_sample_in_presentation )
804 if( TEPT == TDEC && TDEC == TSAP && TSAP == TPTF )
805 data->SAP_type = 1;
806 else if( TEPT == TDEC && TDEC == TSAP && TSAP < TPTF )
807 data->SAP_type = 2;
808 else if( TEPT < TDEC && TDEC == TSAP && TSAP <= TPTF )
809 data->SAP_type = 3;
810 else if( TEPT <= TPTF && TPTF < TDEC && TDEC == TSAP )
811 data->SAP_type = 4;
813 if( data->SAP_type == 0 )
815 if( TEPT == TDEC && TDEC < TSAP )
816 data->SAP_type = 5;
817 else if( TEPT < TDEC && TDEC < TSAP )
818 data->SAP_type = 6;
821 subsegment->segment_duration += data->subsegment_duration;
822 subsegment->first_ed_cts = UINT64_MAX;
823 subsegment->first_rp_cts = UINT64_MAX;
824 subsegment->first_rp_number = 0;
825 subsegment->first_ra_number = 0;
826 subsegment->first_ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE;
827 subsegment->decodable = 0;
829 return 0;
832 static int isom_finish_fragment_movie
834 lsmash_file_t *file
837 if( !file->fragment
838 || !file->fragment->pool )
839 return LSMASH_ERR_NAMELESS;
840 isom_moof_t *moof = file->fragment->movie;
841 if( !moof )
843 if( file == file->initializer )
844 return isom_finish_fragment_initial_movie( file );
845 else
846 return 0; /* No movie fragment to be finished. */
848 /* Don't write the current movie fragment if containing no track fragments.
849 * This is a requirement of DASH Media Segment. */
850 if( !moof->traf_list.head
851 || !moof->traf_list.head->data )
852 return 0;
853 /* Calculate appropriate default_sample_flags of each Track Fragment Header Box.
854 * And check whether that default_sample_flags is useful or not. */
855 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
857 isom_traf_t *traf = (isom_traf_t *)entry->data;
858 if( !traf
859 || !traf->tfhd
860 || !traf->file
861 || !traf->file->initializer
862 || !traf->file->initializer->moov
863 || !traf->file->initializer->moov->mvex )
864 return LSMASH_ERR_NAMELESS;
865 isom_tfhd_t *tfhd = traf->tfhd;
866 isom_trex_t *trex = isom_get_trex( file->initializer->moov->mvex, tfhd->track_ID );
867 if( !trex )
868 return LSMASH_ERR_NAMELESS;
869 struct sample_flags_stats_t
871 uint32_t is_leading [4];
872 uint32_t sample_depends_on [4];
873 uint32_t sample_is_depended_on [4];
874 uint32_t sample_has_redundancy [4];
875 uint32_t sample_is_non_sync_sample[2];
876 } stats = { { 0 }, { 0 }, { 0 }, { 0 }, { 0 } };
877 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
879 isom_trun_t *trun = (isom_trun_t *)trun_entry->data;
880 if( !trun || trun->sample_count == 0 )
881 return LSMASH_ERR_NAMELESS;
882 isom_sample_flags_t *sample_flags;
883 if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT )
885 if( !trun->optional )
886 return LSMASH_ERR_NAMELESS;
887 for( lsmash_entry_t *optional_entry = trun->optional->head; optional_entry; optional_entry = optional_entry->next )
889 isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data;
890 if( !row )
891 return LSMASH_ERR_NAMELESS;
892 sample_flags = &row->sample_flags;
893 ++ stats.is_leading [ sample_flags->is_leading ];
894 ++ stats.sample_depends_on [ sample_flags->sample_depends_on ];
895 ++ stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ];
896 ++ stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ];
897 ++ stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ];
900 else
902 sample_flags = &tfhd->default_sample_flags;
903 stats.is_leading [ sample_flags->is_leading ] += trun->sample_count;
904 stats.sample_depends_on [ sample_flags->sample_depends_on ] += trun->sample_count;
905 stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ] += trun->sample_count;
906 stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ] += trun->sample_count;
907 stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ] += trun->sample_count;
910 uint32_t most_used[5] = { 0, 0, 0, 0, 0 };
911 for( int i = 0; i < 4; i++ )
913 GET_MOST_USED( tfhd, 0, is_leading );
914 GET_MOST_USED( tfhd, 1, sample_depends_on );
915 GET_MOST_USED( tfhd, 2, sample_is_depended_on );
916 GET_MOST_USED( tfhd, 3, sample_has_redundancy );
917 if( i < 2 )
918 GET_MOST_USED( tfhd, 4, sample_is_non_sync_sample );
920 int useful_default_sample_duration = 0;
921 int useful_default_sample_size = 0;
922 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
924 isom_trun_t *trun = (isom_trun_t *)trun_entry->data;
925 if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT) )
926 useful_default_sample_duration = 1;
927 if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT) )
928 useful_default_sample_size = 1;
929 int useful_first_sample_flags = 1;
930 int useful_default_sample_flags = 1;
931 if( trun->sample_count == 1 )
933 /* It is enough to check only if first_sample_flags equals default_sample_flags or not.
934 * If it is equal, just use default_sample_flags.
935 * If not, just use first_sample_flags of this run. */
936 if( !isom_compare_sample_flags( &trun->first_sample_flags, &tfhd->default_sample_flags ) )
937 useful_first_sample_flags = 0;
939 else if( trun->optional
940 && trun->optional->head )
942 lsmash_entry_t *optional_entry = trun->optional->head->next;
943 isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data;
944 isom_sample_flags_t representative_sample_flags = row->sample_flags;
945 if( isom_compare_sample_flags( &tfhd->default_sample_flags, &representative_sample_flags ) )
946 useful_default_sample_flags = 0;
947 if( !isom_compare_sample_flags( &trun->first_sample_flags, &representative_sample_flags ) )
948 useful_first_sample_flags = 0;
949 if( useful_default_sample_flags )
950 for( optional_entry = optional_entry->next; optional_entry; optional_entry = optional_entry->next )
952 row = (isom_trun_optional_row_t *)optional_entry->data;
953 if( isom_compare_sample_flags( &representative_sample_flags, &row->sample_flags ) )
955 useful_default_sample_flags = 0;
956 break;
960 if( useful_default_sample_flags )
962 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT;
963 trun->flags &= ~ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT;
965 else
967 useful_first_sample_flags = 0;
968 trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT;
970 if( useful_first_sample_flags )
971 trun->flags |= ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT;
973 if( useful_default_sample_duration && tfhd->default_sample_duration != trex->default_sample_duration )
974 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
975 else
976 tfhd->default_sample_duration = trex->default_sample_duration; /* This might be redundant, but is to be more natural. */
977 if( useful_default_sample_size && tfhd->default_sample_size != trex->default_sample_size )
978 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT;
979 else
980 tfhd->default_sample_size = trex->default_sample_size; /* This might be redundant, but is to be more natural. */
981 if( !(tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT) )
982 tfhd->default_sample_flags = trex->default_sample_flags; /* This might be redundant, but is to be more natural. */
983 else if( !isom_compare_sample_flags( &tfhd->default_sample_flags, &trex->default_sample_flags ) )
984 tfhd->flags &= ~ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT;
986 /* Complete the last sample groups in the previous track fragments. */
987 int ret;
988 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
990 isom_traf_t *traf = (isom_traf_t *)entry->data;
991 if( traf->cache->rap )
993 isom_sgpd_t *sgpd = isom_get_fragment_sample_group_description( traf, ISOM_GROUP_TYPE_RAP );
994 if( !sgpd )
995 return LSMASH_ERR_NAMELESS;
996 if( (ret = isom_rap_grouping_established( traf->cache->rap, 1, sgpd, 1 )) < 0 )
997 return ret;
998 lsmash_freep( &traf->cache->rap );
1000 if( traf->cache->roll.pool )
1002 isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &traf->sbgp_list );
1003 if( !sbgp )
1004 return LSMASH_ERR_NAMELESS;
1005 if( (ret = isom_all_recovery_completed( sbgp, traf->cache->roll.pool )) < 0 )
1006 return ret;
1009 /* Establish Movie Fragment Box.
1010 * We write exactly one Media Data Box starting immediately after the corresponding Movie Fragment Box. */
1011 if( file->allow_moof_base )
1013 /* In this branch, we use default-base-is-moof flag, which indicates implicit base_data_offsets originate in the
1014 * first byte of each enclosing Movie Fragment Box.
1015 * 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
1016 * the type field of it as the data_offset of the first track run like the following.
1018 * _____________ _ offset := 0
1019 * | | |
1020 * | m | s i z e |
1021 * | |_________|
1022 * | o | |
1023 * | | t y p e |
1024 * | o |_________|
1025 * | | |
1026 * | f | d a t a |
1027 * |___|_________|_ offset := the size of the Movie Fragment Box
1028 * | | |
1029 * | m | s i z e |
1030 * | |_________|
1031 * | d | |
1032 * | | t y p e |
1033 * | a |_________|_ offset := the data_offset of the first track run
1034 * | | |
1035 * | t | d a t a |
1036 * |___|_________|_ offset := the size of a subsegment containing exactly one movie fragment
1038 * For a pair of one Movie Fragment Box and one Media Data Box, placed in this order, implicit base_data_offsets
1039 * indicated by the absence of both base-data-offset-present and default-base-is-moof are somewhat complicated
1040 * since the implicit base_data_offset of the current track fragment is defined by the end of the data of the
1041 * previous track fragment and the data_offset of the track runs could be negative value because of interleaving
1042 * track runs or something other reasons.
1043 * In contrast, implicit base_data_offsets indicated by default-base-is-moof are simple since the base_data_offset
1044 * of each track fragment is always constant for that pair and has no dependency on other track fragments.
1046 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1048 isom_traf_t *traf = (isom_traf_t *)entry->data;
1049 traf->tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_BASE_IS_MOOF;
1050 traf->tfhd->base_data_offset = file->size; /* not written actually though */
1051 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
1053 /* Here, data_offset is always greater than zero. */
1054 isom_trun_t *trun = trun_entry->data;
1055 trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT;
1058 /* Consider the update of tr_flags here. */
1059 if( isom_update_box_size( moof ) == 0 )
1060 return LSMASH_ERR_NAMELESS;
1061 /* Now, we can calculate offsets in the current movie fragment, so do it. */
1062 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1064 isom_traf_t *traf = (isom_traf_t *)entry->data;
1065 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
1067 isom_trun_t *trun = trun_entry->data;
1068 trun->data_offset += moof->size + ISOM_BASEBOX_COMMON_SIZE;
1072 else
1074 /* In this branch, we use explicit base_data_offset. */
1075 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1077 isom_traf_t *traf = (isom_traf_t *)entry->data;
1078 traf->tfhd->flags |= ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT;
1080 /* Consider the update of tf_flags here. */
1081 if( isom_update_box_size( moof ) == 0 )
1082 return LSMASH_ERR_NAMELESS;
1083 /* Now, we can calculate offsets in the current movie fragment, so do it. */
1084 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1086 isom_traf_t *traf = (isom_traf_t *)entry->data;
1087 traf->tfhd->base_data_offset = file->size + moof->size + ISOM_BASEBOX_COMMON_SIZE;
1090 /* Write Movie Fragment Box and its children. */
1091 moof->pos = file->size;
1092 if( (ret = isom_write_box( file->bs, (isom_box_t *)moof )) < 0 )
1093 return ret;
1094 if( file->fragment->first_moof_pos == FIRST_MOOF_POS_UNDETERMINED )
1095 file->fragment->first_moof_pos = moof->pos;
1096 file->size += moof->size;
1097 /* Output samples. */
1098 if( (ret = isom_output_fragment_media_data( file )) < 0 )
1099 return ret;
1100 /* Revert the number of samples in track fragments to 0. */
1101 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1103 isom_traf_t *traf = (isom_traf_t *)entry->data;
1104 if( traf->cache->fragment )
1105 traf->cache->fragment->sample_count = 0;
1107 if( !(file->flags & LSMASH_FILE_MODE_INDEX) || file->max_isom_version < 6 )
1108 return 0;
1109 return isom_make_segment_index_entry( file, moof );
1112 #undef GET_MOST_USED
1114 static isom_trun_optional_row_t *isom_request_trun_optional_row( isom_trun_t *trun, isom_tfhd_t *tfhd, uint32_t sample_number )
1116 isom_trun_optional_row_t *row = NULL;
1117 if( !trun->optional )
1119 trun->optional = lsmash_create_entry_list();
1120 if( !trun->optional )
1121 return NULL;
1123 if( trun->optional->entry_count < sample_number )
1125 while( trun->optional->entry_count < sample_number )
1127 row = lsmash_malloc( sizeof(isom_trun_optional_row_t) );
1128 if( !row )
1129 return NULL;
1130 /* Copy from default. */
1131 row->sample_duration = tfhd->default_sample_duration;
1132 row->sample_size = tfhd->default_sample_size;
1133 row->sample_flags = tfhd->default_sample_flags;
1134 row->sample_composition_time_offset = 0;
1135 if( lsmash_add_entry( trun->optional, row ) < 0 )
1137 lsmash_free( row );
1138 return NULL;
1141 return row;
1143 uint32_t i = 0;
1144 for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next )
1146 row = (isom_trun_optional_row_t *)entry->data;
1147 if( !row )
1148 return NULL;
1149 if( ++i == sample_number )
1150 return row;
1152 return NULL;
1155 int lsmash_create_fragment_empty_duration
1157 lsmash_root_t *root,
1158 uint32_t track_ID,
1159 uint32_t duration
1162 if( isom_check_initializer_present( root ) < 0 )
1163 return LSMASH_ERR_FUNCTION_PARAM;
1164 lsmash_file_t *file = root->file;
1165 if( !file->fragment
1166 || !file->fragment->movie
1167 || !file->initializer->moov )
1168 return LSMASH_ERR_NAMELESS;
1169 isom_trak_t *trak = isom_get_trak( file->initializer, track_ID );
1170 if( !trak
1171 || !trak->tkhd )
1172 return LSMASH_ERR_NAMELESS;
1173 isom_trex_t *trex = isom_get_trex( file->initializer->moov->mvex, track_ID );
1174 if( !trex )
1175 return LSMASH_ERR_NAMELESS;
1176 isom_moof_t *moof = file->fragment->movie;
1177 isom_traf_t *traf = isom_get_traf( moof, track_ID );
1178 if( traf )
1179 return LSMASH_ERR_NAMELESS;
1180 traf = isom_add_traf( moof );
1181 if( !isom_add_tfhd( traf ) )
1182 return LSMASH_ERR_NAMELESS;
1183 isom_tfhd_t *tfhd = traf->tfhd;
1184 tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */
1185 tfhd->track_ID = trak->tkhd->track_ID;
1186 tfhd->default_sample_duration = duration;
1187 if( duration != trex->default_sample_duration )
1188 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1189 traf->cache = trak->cache;
1190 traf->cache->fragment->traf_number = moof->traf_list.entry_count;
1191 traf->cache->fragment->last_duration += duration; /* The duration of the last sample includes this empty-duration. */
1192 return 0;
1195 int isom_set_fragment_last_duration
1197 isom_traf_t *traf,
1198 uint32_t last_duration
1201 isom_tfhd_t *tfhd = traf->tfhd;
1202 if( !traf->trun_list.tail
1203 || !traf->trun_list.tail->data )
1205 /* There are no track runs in this track fragment, so it is a empty-duration. */
1206 isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID );
1207 if( !trex )
1208 return LSMASH_ERR_NAMELESS;
1209 tfhd->flags |= ISOM_TF_FLAGS_DURATION_IS_EMPTY;
1210 if( last_duration != trex->default_sample_duration )
1211 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1212 tfhd->default_sample_duration = last_duration;
1213 traf->cache->fragment->last_duration = last_duration;
1214 return 0;
1216 /* Update the last sample_duration if needed. */
1217 isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data;
1218 if( trun->sample_count == 1
1219 && traf->trun_list.entry_count == 1 )
1221 isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID );
1222 if( !trex )
1223 return LSMASH_ERR_NAMELESS;
1224 if( last_duration != trex->default_sample_duration )
1225 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1226 tfhd->default_sample_duration = last_duration;
1228 else if( last_duration != tfhd->default_sample_duration )
1229 trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT;
1230 if( trun->flags )
1232 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count );
1233 if( !row )
1234 return LSMASH_ERR_NAMELESS;
1235 row->sample_duration = last_duration;
1237 traf->cache->fragment->last_duration = last_duration;
1238 return 0;
1241 int isom_append_fragment_track_run
1243 lsmash_file_t *file,
1244 isom_chunk_t *chunk
1247 if( !chunk->pool || chunk->pool->size == 0 )
1248 return 0;
1249 isom_fragment_manager_t *fragment = file->fragment;
1250 /* Move data in the pool of the current track fragment to the pool of the current movie fragment.
1251 * Empty the pool of current track. We don't delete data of samples here. */
1252 if( lsmash_add_entry( fragment->pool, chunk->pool ) < 0 )
1253 return LSMASH_ERR_MEMORY_ALLOC;
1254 fragment->sample_count += chunk->pool->sample_count;
1255 fragment->pool_size += chunk->pool->size;
1256 chunk->pool = isom_create_sample_pool( chunk->pool->size );
1257 return chunk->pool ? 0 : LSMASH_ERR_MEMORY_ALLOC;
1260 static int isom_output_fragment_cache( isom_traf_t *traf )
1262 isom_cache_t *cache = traf->cache;
1263 int ret = isom_append_fragment_track_run( traf->file, &cache->chunk );
1264 if( ret < 0 )
1265 return ret;
1266 for( lsmash_entry_t *entry = traf->sgpd_list.head; entry; entry = entry->next )
1268 isom_sgpd_t *sgpd = (isom_sgpd_t *)entry->data;
1269 if( !sgpd )
1270 return LSMASH_ERR_NAMELESS;
1271 switch( sgpd->grouping_type )
1273 case ISOM_GROUP_TYPE_RAP :
1275 isom_rap_group_t *group = cache->rap;
1276 if( !group )
1278 if( traf->file->fragment )
1279 continue;
1280 else
1281 return LSMASH_ERR_NAMELESS;
1283 if( !group->random_access )
1284 continue;
1285 group->random_access->num_leading_samples_known = 1;
1286 break;
1288 case ISOM_GROUP_TYPE_ROLL :
1289 case ISOM_GROUP_TYPE_PROL :
1290 if( !cache->roll.pool )
1292 if( traf->file->fragment )
1293 continue;
1294 else
1295 return LSMASH_ERR_NAMELESS;
1297 isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &traf->sbgp_list );
1298 if( !sbgp )
1299 return LSMASH_ERR_NAMELESS;
1300 if( (ret = isom_all_recovery_completed( sbgp, cache->roll.pool )) < 0 )
1301 return ret;
1302 break;
1303 default :
1304 break;
1307 return 0;
1310 int isom_flush_fragment_pooled_samples
1312 lsmash_file_t *file,
1313 uint32_t track_ID,
1314 uint32_t last_sample_duration
1317 isom_traf_t *traf = isom_get_traf( file->fragment->movie, track_ID );
1318 if( !traf )
1319 /* No samples. We don't return as an error here since user might call the flushing function even if the
1320 * current movie fragment has no track fragment with this track_ID. */
1321 return 0;
1322 if( !traf->cache
1323 || !traf->cache->fragment )
1324 return LSMASH_ERR_NAMELESS;
1325 if( traf->trun_list.entry_count
1326 && traf->trun_list.tail
1327 && traf->trun_list.tail->data )
1329 /* Media Data Box preceded by Movie Fragment Box could change base_data_offsets in each track fragments later.
1330 * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */
1331 isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data;
1332 if( file->fragment->pool_size )
1333 trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT;
1334 trun->data_offset = file->fragment->pool_size;
1336 int ret = isom_output_fragment_cache( traf );
1337 if( ret < 0 )
1338 return ret;
1339 return isom_set_fragment_last_duration( traf, last_sample_duration );
1342 /* This function doesn't update sample_duration of the last sample in the previous movie fragment.
1343 * Instead of this, isom_finish_movie_fragment undertakes this task. */
1344 static int isom_update_fragment_previous_sample_duration( isom_traf_t *traf, isom_trex_t *trex, uint32_t duration )
1346 isom_tfhd_t *tfhd = traf->tfhd;
1347 isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data;
1348 int previous_run_has_previous_sample = 0;
1349 if( trun->sample_count == 1 )
1351 if( traf->trun_list.entry_count == 1 )
1352 return 0; /* The previous track run belongs to the previous movie fragment if it exists. */
1353 if( !traf->trun_list.tail->prev
1354 || !traf->trun_list.tail->prev->data )
1355 return LSMASH_ERR_NAMELESS;
1356 /* OK. The previous sample exists in the previous track run in the same track fragment. */
1357 trun = (isom_trun_t *)traf->trun_list.tail->prev->data;
1358 previous_run_has_previous_sample = 1;
1360 /* Update default_sample_duration of the Track Fragment Header Box
1361 * if this duration is what the first sample in the current track fragment owns. */
1362 if( (trun->sample_count == 2 && traf->trun_list.entry_count == 1)
1363 || (trun->sample_count == 1 && traf->trun_list.entry_count == 2) )
1365 if( duration != trex->default_sample_duration )
1366 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1367 tfhd->default_sample_duration = duration;
1369 /* Update the previous sample_duration if needed. */
1370 if( duration != tfhd->default_sample_duration )
1371 trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT;
1372 if( trun->flags )
1374 uint32_t sample_number = trun->sample_count - !previous_run_has_previous_sample;
1375 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, sample_number );
1376 if( !row )
1377 return LSMASH_ERR_NAMELESS;
1378 row->sample_duration = duration;
1380 traf->cache->fragment->last_duration = duration;
1381 return 0;
1384 static isom_sample_flags_t isom_generate_fragment_sample_flags( lsmash_sample_t *sample )
1386 isom_sample_flags_t flags;
1387 flags.reserved = 0;
1388 flags.is_leading = sample->prop.leading & 0x3;
1389 flags.sample_depends_on = sample->prop.independent & 0x3;
1390 flags.sample_is_depended_on = sample->prop.disposable & 0x3;
1391 flags.sample_has_redundancy = sample->prop.redundant & 0x3;
1392 flags.sample_padding_value = 0;
1393 flags.sample_is_non_sync_sample = !(sample->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC);
1394 flags.sample_degradation_priority = 0;
1395 return flags;
1398 static int isom_update_fragment_sample_tables( isom_traf_t *traf, lsmash_sample_t *sample )
1400 isom_tfhd_t *tfhd = traf->tfhd;
1401 isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID );
1402 if( !trex )
1403 return LSMASH_ERR_NAMELESS;
1404 lsmash_file_t *file = traf->file;
1405 isom_cache_t *cache = traf->cache;
1406 isom_chunk_t *current = &cache->chunk;
1407 if( !current->pool )
1409 /* Very initial settings, just once per track */
1410 current->pool = isom_create_sample_pool( 0 );
1411 if( !current->pool )
1412 return LSMASH_ERR_MEMORY_ALLOC;
1414 /* Create a new track run if the duration exceeds max_chunk_duration.
1415 * Old one will be appended to the pool of this movie fragment. */
1416 uint32_t media_timescale = lsmash_get_media_timescale( file->root, tfhd->track_ID );
1417 if( !media_timescale )
1418 return LSMASH_ERR_NAMELESS;
1419 int delimit = (file->max_chunk_duration < ((double)(sample->dts - current->first_dts) / media_timescale))
1420 || (file->max_chunk_size < (current->pool->size + sample->length));
1421 isom_trun_t *trun = NULL;
1422 if( !traf->trun_list.entry_count || delimit )
1424 if( delimit
1425 && traf->trun_list.entry_count
1426 && traf->trun_list.tail
1427 && traf->trun_list.tail->data )
1429 /* Media Data Box preceded by Movie Fragment Box could change base data offsets in each track fragments later.
1430 * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */
1431 trun = (isom_trun_t *)traf->trun_list.tail->data;
1432 if( file->fragment->pool_size )
1433 trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT;
1434 trun->data_offset = file->fragment->pool_size;
1436 trun = isom_add_trun( traf );
1437 if( !trun )
1438 return LSMASH_ERR_NAMELESS;
1440 else
1442 if( !traf->trun_list.tail
1443 || !traf->trun_list.tail->data )
1444 return LSMASH_ERR_NAMELESS;
1445 trun = (isom_trun_t *)traf->trun_list.tail->data;
1447 isom_sample_flags_t sample_flags = isom_generate_fragment_sample_flags( sample );
1448 if( ++trun->sample_count == 1 )
1450 if( traf->trun_list.entry_count == 1 )
1452 /* This track fragment isn't empty-duration-fragment any more. */
1453 tfhd->flags &= ~ISOM_TF_FLAGS_DURATION_IS_EMPTY;
1454 /* Set up sample_description_index in this track fragment. */
1455 if( sample->index != trex->default_sample_description_index )
1456 tfhd->flags |= ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT;
1457 tfhd->sample_description_index = current->sample_description_index = sample->index;
1458 /* Set up default_sample_size used in this track fragment. */
1459 tfhd->default_sample_size = sample->length;
1460 /* Set up default_sample_flags used in this track fragment.
1461 * Note: we decide an appropriate default value at the end of this movie fragment. */
1462 tfhd->default_sample_flags = sample_flags;
1463 /* Set up random access information if this sample is a sync sample.
1464 * We inform only the first sample in each movie fragment. */
1465 if( file->mfra && (sample->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC) )
1467 isom_tfra_t *tfra = isom_get_tfra( file->mfra, tfhd->track_ID );
1468 if( !tfra )
1470 tfra = isom_add_tfra( file->mfra );
1471 if( !tfra )
1472 return LSMASH_ERR_NAMELESS;
1473 tfra->track_ID = tfhd->track_ID;
1475 if( !tfra->list )
1477 tfra->list = lsmash_create_entry_list();
1478 if( !tfra->list )
1479 return LSMASH_ERR_MEMORY_ALLOC;
1481 isom_tfra_location_time_entry_t *rap = lsmash_malloc( sizeof(isom_tfra_location_time_entry_t) );
1482 if( !rap )
1483 return LSMASH_ERR_MEMORY_ALLOC;
1484 rap->time = sample->cts; /* Set composition timestamp temporally.
1485 * At the end of the whole movie, this will be reset as presentation time. */
1486 rap->moof_offset = file->size; /* We place Movie Fragment Box in the head of each movie fragment. */
1487 rap->traf_number = cache->fragment->traf_number;
1488 rap->trun_number = traf->trun_list.entry_count;
1489 rap->sample_number = trun->sample_count;
1490 if( lsmash_add_entry( tfra->list, rap ) < 0 )
1492 lsmash_free( rap );
1493 return LSMASH_ERR_MEMORY_ALLOC;
1495 tfra->number_of_entry = tfra->list->entry_count;
1496 int length;
1497 for( length = 1; rap->traf_number >> (length * 8); length++ );
1498 tfra->length_size_of_traf_num = LSMASH_MAX( length - 1, tfra->length_size_of_traf_num );
1499 for( length = 1; rap->traf_number >> (length * 8); length++ );
1500 tfra->length_size_of_trun_num = LSMASH_MAX( length - 1, tfra->length_size_of_trun_num );
1501 for( length = 1; rap->sample_number >> (length * 8); length++ );
1502 tfra->length_size_of_sample_num = LSMASH_MAX( length - 1, tfra->length_size_of_sample_num );
1504 /* Set up the base media decode time of this track fragment.
1505 * This feature is available under ISO Base Media version 6 or later.
1506 * For DASH Media Segment, each Track Fragment Box shall contain a Track Fragment Base Media Decode Time Box. */
1507 if( file->max_isom_version >= 6 || file->media_segment )
1509 assert( !traf->tfdt );
1510 if( !isom_add_tfdt( traf ) )
1511 return LSMASH_ERR_NAMELESS;
1512 if( sample->dts > UINT32_MAX )
1513 traf->tfdt->version = 1;
1514 traf->tfdt->baseMediaDecodeTime = sample->dts;
1517 trun->first_sample_flags = sample_flags;
1518 current->first_dts = sample->dts;
1520 /* Update the optional rows in the current track run except for sample_duration if needed. */
1521 if( sample->length != tfhd->default_sample_size )
1522 trun->flags |= ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT;
1523 if( isom_compare_sample_flags( &sample_flags, &tfhd->default_sample_flags ) )
1524 trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT;
1525 uint32_t sample_composition_time_offset = sample->cts - sample->dts;
1526 if( sample_composition_time_offset )
1528 trun->flags |= ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT;
1529 /* Check if negative composition time offset is present. */
1530 isom_timestamp_t *ts_cache = &cache->timestamp;
1531 if( (sample->cts + ts_cache->ctd_shift) < sample->dts )
1533 if( file->max_isom_version < 6 )
1534 return LSMASH_ERR_INVALID_DATA; /* Negative composition time offset is invalid. */
1535 if( (sample->dts - sample->cts) > INT32_MAX )
1536 return LSMASH_ERR_INVALID_DATA; /* Overflow */
1537 ts_cache->ctd_shift = sample->dts - sample->cts;
1538 if( trun->version == 0 && file->max_isom_version >= 6 )
1539 trun->version = 1;
1542 if( trun->flags )
1544 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count );
1545 if( !row )
1546 return LSMASH_ERR_NAMELESS;
1547 row->sample_size = sample->length;
1548 row->sample_flags = sample_flags;
1549 row->sample_composition_time_offset = sample_composition_time_offset;
1551 /* Set up the sample groupings for random access. */
1552 int ret;
1553 if( (ret = isom_group_random_access( (isom_box_t *)traf, sample )) < 0
1554 || (ret = isom_group_roll_recovery( (isom_box_t *)traf, sample )) < 0 )
1555 return ret;
1556 /* Set up the previous sample_duration if this sample is not the first sample in the overall movie. */
1557 if( cache->fragment->has_samples )
1559 /* Note: when using for live streaming, it is not good idea to return error by sample->dts < prev_dts
1560 * since that's trivial for such semi-permanent presentation. */
1561 uint64_t prev_dts = cache->timestamp.dts;
1562 if( sample->dts <= prev_dts
1563 || sample->dts > prev_dts + UINT32_MAX )
1564 return LSMASH_ERR_INVALID_DATA;
1565 uint32_t sample_duration = sample->dts - prev_dts;
1566 if( (ret = isom_update_fragment_previous_sample_duration( traf, trex, sample_duration )) < 0 )
1567 return ret;
1569 /* Cache */
1570 cache->timestamp.dts = sample->dts;
1571 cache->timestamp.cts = sample->cts;
1572 cache->fragment->largest_cts = LSMASH_MAX( sample->cts, cache->fragment->largest_cts );
1573 isom_subsegment_t *subsegment = &cache->fragment->subsegment;
1574 if( trun->sample_count == 1 && traf->trun_list.entry_count == 1 )
1576 subsegment->first_cts = sample->cts;
1577 subsegment->largest_cts = sample->cts;
1578 subsegment->smallest_cts = sample->cts;
1580 else
1582 subsegment->largest_cts = LSMASH_MAX( sample->cts, subsegment->largest_cts );
1583 subsegment->smallest_cts = LSMASH_MIN( sample->cts, subsegment->smallest_cts );
1585 if( subsegment->first_ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE )
1587 subsegment->first_ra_flags = sample->prop.ra_flags;
1588 subsegment->first_ra_number = cache->fragment->sample_count + 1;
1589 if( sample->prop.ra_flags & (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP) )
1591 subsegment->first_rp_number = subsegment->first_ra_number;
1592 subsegment->first_rp_cts = sample->cts;
1593 subsegment->first_ed_cts = sample->cts;
1594 subsegment->decodable = 1;
1597 else if( subsegment->decodable )
1599 if( (subsegment->first_ra_flags & (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP))
1600 ? (sample->prop.leading == ISOM_SAMPLE_IS_DECODABLE_LEADING)
1601 : (subsegment->first_ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START) )
1602 subsegment->first_ed_cts = LSMASH_MIN( sample->cts, subsegment->first_ed_cts );
1603 else
1604 subsegment->decodable = 0;
1606 return delimit;
1609 static int isom_append_fragment_sample_internal_initial( isom_trak_t *trak, lsmash_sample_t *sample )
1611 /* Update the sample tables of this track fragment.
1612 * If a new chunk was created, append the previous one to the pool of this movie fragment. */
1613 uint32_t samples_per_packet;
1614 int ret = isom_update_sample_tables( trak, sample, &samples_per_packet );
1615 if( ret < 0 )
1616 return ret;
1617 else if( ret == 1 )
1618 isom_append_fragment_track_run( trak->file, &trak->cache->chunk );
1619 /* Add a new sample into the pool of this track fragment. */
1620 if( (ret = isom_pool_sample( trak->cache->chunk.pool, sample, samples_per_packet )) < 0 )
1621 return ret;
1622 trak->cache->fragment->has_samples = 1;
1623 trak->cache->fragment->sample_count += 1;
1624 return 0;
1627 static int isom_append_fragment_sample_internal( isom_traf_t *traf, lsmash_sample_t *sample )
1629 /* Update the sample tables of this track fragment.
1630 * If a new track run was created, append the previous one to the pool of this movie fragment. */
1631 int ret = isom_update_fragment_sample_tables( traf, sample );
1632 if( ret < 0 )
1633 return ret;
1634 else if( ret == 1 )
1635 isom_append_fragment_track_run( traf->file, &traf->cache->chunk );
1636 /* Add a new sample into the pool of this track fragment. */
1637 if( (ret = isom_pool_sample( traf->cache->chunk.pool, sample, 1 )) < 0 )
1638 return ret;
1639 traf->cache->fragment->has_samples = 1;
1640 traf->cache->fragment->sample_count += 1;
1641 return 0;
1644 int isom_append_fragment_sample
1646 lsmash_file_t *file,
1647 uint32_t track_ID,
1648 lsmash_sample_t *sample
1651 isom_fragment_manager_t *fragment = file->fragment;
1652 assert( fragment && fragment->pool );
1653 isom_trak_t *trak = isom_get_trak( file->initializer, track_ID );
1654 if( !trak
1655 || !trak->file
1656 || !trak->cache
1657 || !trak->cache->fragment
1658 || !trak->tkhd
1659 || !trak->mdia
1660 || !trak->mdia->mdhd
1661 || trak->mdia->mdhd->timescale == 0
1662 || !trak->mdia->minf
1663 || !trak->mdia->minf->stbl
1664 || !trak->mdia->minf->stbl->stsd
1665 || !trak->mdia->minf->stbl->stsc || !trak->mdia->minf->stbl->stsc->list )
1666 return LSMASH_ERR_NAMELESS;
1667 /* Write the Segment Type Box here if required and if it was not written yet. */
1668 if( !(file->flags & LSMASH_FILE_MODE_INITIALIZATION)
1669 && file->styp_list.head && file->styp_list.head->data )
1671 isom_styp_t *styp = (isom_styp_t *)file->styp_list.head->data;
1672 if( !(styp->manager & LSMASH_WRITTEN_BOX) )
1674 int ret = isom_write_box( file->bs, (isom_box_t *)styp );
1675 if( ret < 0 )
1676 return ret;
1677 file->size += styp->size;
1680 int (*append_sample_func)( void *, lsmash_sample_t * ) = NULL;
1681 void *track_fragment = NULL;
1682 if( !fragment->movie )
1684 /* Forbid adding a sample into the initial movie if requiring compatibility with Media Segment. */
1685 if( file->media_segment )
1686 return LSMASH_ERR_NAMELESS;
1687 append_sample_func = (int (*)( void *, lsmash_sample_t * ))isom_append_fragment_sample_internal_initial;
1688 track_fragment = trak;
1690 else
1692 isom_traf_t *traf = isom_get_traf( fragment->movie, track_ID );
1693 if( !traf )
1695 traf = isom_add_traf( fragment->movie );
1696 if( !isom_add_tfhd( traf ) )
1697 return LSMASH_ERR_NAMELESS;
1698 traf->tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */
1699 traf->tfhd->track_ID = trak->tkhd->track_ID;
1700 traf->cache = trak->cache;
1701 traf->cache->fragment->traf_number = fragment->movie->traf_list.entry_count;
1702 int ret;
1703 if( (traf->cache->fragment->rap_grouping && (ret = isom_add_sample_grouping( (isom_box_t *)traf, ISOM_GROUP_TYPE_RAP )) < 0)
1704 || (traf->cache->fragment->roll_grouping && (ret = isom_add_sample_grouping( (isom_box_t *)traf, ISOM_GROUP_TYPE_ROLL )) < 0) )
1705 return ret;
1707 else if( !traf->file
1708 || !traf->file->initializer
1709 || !traf->file->initializer->moov
1710 || !traf->file->initializer->moov->mvex
1711 || !traf->cache
1712 || !traf->tfhd )
1713 return LSMASH_ERR_NAMELESS;
1714 append_sample_func = (int (*)( void *, lsmash_sample_t * ))isom_append_fragment_sample_internal;
1715 track_fragment = traf;
1717 isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)lsmash_get_entry_data( &trak->mdia->minf->stbl->stsd->list, sample->index );
1718 if( !sample_entry )
1719 return LSMASH_ERR_NAMELESS;
1720 if( isom_is_lpcm_audio( sample_entry ) )
1722 uint32_t frame_size = ((isom_audio_entry_t *)sample_entry)->constBytesPerAudioPacket;
1723 if( sample->length == frame_size )
1724 return append_sample_func( track_fragment, sample );
1725 else if( sample->length < frame_size )
1726 return LSMASH_ERR_INVALID_DATA;
1727 /* Append samples splitted into each LPCMFrame. */
1728 uint64_t dts = sample->dts;
1729 uint64_t cts = sample->cts;
1730 for( uint32_t offset = 0; offset < sample->length; offset += frame_size )
1732 lsmash_sample_t *lpcm_sample = lsmash_create_sample( frame_size );
1733 if( !lpcm_sample )
1734 return LSMASH_ERR_MEMORY_ALLOC;
1735 memcpy( lpcm_sample->data, sample->data + offset, frame_size );
1736 lpcm_sample->dts = dts++;
1737 lpcm_sample->cts = cts++;
1738 lpcm_sample->prop = sample->prop;
1739 lpcm_sample->index = sample->index;
1740 int ret = append_sample_func( track_fragment, lpcm_sample );
1741 if( ret < 0 )
1743 lsmash_delete_sample( lpcm_sample );
1744 return ret;
1747 lsmash_delete_sample( sample );
1748 return 0;
1750 return append_sample_func( track_fragment, sample );