box: Utilize meaningful error values.
[L-SMASH.git] / core / fragment.c
blob37c149e19b6217f0879d9890af1a4f2929eac0b0
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 -1;
54 lsmash_file_t *file = root->file;
55 if( !file
56 || !file->bs
57 || !file->fragment )
58 return -1;
59 /* Finish and write the current movie fragment before starting a new one. */
60 if( isom_finish_fragment_movie( file ) < 0 )
61 return -1;
62 /* Add a new movie fragment if the current one is not present or not written. */
63 if( !file->fragment->movie || (file->fragment->movie->manager & LSMASH_WRITTEN_BOX) )
65 /* We always hold only one movie fragment except for the initial movie (a pair of moov and mdat). */
66 if( file->fragment->movie && file->moof_list.entry_count != 1 )
67 return -1;
68 isom_moof_t *moof = isom_add_moof( file );
69 if( !isom_add_mfhd( moof ) )
70 return -1;
71 file->fragment->movie = moof;
72 moof->mfhd->sequence_number = ++ file->fragment_count;
73 if( file->moof_list.entry_count == 1 )
74 return 0;
75 /* Remove the previous movie fragment. */
76 if( file->moof_list.head )
77 isom_remove_box_by_itself( file->moof_list.head->data );
79 return 0;
82 static int isom_set_fragment_overall_duration( lsmash_file_t *file )
84 assert( file == file->initializer );
85 /* Get the longest duration of the tracks. */
86 uint64_t longest_duration = 0;
87 for( lsmash_entry_t *entry = file->moov->trak_list.head; entry; entry = entry->next )
89 isom_trak_t *trak = (isom_trak_t *)entry->data;
90 if( !trak
91 || !trak->cache
92 || !trak->cache->fragment
93 || !trak->mdia
94 || !trak->mdia->mdhd
95 || !trak->mdia->mdhd->timescale )
96 return -1;
97 uint64_t duration;
98 if( !trak->edts
99 || !trak->edts->elst
100 || !trak->edts->elst->list )
102 duration = trak->cache->fragment->largest_cts + trak->cache->fragment->last_duration;
103 duration = (uint64_t)(((double)duration / trak->mdia->mdhd->timescale) * file->moov->mvhd->timescale);
105 else
107 duration = 0;
108 for( lsmash_entry_t *elst_entry = trak->edts->elst->list->head; elst_entry; elst_entry = elst_entry->next )
110 isom_elst_entry_t *data = (isom_elst_entry_t *)elst_entry->data;
111 if( !data )
112 return -1;
113 if( data->segment_duration == ISOM_EDIT_DURATION_IMPLICIT )
115 uint64_t segment_duration = trak->cache->fragment->largest_cts + trak->cache->fragment->last_duration;
116 duration += (uint64_t)(((double)segment_duration / trak->mdia->mdhd->timescale) * file->moov->mvhd->timescale);
118 else
119 duration += data->segment_duration;
122 longest_duration = LSMASH_MAX( duration, longest_duration );
124 isom_mehd_t *mehd = file->moov->mvex->mehd;
125 mehd->fragment_duration = longest_duration;
126 mehd->version = 1;
127 mehd->manager &= ~(LSMASH_PLACEHOLDER | LSMASH_WRITTEN_BOX); /* Update per media segment. */
128 isom_update_box_size( mehd );
129 /* Write Movie Extends Header Box here. */
130 lsmash_bs_t *bs = file->bs;
131 uint64_t current_pos = bs->offset;
132 lsmash_bs_write_seek( bs, mehd->pos, SEEK_SET );
133 int ret = isom_write_box( bs, (isom_box_t *)mehd );
134 lsmash_bs_write_seek( bs, current_pos, SEEK_SET );
135 return ret;
138 static int isom_write_fragment_random_access_info( lsmash_file_t *file )
140 assert( file == file->initializer );
141 if( !file->mfra )
142 return 0;
143 if( !file->moov->mvex )
144 return -1;
145 /* Reconstruct the Movie Fragment Random Access Box.
146 * All 'time' field in the Track Fragment Random Access Boxes shall reflect edit list. */
147 uint32_t movie_timescale = lsmash_get_movie_timescale( file->root );
148 if( movie_timescale == 0 )
149 return -1; /* Division by zero will occur. */
150 for( lsmash_entry_t *trex_entry = file->moov->mvex->trex_list.head; trex_entry; trex_entry = trex_entry->next )
152 isom_trex_t *trex = (isom_trex_t *)trex_entry->data;
153 if( !trex )
154 return -1;
155 /* Get the edit list of the track associated with the trex->track_ID.
156 * If failed or absent, implicit timeline mapping edit is used, and skip this operation for the track. */
157 isom_trak_t *trak = isom_get_trak( file, trex->track_ID );
158 if( !trak )
159 return -1;
160 if( !trak->edts
161 || !trak->edts->elst
162 || !trak->edts->elst->list
163 || !trak->edts->elst->list->head
164 || !trak->edts->elst->list->head->data )
165 continue;
166 isom_elst_t *elst = trak->edts->elst;
167 /* Get the Track Fragment Random Access Boxes of the track associated with the trex->track_ID.
168 * If failed or absent, skip reconstructing the Track Fragment Random Access Box of the track. */
169 isom_tfra_t *tfra = isom_get_tfra( file->mfra, trex->track_ID );
170 if( !tfra )
171 continue;
172 /* Reconstruct the Track Fragment Random Access Box. */
173 lsmash_entry_t *edit_entry = elst->list->head;
174 isom_elst_entry_t *edit = edit_entry->data;
175 uint64_t edit_offset = 0; /* units in media timescale */
176 uint32_t media_timescale = lsmash_get_media_timescale( file->root, trex->track_ID );
177 for( lsmash_entry_t *rap_entry = tfra->list->head; rap_entry; )
179 isom_tfra_location_time_entry_t *rap = (isom_tfra_location_time_entry_t *)rap_entry->data;
180 if( !rap )
182 /* Irregular case. Drop this entry. */
183 lsmash_entry_t *next = rap_entry->next;
184 lsmash_remove_entry_direct( tfra->list, rap_entry, NULL );
185 rap_entry = next;
186 continue;
188 uint64_t composition_time = rap->time;
189 /* Skip edits that doesn't need the current sync sample indicated in the Track Fragment Random Access Box. */
190 while( edit )
192 uint64_t segment_duration = edit->segment_duration == ISOM_EDIT_DURATION_IMPLICIT
193 ? trak->cache->fragment->largest_cts + trak->cache->fragment->last_duration
194 : ((edit->segment_duration - 1) / movie_timescale + 1) * media_timescale;
195 if( edit->media_time != ISOM_EDIT_MODE_EMPTY
196 && composition_time < edit->media_time + segment_duration )
197 break; /* This Timeline Mapping Edit might require the current sync sample.
198 * Note: this condition doesn't cover all cases.
199 * For instance, matching the both following conditions
200 * 1. A sync sample isn't in the presentation.
201 * 2. The other samples, which precede it in the composition timeline, is in the presentation. */
202 edit_offset += segment_duration;
203 edit_entry = edit_entry->next;
204 if( !edit_entry )
206 /* No more presentation. */
207 edit = NULL;
208 break;
210 edit = edit_entry->data;
212 if( !edit )
214 /* No more presentation.
215 * Drop the rest of sync samples since they are generally absent in the whole presentation.
216 * Though the exceptions are sync samples with earlier composition time, we ignore them. (SAP type 2: TEPT = TDEC = TSAP < TPTF)
217 * To support this exception, we need sorting entries of the list by composition times. */
218 while( rap_entry )
220 lsmash_entry_t *next = rap_entry->next;
221 lsmash_remove_entry_direct( tfra->list, rap_entry, NULL );
222 rap_entry = next;
224 break;
226 /* If the sync sample isn't in the presentation,
227 * we pick the earliest presentation time of the current edit as its presentation time. */
228 rap->time = edit_offset;
229 if( composition_time >= edit->media_time )
230 rap->time += composition_time - edit->media_time;
231 rap_entry = rap_entry->next;
233 tfra->number_of_entry = tfra->list->entry_count;
235 /* Decide the size of the Movie Fragment Random Access Box. */
236 if( isom_update_box_size( file->mfra ) == 0 )
237 return -1;
238 /* Write the Movie Fragment Random Access Box. */
239 return isom_write_box( file->bs, (isom_box_t *)file->mfra );
242 static int isom_update_indexed_material_offset
244 lsmash_file_t *file,
245 isom_sidx_t *last_sidx
248 /* Update the size of each Segment Index Box. */
249 for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next )
251 isom_sidx_t *sidx = (isom_sidx_t *)entry->data;
252 if( !sidx )
253 continue;
254 if( isom_update_box_size( sidx ) == 0 )
255 return -1;
257 /* first_offset: the sum of the size of subsequent Segment Index Boxes
258 * Be careful about changing the size of them. */
259 last_sidx->first_offset = 0;
260 for( lsmash_entry_t *a_entry = file->sidx_list.head; a_entry && a_entry->data != last_sidx; a_entry = a_entry->next )
262 isom_sidx_t *a = (isom_sidx_t *)a_entry->data;
263 a->first_offset = 0;
264 for( lsmash_entry_t *b_entry = a_entry->next; b_entry; b_entry = b_entry->next )
266 isom_sidx_t *b = (isom_sidx_t *)b_entry->data;
267 a->first_offset += b->size;
270 return 0;
273 static int isom_write_segment_indexes
275 lsmash_file_t *file,
276 lsmash_adhoc_remux_t *remux
279 /* Update the size of each Segment Index Box and establish the offset from the anchor point to the indexed material. */
280 if( isom_update_indexed_material_offset( file, (isom_sidx_t *)file->sidx_list.tail->data ) < 0 )
281 return -1;
282 /* Get the total size of all Segment Index Boxes. */
283 uint64_t total_sidx_size = 0;
284 for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next )
286 isom_sidx_t *sidx = (isom_sidx_t *)entry->data;
287 if( !sidx )
288 continue;
289 total_sidx_size += sidx->size;
291 /* The buffer size must be at least total_sidx_size * 2. */
292 size_t buffer_size = total_sidx_size * 2;
293 if( remux->buffer_size > buffer_size )
294 buffer_size = remux->buffer_size;
295 /* Split to 2 buffers. */
296 uint8_t *buf[2] = { NULL, NULL };
297 if( (buf[0] = (uint8_t *)lsmash_malloc( buffer_size )) == NULL )
298 return -1;
299 size_t size = buffer_size / 2;
300 buf[1] = buf[0] + size;
301 /* Seek to the beginning of the first Movie Fragment Box i.e. the first subsegment within this media segment. */
302 lsmash_bs_t *bs = file->bs;
303 if( lsmash_bs_write_seek( bs, file->fragment->first_moof_pos, SEEK_SET ) < 0 )
304 goto fail;
305 size_t read_num = size;
306 lsmash_bs_read_data( bs, buf[0], &read_num );
307 uint64_t read_pos = bs->offset;
308 /* Write the Segment Index Boxes actually here. */
309 if( lsmash_bs_write_seek( bs, file->fragment->first_moof_pos, SEEK_SET ) < 0 )
310 goto fail;
311 for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next )
313 isom_sidx_t *sidx = (isom_sidx_t *)entry->data;
314 if( !sidx )
315 continue;
316 if( isom_write_box( file->bs, (isom_box_t *)sidx ) < 0 )
317 return -1;
319 /* Rearrange subsequent data. */
320 uint64_t write_pos = bs->offset;
321 uint64_t total = file->size + total_sidx_size;
322 if( isom_rearrange_data( file, remux, buf, read_num, size, read_pos, write_pos, total ) < 0 )
323 goto fail;
324 file->size += total_sidx_size;
325 lsmash_freep( &buf[0] );
326 /* Update 'moof_offset' of each entry within the Track Fragment Random Access Boxes. */
327 if( file->mfra )
328 for( lsmash_entry_t *entry = file->mfra->tfra_list.head; entry; entry = entry->next )
330 isom_tfra_t *tfra = (isom_tfra_t *)entry->data;
331 if( !tfra )
332 continue;
333 for( lsmash_entry_t *rap_entry = tfra->list->head; rap_entry; rap_entry = rap_entry->next )
335 isom_tfra_location_time_entry_t *rap = (isom_tfra_location_time_entry_t *)rap_entry->data;
336 if( !rap )
337 continue;
338 rap->moof_offset += total_sidx_size;
341 return 0;
342 fail:
343 lsmash_free( buf[0] );
344 return -1;
347 int isom_finish_final_fragment_movie
349 lsmash_file_t *file,
350 lsmash_adhoc_remux_t *remux
353 /* Output the final movie fragment. */
354 if( isom_finish_fragment_movie( file ) < 0 )
355 return -1;
356 if( file->bs->unseekable )
357 return 0;
358 /* Write Segment Index Boxes.
359 * This occurs only when the initial movie has no samples.
360 * We don't consider updating of chunk offsets within initial movie sample table here.
361 * This is reasonable since DASH requires no samples in the initial movie.
362 * This implementation is not suitable for live-streaming.
363 + To support live-streaming, it is good to use daisy-chained index. */
364 if( (file->flags & LSMASH_FILE_MODE_MEDIA)
365 && (file->flags & LSMASH_FILE_MODE_INDEX)
366 && (file->flags & LSMASH_FILE_MODE_SEGMENT)
367 && (!remux || isom_write_segment_indexes( file, remux ) < 0) )
368 return -1;
369 /* Write the overall random access information at the tail of the movie if this file is self-contained. */
370 if( isom_write_fragment_random_access_info( file->initializer ) < 0 )
371 return -1;
372 /* Set overall duration of the movie. */
373 return isom_set_fragment_overall_duration( file->initializer );
376 #define GET_MOST_USED( box_name, index, flag_name ) \
377 if( most_used[index] < stats.flag_name[i] ) \
379 most_used[index] = stats.flag_name[i]; \
380 box_name->default_sample_flags.flag_name = i; \
383 static int isom_create_fragment_overall_default_settings( lsmash_file_t *file )
385 assert( file == file->initializer );
386 if( !isom_add_mvex( file->moov ) )
387 return -1;
388 if( !file->bs->unseekable )
390 if( !isom_add_mehd( file->moov->mvex ) )
391 return -1;
392 file->moov->mvex->mehd->manager |= LSMASH_PLACEHOLDER;
394 for( lsmash_entry_t *trak_entry = file->moov->trak_list.head; trak_entry; trak_entry = trak_entry->next )
396 isom_trak_t *trak = (isom_trak_t *)trak_entry->data;
397 if( !trak
398 || !trak->cache
399 || !trak->tkhd
400 || !trak->mdia
401 || !trak->mdia->minf
402 || !trak->mdia->minf->stbl )
403 return -1;
404 isom_stbl_t *stbl = trak->mdia->minf->stbl;
405 if( !stbl->stts || !stbl->stts->list
406 || !stbl->stsz
407 || (stbl->stts->list->tail && !stbl->stts->list->tail->data)
408 || (stbl->stsz->list && stbl->stsz->list->head && !stbl->stsz->list->head->data) )
409 return -1;
410 isom_trex_t *trex = isom_add_trex( file->moov->mvex );
411 if( !trex )
412 return -1;
413 trex->track_ID = trak->tkhd->track_ID;
414 /* Set up defaults. */
415 trex->default_sample_description_index = trak->cache->chunk.sample_description_index
416 ? trak->cache->chunk.sample_description_index
417 : 1;
418 trex->default_sample_duration = stbl->stts->list->tail
419 ? ((isom_stts_entry_t *)stbl->stts->list->tail->data)->sample_delta
420 : 1;
421 trex->default_sample_size = !stbl->stsz->list
422 ? stbl->stsz->sample_size : stbl->stsz->list->head
423 ? ((isom_stsz_entry_t *)stbl->stsz->list->head->data)->entry_size : 0;
424 if( stbl->sdtp
425 && stbl->sdtp->list )
427 struct sample_flags_stats_t
429 uint32_t is_leading [4];
430 uint32_t sample_depends_on [4];
431 uint32_t sample_is_depended_on[4];
432 uint32_t sample_has_redundancy[4];
433 } stats = { { 0 }, { 0 }, { 0 }, { 0 } };
434 for( lsmash_entry_t *sdtp_entry = stbl->sdtp->list->head; sdtp_entry; sdtp_entry = sdtp_entry->next )
436 isom_sdtp_entry_t *data = (isom_sdtp_entry_t *)sdtp_entry->data;
437 if( !data )
438 return -1;
439 ++ stats.is_leading [ data->is_leading ];
440 ++ stats.sample_depends_on [ data->sample_depends_on ];
441 ++ stats.sample_is_depended_on[ data->sample_is_depended_on ];
442 ++ stats.sample_has_redundancy[ data->sample_has_redundancy ];
444 uint32_t most_used[4] = { 0, 0, 0, 0 };
445 for( int i = 0; i < 4; i++ )
447 GET_MOST_USED( trex, 0, is_leading );
448 GET_MOST_USED( trex, 1, sample_depends_on );
449 GET_MOST_USED( trex, 2, sample_is_depended_on );
450 GET_MOST_USED( trex, 3, sample_has_redundancy );
453 trex->default_sample_flags.sample_is_non_sync_sample = !trak->cache->all_sync;
455 return 0;
458 static int isom_prepare_random_access_info( lsmash_file_t *file )
460 assert( file == file->initializer );
461 /* Don't write the random access info at the end of the file if unseekable or not self-contained. */
462 if( file->bs->unseekable
463 || !(file->flags & LSMASH_FILE_MODE_BOX)
464 || !(file->flags & LSMASH_FILE_MODE_INITIALIZATION)
465 || !(file->flags & LSMASH_FILE_MODE_MEDIA)
466 || (file->flags & LSMASH_FILE_MODE_SEGMENT) )
467 return 0;
468 if( !isom_add_mfra( file )
469 || !isom_add_mfro( file->mfra ) )
470 return -1;
471 return 0;
474 static int isom_output_fragment_media_data( lsmash_file_t *file )
476 isom_fragment_manager_t *fragment = file->fragment;
477 /* If there is no available Media Data Box to write samples, add and write a new one. */
478 if( fragment->sample_count )
480 if( !file->mdat && !isom_add_mdat( file ) )
481 return -1;
482 file->mdat->manager &= ~(LSMASH_INCOMPLETE_BOX | LSMASH_WRITTEN_BOX);
483 if( isom_write_box( file->bs, (isom_box_t *)file->mdat ) < 0 )
484 return -1;
485 file->size += file->mdat->size;
486 file->mdat->size = 0;
487 file->mdat->media_size = 0;
489 lsmash_remove_entries( fragment->pool, isom_remove_sample_pool );
490 fragment->pool_size = 0;
491 fragment->sample_count = 0;
492 return 0;
495 static int isom_finish_fragment_initial_movie( lsmash_file_t *file )
497 assert( file == file->initializer );
498 if( !file->moov )
499 return -1;
500 isom_moov_t *moov = file->moov;
501 for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next )
503 isom_trak_t *trak = (isom_trak_t *)entry->data;
504 if( !trak
505 || !trak->cache
506 || !trak->tkhd
507 || !trak->mdia
508 || !trak->mdia->mdhd
509 || !trak->mdia->minf
510 || !trak->mdia->minf->stbl
511 || isom_complement_data_reference( trak->mdia->minf ) < 0 )
512 return -1;
513 isom_stbl_t *stbl = trak->mdia->minf->stbl;
514 if( isom_get_sample_count( trak ) )
516 /* Add stss box if any samples aren't sync sample. */
517 if( !trak->cache->all_sync && !stbl->stss && !isom_add_stss( stbl ) )
518 return -1;
519 if( isom_update_tkhd_duration( trak ) < 0 )
520 return -1;
522 else
523 trak->tkhd->duration = 0;
524 if( isom_update_bitrate_description( trak->mdia ) < 0 )
525 return -1;
526 /* Complete the last sample groups within tracks in the initial movie. */
527 if( trak->cache->rap )
529 isom_sgpd_t *sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP );
530 if( !sgpd || isom_rap_grouping_established( trak->cache->rap, 1, sgpd, 0 ) < 0 )
531 return -1;
532 lsmash_freep( &trak->cache->rap );
534 if( trak->cache->roll.pool )
536 isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &stbl->sbgp_list );
537 if( !sbgp || isom_all_recovery_completed( sbgp, trak->cache->roll.pool ) < 0 )
538 return -1;
541 if( file->mp4_version1 == 1 && isom_setup_iods( moov ) < 0 )
542 return -1;
543 if( isom_create_fragment_overall_default_settings( file ) < 0
544 || isom_prepare_random_access_info ( file ) < 0
545 || isom_establish_movie ( file ) < 0 )
546 return -1;
547 /* stco->co64 conversion, depending on last chunk's offset */
548 uint64_t meta_size = file->meta ? file->meta->size : 0;
549 if( isom_check_large_offset_requirement( moov, meta_size ) < 0 )
550 return -1;
551 /* Now, the amount of the offset is fixed. apply it to stco/co64 */
552 uint64_t preceding_size = moov->size + meta_size;
553 isom_add_preceding_box_size( moov, preceding_size );
554 /* Write File Type Box here if it was not written yet. */
555 if( file->ftyp && !(file->ftyp->manager & LSMASH_WRITTEN_BOX) )
557 if( isom_write_box( file->bs, (isom_box_t *)file->ftyp ) < 0 )
558 return -1;
559 file->size += file->ftyp->size;
561 /* Write Movie Box. */
562 if( isom_write_box( file->bs, (isom_box_t *)file->moov ) < 0
563 || isom_write_box( file->bs, (isom_box_t *)file->meta ) < 0 )
564 return -1;
565 file->size += preceding_size;
566 /* Output samples. */
567 if( isom_output_fragment_media_data( file ) < 0 )
568 return -1;
569 /* Revert the number of samples in tracks to 0. */
570 for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next )
572 isom_trak_t *trak = (isom_trak_t *)entry->data;
573 if( trak->cache->fragment )
574 trak->cache->fragment->sample_count = 0;
576 return 0;
579 /* Return 1 if there is diffrence, otherwise return 0. */
580 static int isom_compare_sample_flags( isom_sample_flags_t *a, isom_sample_flags_t *b )
582 return (a->reserved != b->reserved)
583 || (a->is_leading != b->is_leading)
584 || (a->sample_depends_on != b->sample_depends_on)
585 || (a->sample_is_depended_on != b->sample_is_depended_on)
586 || (a->sample_has_redundancy != b->sample_has_redundancy)
587 || (a->sample_padding_value != b->sample_padding_value)
588 || (a->sample_is_non_sync_sample != b->sample_is_non_sync_sample)
589 || (a->sample_degradation_priority != b->sample_degradation_priority);
592 static int isom_make_segment_index_entry
594 lsmash_file_t *file,
595 isom_moof_t *moof
598 /* Make the index of this subsegment. */
599 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
601 isom_traf_t *traf = (isom_traf_t *)entry->data;
602 isom_tfhd_t *tfhd = traf->tfhd;
603 isom_fragment_t *track_fragment = traf->cache->fragment;
604 isom_subsegment_t *subsegment = &track_fragment->subsegment;
605 isom_sidx_t *sidx = isom_get_sidx( file, tfhd->track_ID );
606 isom_trak_t *trak = isom_get_trak( file->initializer, tfhd->track_ID );
607 if( !trak
608 || !trak->mdia
609 || !trak->mdia->mdhd )
610 return -1;
611 assert( traf->tfdt );
612 if( !sidx )
614 sidx = isom_add_sidx( file );
615 if( !sidx )
616 return -1;
617 sidx->reference_ID = tfhd->track_ID;
618 sidx->timescale = trak->mdia->mdhd->timescale;
619 sidx->reserved = 0;
620 sidx->reference_count = 0;
621 if( isom_update_indexed_material_offset( file, sidx ) < 0 )
622 return -1;
624 /* One pair of a Movie Fragment Box with an associated Media Box per subsegment. */
625 isom_sidx_referenced_item_t *data = lsmash_malloc( sizeof(isom_sidx_referenced_item_t) );
626 if( !data )
627 return -1;
628 if( lsmash_add_entry( sidx->list, data ) < 0 )
630 lsmash_free( data );
631 return -1;
633 sidx->reference_count = sidx->list->entry_count;
634 data->reference_type = 0; /* media */
635 data->reference_size = file->size - moof->pos;
636 /* presentation */
637 uint64_t TSAP;
638 uint64_t TDEC;
639 uint64_t TEPT;
640 uint64_t TPTF;
641 uint64_t composition_duration = subsegment->largest_cts - subsegment->smallest_cts + track_fragment->last_duration;
642 int subsegment_in_presentation; /* If set to 1, TEPT is available. */
643 int first_rp_in_presentation; /* If set to 1, both TSAP and TDEC are available. */
644 int first_sample_in_presentation; /* If set to 1, TPTF is available. */
645 if( trak->edts && trak->edts->elst && trak->edts->elst->list )
647 /**-- Explicit edits --**/
648 const isom_elst_t *elst = trak->edts->elst;
649 const isom_elst_entry_t *edit = NULL;
650 uint32_t movie_timescale = file->initializer->moov->mvhd->timescale;
651 uint64_t pts = subsegment->segment_duration;
652 /* This initialization is redundant since these are unused when uninitialized
653 * and are initialized always in used cases, but unclever compilers may
654 * complain that these variables may be uninitialized. */
655 TSAP = 0;
656 TDEC = 0;
657 TEPT = 0;
658 TPTF = 0;
659 /* */
660 subsegment_in_presentation = 0;
661 first_rp_in_presentation = 0;
662 first_sample_in_presentation = 0;
663 for( lsmash_entry_t *elst_entry = elst->list->head; elst_entry; elst_entry = elst_entry->next )
665 edit = (isom_elst_entry_t *)elst_entry->data;
666 if( !edit )
667 continue;
668 uint64_t edit_end_pts;
669 uint64_t edit_end_cts;
670 if( edit->segment_duration == ISOM_EDIT_DURATION_IMPLICIT
671 || (elst->version == 0 && edit->segment_duration == ISOM_EDIT_DURATION_UNKNOWN32)
672 || (elst->version == 1 && edit->segment_duration == ISOM_EDIT_DURATION_UNKNOWN64) )
674 edit_end_cts = UINT64_MAX;
675 edit_end_pts = UINT64_MAX;
677 else
679 if( edit->segment_duration )
681 double segment_duration = edit->segment_duration * ((double)sidx->timescale / movie_timescale);
682 edit_end_cts = edit->media_time + (uint64_t)(segment_duration * ((double)edit->media_rate / (1 << 16)));
683 edit_end_pts = pts + (uint64_t)segment_duration;
685 else
687 uint64_t segment_duration = composition_duration;
688 if( edit->media_time > subsegment->smallest_cts )
690 if( subsegment->largest_cts + track_fragment->last_duration > edit->media_time )
691 segment_duration -= edit->media_time - subsegment->smallest_cts;
692 else
693 segment_duration = 0;
695 edit_end_cts = edit->media_time + (uint64_t)(segment_duration * ((double)edit->media_rate / (1 << 16)));
696 edit_end_pts = pts + (uint64_t)segment_duration;
699 if( edit->media_time == ISOM_EDIT_MODE_EMPTY )
701 pts = edit_end_pts;
702 continue;
704 if( (subsegment->smallest_cts >= edit->media_time && subsegment->smallest_cts < edit_end_cts)
705 || (subsegment->largest_cts >= edit->media_time && subsegment->largest_cts < edit_end_cts) )
707 /* This subsegment is present in this edit. */
708 double rate = (double)edit->media_rate / (1 << 16);
709 uint64_t start_time = LSMASH_MAX( subsegment->smallest_cts, edit->media_time );
710 if( sidx->reference_count == 1 )
711 sidx->earliest_presentation_time = pts;
712 if( subsegment_in_presentation == 0 )
714 subsegment_in_presentation = 1;
715 if( subsegment->smallest_cts >= edit->media_time )
716 TEPT = pts + (uint64_t)((subsegment->smallest_cts - start_time) / rate);
717 else
718 TEPT = pts;
720 if( first_rp_in_presentation == 0
721 && ((subsegment->first_ed_cts >= edit->media_time && subsegment->first_ed_cts < edit_end_cts)
722 || (subsegment->first_rp_cts >= edit->media_time && subsegment->first_rp_cts < edit_end_cts)) )
724 /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */
725 first_rp_in_presentation = 1;
726 if( subsegment->first_ed_cts >= edit->media_time && subsegment->first_ed_cts < edit_end_cts )
727 TSAP = pts + (uint64_t)((subsegment->first_ed_cts - start_time) / rate);
728 else
729 TSAP = pts;
730 TDEC = TSAP;
732 if( first_sample_in_presentation == 0
733 && subsegment->first_cts >= edit->media_time && subsegment->first_cts < edit_end_cts )
735 first_sample_in_presentation = 1;
736 TPTF = pts + (uint64_t)((subsegment->first_cts - start_time) / rate);
738 uint64_t subsegment_end_pts = pts + (uint64_t)(composition_duration / rate);
739 pts = LSMASH_MIN( edit_end_pts, subsegment_end_pts );
740 /* Update subsegment_duration. */
741 data->subsegment_duration = pts - subsegment->segment_duration;
743 else
744 /* This subsegment is not present in this edit. */
745 pts = edit_end_pts;
748 else
750 /**-- Implicit edit --**/
751 if( sidx->reference_count == 1 )
752 sidx->earliest_presentation_time = subsegment->smallest_cts;
753 data->subsegment_duration = composition_duration;
754 /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */
755 TSAP = subsegment->first_rp_cts;
756 TDEC = subsegment->first_rp_cts;
757 TEPT = subsegment->smallest_cts;
758 TPTF = subsegment->first_cts;
759 subsegment_in_presentation = 1;
760 first_rp_in_presentation = 1;
761 first_sample_in_presentation = 1;
763 if( subsegment->first_ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE
764 || subsegment->first_ra_number == 0
765 || subsegment->first_rp_number == 0
766 || subsegment_in_presentation == 0
767 || first_rp_in_presentation == 0 )
769 /* No SAP in this subsegment. */
770 data->starts_with_SAP = 0;
771 data->SAP_type = 0;
772 data->SAP_delta_time = 0;
774 else
776 data->starts_with_SAP = (subsegment->first_ra_number == 1);
777 data->SAP_type = 0;
778 data->SAP_delta_time = TSAP - TEPT;
779 /* Decide SAP_type. */
780 if( first_sample_in_presentation )
782 if( TEPT == TDEC && TDEC == TSAP && TSAP == TPTF )
783 data->SAP_type = 1;
784 else if( TEPT == TDEC && TDEC == TSAP && TSAP < TPTF )
785 data->SAP_type = 2;
786 else if( TEPT < TDEC && TDEC == TSAP && TSAP <= TPTF )
787 data->SAP_type = 3;
788 else if( TEPT <= TPTF && TPTF < TDEC && TDEC == TSAP )
789 data->SAP_type = 4;
791 if( data->SAP_type == 0 )
793 if( TEPT == TDEC && TDEC < TSAP )
794 data->SAP_type = 5;
795 else if( TEPT < TDEC && TDEC < TSAP )
796 data->SAP_type = 6;
799 subsegment->segment_duration += data->subsegment_duration;
800 subsegment->first_ed_cts = UINT64_MAX;
801 subsegment->first_rp_cts = UINT64_MAX;
802 subsegment->first_rp_number = 0;
803 subsegment->first_ra_number = 0;
804 subsegment->first_ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE;
805 subsegment->decodable = 0;
807 return 0;
810 static int isom_finish_fragment_movie
812 lsmash_file_t *file
815 if( !file->fragment
816 || !file->fragment->pool )
817 return -1;
818 isom_moof_t *moof = file->fragment->movie;
819 if( !moof )
821 if( file == file->initializer )
822 return isom_finish_fragment_initial_movie( file );
823 else
824 return 0; /* No movie fragment to be finished. */
826 /* Don't write the current movie fragment if containing no track fragments.
827 * This is a requirement of DASH Media Segment. */
828 if( !moof->traf_list.head
829 || !moof->traf_list.head->data )
830 return 0;
831 /* Calculate appropriate default_sample_flags of each Track Fragment Header Box.
832 * And check whether that default_sample_flags is useful or not. */
833 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
835 isom_traf_t *traf = (isom_traf_t *)entry->data;
836 if( !traf
837 || !traf->tfhd
838 || !traf->file
839 || !traf->file->initializer
840 || !traf->file->initializer->moov
841 || !traf->file->initializer->moov->mvex )
842 return -1;
843 isom_tfhd_t *tfhd = traf->tfhd;
844 isom_trex_t *trex = isom_get_trex( file->initializer->moov->mvex, tfhd->track_ID );
845 if( !trex )
846 return -1;
847 struct sample_flags_stats_t
849 uint32_t is_leading [4];
850 uint32_t sample_depends_on [4];
851 uint32_t sample_is_depended_on [4];
852 uint32_t sample_has_redundancy [4];
853 uint32_t sample_is_non_sync_sample[2];
854 } stats = { { 0 }, { 0 }, { 0 }, { 0 }, { 0 } };
855 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
857 isom_trun_t *trun = (isom_trun_t *)trun_entry->data;
858 if( !trun || trun->sample_count == 0 )
859 return -1;
860 isom_sample_flags_t *sample_flags;
861 if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT )
863 if( !trun->optional )
864 return -1;
865 for( lsmash_entry_t *optional_entry = trun->optional->head; optional_entry; optional_entry = optional_entry->next )
867 isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data;
868 if( !row )
869 return -1;
870 sample_flags = &row->sample_flags;
871 ++ stats.is_leading [ sample_flags->is_leading ];
872 ++ stats.sample_depends_on [ sample_flags->sample_depends_on ];
873 ++ stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ];
874 ++ stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ];
875 ++ stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ];
878 else
880 sample_flags = &tfhd->default_sample_flags;
881 stats.is_leading [ sample_flags->is_leading ] += trun->sample_count;
882 stats.sample_depends_on [ sample_flags->sample_depends_on ] += trun->sample_count;
883 stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ] += trun->sample_count;
884 stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ] += trun->sample_count;
885 stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ] += trun->sample_count;
888 uint32_t most_used[5] = { 0, 0, 0, 0, 0 };
889 for( int i = 0; i < 4; i++ )
891 GET_MOST_USED( tfhd, 0, is_leading );
892 GET_MOST_USED( tfhd, 1, sample_depends_on );
893 GET_MOST_USED( tfhd, 2, sample_is_depended_on );
894 GET_MOST_USED( tfhd, 3, sample_has_redundancy );
895 if( i < 2 )
896 GET_MOST_USED( tfhd, 4, sample_is_non_sync_sample );
898 int useful_default_sample_duration = 0;
899 int useful_default_sample_size = 0;
900 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
902 isom_trun_t *trun = (isom_trun_t *)trun_entry->data;
903 if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT) )
904 useful_default_sample_duration = 1;
905 if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT) )
906 useful_default_sample_size = 1;
907 int useful_first_sample_flags = 1;
908 int useful_default_sample_flags = 1;
909 if( trun->sample_count == 1 )
911 /* It is enough to check only if first_sample_flags equals default_sample_flags or not.
912 * If it is equal, just use default_sample_flags.
913 * If not, just use first_sample_flags of this run. */
914 if( !isom_compare_sample_flags( &trun->first_sample_flags, &tfhd->default_sample_flags ) )
915 useful_first_sample_flags = 0;
917 else if( trun->optional
918 && trun->optional->head )
920 lsmash_entry_t *optional_entry = trun->optional->head->next;
921 isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data;
922 isom_sample_flags_t representative_sample_flags = row->sample_flags;
923 if( isom_compare_sample_flags( &tfhd->default_sample_flags, &representative_sample_flags ) )
924 useful_default_sample_flags = 0;
925 if( !isom_compare_sample_flags( &trun->first_sample_flags, &representative_sample_flags ) )
926 useful_first_sample_flags = 0;
927 if( useful_default_sample_flags )
928 for( optional_entry = optional_entry->next; optional_entry; optional_entry = optional_entry->next )
930 row = (isom_trun_optional_row_t *)optional_entry->data;
931 if( isom_compare_sample_flags( &representative_sample_flags, &row->sample_flags ) )
933 useful_default_sample_flags = 0;
934 break;
938 if( useful_default_sample_flags )
940 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT;
941 trun->flags &= ~ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT;
943 else
945 useful_first_sample_flags = 0;
946 trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT;
948 if( useful_first_sample_flags )
949 trun->flags |= ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT;
951 if( useful_default_sample_duration && tfhd->default_sample_duration != trex->default_sample_duration )
952 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
953 else
954 tfhd->default_sample_duration = trex->default_sample_duration; /* This might be redundant, but is to be more natural. */
955 if( useful_default_sample_size && tfhd->default_sample_size != trex->default_sample_size )
956 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT;
957 else
958 tfhd->default_sample_size = trex->default_sample_size; /* This might be redundant, but is to be more natural. */
959 if( !(tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT) )
960 tfhd->default_sample_flags = trex->default_sample_flags; /* This might be redundant, but is to be more natural. */
961 else if( !isom_compare_sample_flags( &tfhd->default_sample_flags, &trex->default_sample_flags ) )
962 tfhd->flags &= ~ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT;
964 /* Complete the last sample groups in the previous track fragments. */
965 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
967 isom_traf_t *traf = (isom_traf_t *)entry->data;
968 if( traf->cache->rap )
970 isom_sgpd_t *sgpd = isom_get_fragment_sample_group_description( traf, ISOM_GROUP_TYPE_RAP );
971 if( !sgpd || isom_rap_grouping_established( traf->cache->rap, 1, sgpd, 1 ) < 0 )
972 return -1;
973 lsmash_freep( &traf->cache->rap );
975 if( traf->cache->roll.pool )
977 isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &traf->sbgp_list );
978 if( !sbgp || isom_all_recovery_completed( sbgp, traf->cache->roll.pool ) < 0 )
979 return -1;
982 /* Establish Movie Fragment Box.
983 * We write exactly one Media Data Box starting immediately after the corresponding Movie Fragment Box. */
984 if( file->allow_moof_base )
986 /* In this branch, we use default-base-is-moof flag, which indicates implicit base_data_offsets originate in the
987 * first byte of each enclosing Movie Fragment Box.
988 * 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
989 * the type field of it as the data_offset of the first track run like the following.
991 * _____________ _ offset := 0
992 * | | |
993 * | m | s i z e |
994 * | |_________|
995 * | o | |
996 * | | t y p e |
997 * | o |_________|
998 * | | |
999 * | f | d a t a |
1000 * |___|_________|_ offset := the size of the Movie Fragment Box
1001 * | | |
1002 * | m | s i z e |
1003 * | |_________|
1004 * | d | |
1005 * | | t y p e |
1006 * | a |_________|_ offset := the data_offset of the first track run
1007 * | | |
1008 * | t | d a t a |
1009 * |___|_________|_ offset := the size of a subsegment containing exactly one movie fragment
1011 * For a pair of one Movie Fragment Box and one Media Data Box, placed in this order, implicit base_data_offsets
1012 * indicated by the absence of both base-data-offset-present and default-base-is-moof are somewhat complicated
1013 * since the implicit base_data_offset of the current track fragment is defined by the end of the data of the
1014 * previous track fragment and the data_offset of the track runs could be negative value because of interleaving
1015 * track runs or something other reasons.
1016 * In contrast, implicit base_data_offsets indicated by default-base-is-moof are simple since the base_data_offset
1017 * of each track fragment is always constant for that pair and has no dependency on other track fragments.
1019 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1021 isom_traf_t *traf = (isom_traf_t *)entry->data;
1022 traf->tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_BASE_IS_MOOF;
1023 traf->tfhd->base_data_offset = file->size; /* not written actually though */
1024 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
1026 /* Here, data_offset is always greater than zero. */
1027 isom_trun_t *trun = trun_entry->data;
1028 trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT;
1031 /* Consider the update of tr_flags here. */
1032 if( isom_update_box_size( moof ) == 0 )
1033 return -1;
1034 /* Now, we can calculate offsets in the current movie fragment, so do it. */
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 for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next )
1040 isom_trun_t *trun = trun_entry->data;
1041 trun->data_offset += moof->size + ISOM_BASEBOX_COMMON_SIZE;
1045 else
1047 /* In this branch, we use explicit base_data_offset. */
1048 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1050 isom_traf_t *traf = (isom_traf_t *)entry->data;
1051 traf->tfhd->flags |= ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT;
1053 /* Consider the update of tf_flags here. */
1054 if( isom_update_box_size( moof ) == 0 )
1055 return -1;
1056 /* Now, we can calculate offsets in the current movie fragment, so do it. */
1057 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1059 isom_traf_t *traf = (isom_traf_t *)entry->data;
1060 traf->tfhd->base_data_offset = file->size + moof->size + ISOM_BASEBOX_COMMON_SIZE;
1063 /* Write Movie Fragment Box and its children. */
1064 moof->pos = file->size;
1065 if( isom_write_box( file->bs, (isom_box_t *)moof ) < 0 )
1066 return -1;
1067 if( file->fragment->first_moof_pos == FIRST_MOOF_POS_UNDETERMINED )
1068 file->fragment->first_moof_pos = moof->pos;
1069 file->size += moof->size;
1070 /* Output samples. */
1071 if( isom_output_fragment_media_data( file ) < 0 )
1072 return -1;
1073 /* Revert the number of samples in track fragments to 0. */
1074 for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next )
1076 isom_traf_t *traf = (isom_traf_t *)entry->data;
1077 if( traf->cache->fragment )
1078 traf->cache->fragment->sample_count = 0;
1080 if( !(file->flags & LSMASH_FILE_MODE_INDEX) || file->max_isom_version < 6 )
1081 return 0;
1082 return isom_make_segment_index_entry( file, moof );
1085 #undef GET_MOST_USED
1087 static isom_trun_optional_row_t *isom_request_trun_optional_row( isom_trun_t *trun, isom_tfhd_t *tfhd, uint32_t sample_number )
1089 isom_trun_optional_row_t *row = NULL;
1090 if( !trun->optional )
1092 trun->optional = lsmash_create_entry_list();
1093 if( !trun->optional )
1094 return NULL;
1096 if( trun->optional->entry_count < sample_number )
1098 while( trun->optional->entry_count < sample_number )
1100 row = lsmash_malloc( sizeof(isom_trun_optional_row_t) );
1101 if( !row )
1102 return NULL;
1103 /* Copy from default. */
1104 row->sample_duration = tfhd->default_sample_duration;
1105 row->sample_size = tfhd->default_sample_size;
1106 row->sample_flags = tfhd->default_sample_flags;
1107 row->sample_composition_time_offset = 0;
1108 if( lsmash_add_entry( trun->optional, row ) < 0 )
1110 lsmash_free( row );
1111 return NULL;
1114 return row;
1116 uint32_t i = 0;
1117 for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next )
1119 row = (isom_trun_optional_row_t *)entry->data;
1120 if( !row )
1121 return NULL;
1122 if( ++i == sample_number )
1123 return row;
1125 return NULL;
1128 int lsmash_create_fragment_empty_duration
1130 lsmash_root_t *root,
1131 uint32_t track_ID,
1132 uint32_t duration
1135 if( isom_check_initializer_present( root ) < 0 )
1136 return -1;
1137 lsmash_file_t *file = root->file;
1138 if( !file->fragment
1139 || !file->fragment->movie
1140 || !file->initializer->moov )
1141 return -1;
1142 isom_trak_t *trak = isom_get_trak( file->initializer, track_ID );
1143 if( !trak
1144 || !trak->tkhd )
1145 return -1;
1146 isom_trex_t *trex = isom_get_trex( file->initializer->moov->mvex, track_ID );
1147 if( !trex )
1148 return -1;
1149 isom_moof_t *moof = file->fragment->movie;
1150 isom_traf_t *traf = isom_get_traf( moof, track_ID );
1151 if( traf )
1152 return -1;
1153 traf = isom_add_traf( moof );
1154 if( !isom_add_tfhd( traf ) )
1155 return -1;
1156 isom_tfhd_t *tfhd = traf->tfhd;
1157 tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */
1158 tfhd->track_ID = trak->tkhd->track_ID;
1159 tfhd->default_sample_duration = duration;
1160 if( duration != trex->default_sample_duration )
1161 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1162 traf->cache = trak->cache;
1163 traf->cache->fragment->traf_number = moof->traf_list.entry_count;
1164 traf->cache->fragment->last_duration += duration; /* The duration of the last sample includes this empty-duration. */
1165 return 0;
1168 int isom_set_fragment_last_duration
1170 isom_traf_t *traf,
1171 uint32_t last_duration
1174 isom_tfhd_t *tfhd = traf->tfhd;
1175 if( !traf->trun_list.tail
1176 || !traf->trun_list.tail->data )
1178 /* There are no track runs in this track fragment, so it is a empty-duration. */
1179 isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID );
1180 if( !trex )
1181 return -1;
1182 tfhd->flags |= ISOM_TF_FLAGS_DURATION_IS_EMPTY;
1183 if( last_duration != trex->default_sample_duration )
1184 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1185 tfhd->default_sample_duration = last_duration;
1186 traf->cache->fragment->last_duration = last_duration;
1187 return 0;
1189 /* Update the last sample_duration if needed. */
1190 isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data;
1191 if( trun->sample_count == 1
1192 && traf->trun_list.entry_count == 1 )
1194 isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID );
1195 if( !trex )
1196 return -1;
1197 if( last_duration != trex->default_sample_duration )
1198 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1199 tfhd->default_sample_duration = last_duration;
1201 else if( last_duration != tfhd->default_sample_duration )
1202 trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT;
1203 if( trun->flags )
1205 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count );
1206 if( !row )
1207 return -1;
1208 row->sample_duration = last_duration;
1210 traf->cache->fragment->last_duration = last_duration;
1211 return 0;
1214 int isom_append_fragment_track_run
1216 lsmash_file_t *file,
1217 isom_chunk_t *chunk
1220 if( !chunk->pool || chunk->pool->size == 0 )
1221 return 0;
1222 isom_fragment_manager_t *fragment = file->fragment;
1223 /* Move data in the pool of the current track fragment to the pool of the current movie fragment.
1224 * Empty the pool of current track. We don't delete data of samples here. */
1225 if( lsmash_add_entry( fragment->pool, chunk->pool ) < 0 )
1226 return -1;
1227 fragment->sample_count += chunk->pool->sample_count;
1228 fragment->pool_size += chunk->pool->size;
1229 chunk->pool = isom_create_sample_pool( chunk->pool->size );
1230 return chunk->pool ? 0 : -1;
1233 static int isom_output_fragment_cache( isom_traf_t *traf )
1235 isom_cache_t *cache = traf->cache;
1236 if( isom_append_fragment_track_run( traf->file, &cache->chunk ) < 0 )
1237 return -1;
1238 for( lsmash_entry_t *entry = traf->sgpd_list.head; entry; entry = entry->next )
1240 isom_sgpd_t *sgpd = (isom_sgpd_t *)entry->data;
1241 if( !sgpd )
1242 return -1;
1243 switch( sgpd->grouping_type )
1245 case ISOM_GROUP_TYPE_RAP :
1247 isom_rap_group_t *group = cache->rap;
1248 if( !group )
1250 if( traf->file->fragment )
1251 continue;
1252 else
1253 return -1;
1255 if( !group->random_access )
1256 continue;
1257 group->random_access->num_leading_samples_known = 1;
1258 break;
1260 case ISOM_GROUP_TYPE_ROLL :
1261 case ISOM_GROUP_TYPE_PROL :
1262 if( !cache->roll.pool )
1264 if( traf->file->fragment )
1265 continue;
1266 else
1267 return -1;
1269 isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &traf->sbgp_list );
1270 if( !sbgp || isom_all_recovery_completed( sbgp, cache->roll.pool ) < 0 )
1271 return -1;
1272 break;
1273 default :
1274 break;
1277 return 0;
1280 int isom_flush_fragment_pooled_samples
1282 lsmash_file_t *file,
1283 uint32_t track_ID,
1284 uint32_t last_sample_duration
1287 isom_traf_t *traf = isom_get_traf( file->fragment->movie, track_ID );
1288 if( !traf )
1289 /* No samples. We don't return as an error here since user might call the flushing function even if the
1290 * current movie fragment has no track fragment with this track_ID. */
1291 return 0;
1292 if( !traf->cache
1293 || !traf->cache->fragment )
1294 return -1;
1295 if( traf->trun_list.entry_count
1296 && traf->trun_list.tail
1297 && traf->trun_list.tail->data )
1299 /* Media Data Box preceded by Movie Fragment Box could change base_data_offsets in each track fragments later.
1300 * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */
1301 isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data;
1302 if( file->fragment->pool_size )
1303 trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT;
1304 trun->data_offset = file->fragment->pool_size;
1306 if( isom_output_fragment_cache( traf ) < 0 )
1307 return -1;
1308 return isom_set_fragment_last_duration( traf, last_sample_duration );
1311 /* This function doesn't update sample_duration of the last sample in the previous movie fragment.
1312 * Instead of this, isom_finish_movie_fragment undertakes this task. */
1313 static int isom_update_fragment_previous_sample_duration( isom_traf_t *traf, isom_trex_t *trex, uint32_t duration )
1315 isom_tfhd_t *tfhd = traf->tfhd;
1316 isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data;
1317 int previous_run_has_previous_sample = 0;
1318 if( trun->sample_count == 1 )
1320 if( traf->trun_list.entry_count == 1 )
1321 return 0; /* The previous track run belongs to the previous movie fragment if it exists. */
1322 if( !traf->trun_list.tail->prev
1323 || !traf->trun_list.tail->prev->data )
1324 return -1;
1325 /* OK. The previous sample exists in the previous track run in the same track fragment. */
1326 trun = (isom_trun_t *)traf->trun_list.tail->prev->data;
1327 previous_run_has_previous_sample = 1;
1329 /* Update default_sample_duration of the Track Fragment Header Box
1330 * if this duration is what the first sample in the current track fragment owns. */
1331 if( (trun->sample_count == 2 && traf->trun_list.entry_count == 1)
1332 || (trun->sample_count == 1 && traf->trun_list.entry_count == 2) )
1334 if( duration != trex->default_sample_duration )
1335 tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT;
1336 tfhd->default_sample_duration = duration;
1338 /* Update the previous sample_duration if needed. */
1339 if( duration != tfhd->default_sample_duration )
1340 trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT;
1341 if( trun->flags )
1343 uint32_t sample_number = trun->sample_count - !previous_run_has_previous_sample;
1344 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, sample_number );
1345 if( !row )
1346 return -1;
1347 row->sample_duration = duration;
1349 traf->cache->fragment->last_duration = duration;
1350 return 0;
1353 static isom_sample_flags_t isom_generate_fragment_sample_flags( lsmash_sample_t *sample )
1355 isom_sample_flags_t flags;
1356 flags.reserved = 0;
1357 flags.is_leading = sample->prop.leading & 0x3;
1358 flags.sample_depends_on = sample->prop.independent & 0x3;
1359 flags.sample_is_depended_on = sample->prop.disposable & 0x3;
1360 flags.sample_has_redundancy = sample->prop.redundant & 0x3;
1361 flags.sample_padding_value = 0;
1362 flags.sample_is_non_sync_sample = !(sample->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC);
1363 flags.sample_degradation_priority = 0;
1364 return flags;
1367 static int isom_update_fragment_sample_tables( isom_traf_t *traf, lsmash_sample_t *sample )
1369 isom_tfhd_t *tfhd = traf->tfhd;
1370 isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID );
1371 if( !trex )
1372 return -1;
1373 lsmash_file_t *file = traf->file;
1374 isom_cache_t *cache = traf->cache;
1375 isom_chunk_t *current = &cache->chunk;
1376 if( !current->pool )
1378 /* Very initial settings, just once per track */
1379 current->pool = isom_create_sample_pool( 0 );
1380 if( !current->pool )
1381 return -1;
1383 /* Create a new track run if the duration exceeds max_chunk_duration.
1384 * Old one will be appended to the pool of this movie fragment. */
1385 uint32_t media_timescale = lsmash_get_media_timescale( file->root, tfhd->track_ID );
1386 if( !media_timescale )
1387 return -1;
1388 int delimit = (file->max_chunk_duration < ((double)(sample->dts - current->first_dts) / media_timescale))
1389 || (file->max_chunk_size < (current->pool->size + sample->length));
1390 isom_trun_t *trun = NULL;
1391 if( !traf->trun_list.entry_count || delimit )
1393 if( delimit
1394 && traf->trun_list.entry_count
1395 && traf->trun_list.tail
1396 && traf->trun_list.tail->data )
1398 /* Media Data Box preceded by Movie Fragment Box could change base data offsets in each track fragments later.
1399 * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */
1400 trun = (isom_trun_t *)traf->trun_list.tail->data;
1401 if( file->fragment->pool_size )
1402 trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT;
1403 trun->data_offset = file->fragment->pool_size;
1405 trun = isom_add_trun( traf );
1406 if( !trun )
1407 return -1;
1409 else
1411 if( !traf->trun_list.tail
1412 || !traf->trun_list.tail->data )
1413 return -1;
1414 trun = (isom_trun_t *)traf->trun_list.tail->data;
1416 isom_sample_flags_t sample_flags = isom_generate_fragment_sample_flags( sample );
1417 if( ++trun->sample_count == 1 )
1419 if( traf->trun_list.entry_count == 1 )
1421 /* This track fragment isn't empty-duration-fragment any more. */
1422 tfhd->flags &= ~ISOM_TF_FLAGS_DURATION_IS_EMPTY;
1423 /* Set up sample_description_index in this track fragment. */
1424 if( sample->index != trex->default_sample_description_index )
1425 tfhd->flags |= ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT;
1426 tfhd->sample_description_index = current->sample_description_index = sample->index;
1427 /* Set up default_sample_size used in this track fragment. */
1428 tfhd->default_sample_size = sample->length;
1429 /* Set up default_sample_flags used in this track fragment.
1430 * Note: we decide an appropriate default value at the end of this movie fragment. */
1431 tfhd->default_sample_flags = sample_flags;
1432 /* Set up random access information if this sample is a sync sample.
1433 * We inform only the first sample in each movie fragment. */
1434 if( file->mfra && (sample->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC) )
1436 isom_tfra_t *tfra = isom_get_tfra( file->mfra, tfhd->track_ID );
1437 if( !tfra )
1439 tfra = isom_add_tfra( file->mfra );
1440 if( !tfra )
1441 return -1;
1442 tfra->track_ID = tfhd->track_ID;
1444 if( !tfra->list )
1446 tfra->list = lsmash_create_entry_list();
1447 if( !tfra->list )
1448 return -1;
1450 isom_tfra_location_time_entry_t *rap = lsmash_malloc( sizeof(isom_tfra_location_time_entry_t) );
1451 if( !rap )
1452 return -1;
1453 rap->time = sample->cts; /* Set composition timestamp temporally.
1454 * At the end of the whole movie, this will be reset as presentation time. */
1455 rap->moof_offset = file->size; /* We place Movie Fragment Box in the head of each movie fragment. */
1456 rap->traf_number = cache->fragment->traf_number;
1457 rap->trun_number = traf->trun_list.entry_count;
1458 rap->sample_number = trun->sample_count;
1459 if( lsmash_add_entry( tfra->list, rap ) < 0 )
1461 lsmash_free( rap );
1462 return -1;
1464 tfra->number_of_entry = tfra->list->entry_count;
1465 int length;
1466 for( length = 1; rap->traf_number >> (length * 8); length++ );
1467 tfra->length_size_of_traf_num = LSMASH_MAX( length - 1, tfra->length_size_of_traf_num );
1468 for( length = 1; rap->traf_number >> (length * 8); length++ );
1469 tfra->length_size_of_trun_num = LSMASH_MAX( length - 1, tfra->length_size_of_trun_num );
1470 for( length = 1; rap->sample_number >> (length * 8); length++ );
1471 tfra->length_size_of_sample_num = LSMASH_MAX( length - 1, tfra->length_size_of_sample_num );
1473 /* Set up the base media decode time of this track fragment.
1474 * This feature is available under ISO Base Media version 6 or later.
1475 * For DASH Media Segment, each Track Fragment Box shall contain a Track Fragment Base Media Decode Time Box. */
1476 if( file->max_isom_version >= 6 || file->media_segment )
1478 assert( !traf->tfdt );
1479 if( !isom_add_tfdt( traf ) )
1480 return -1;
1481 if( sample->dts > UINT32_MAX )
1482 traf->tfdt->version = 1;
1483 traf->tfdt->baseMediaDecodeTime = sample->dts;
1486 trun->first_sample_flags = sample_flags;
1487 current->first_dts = sample->dts;
1489 /* Update the optional rows in the current track run except for sample_duration if needed. */
1490 if( sample->length != tfhd->default_sample_size )
1491 trun->flags |= ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT;
1492 if( isom_compare_sample_flags( &sample_flags, &tfhd->default_sample_flags ) )
1493 trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT;
1494 uint32_t sample_composition_time_offset = sample->cts - sample->dts;
1495 if( sample_composition_time_offset )
1497 trun->flags |= ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT;
1498 /* Check if negative composition time offset is present. */
1499 isom_timestamp_t *ts_cache = &cache->timestamp;
1500 if( (sample->cts + ts_cache->ctd_shift) < sample->dts )
1502 if( file->max_isom_version < 6 )
1503 return -1; /* Negative composition time offset is not supported. */
1504 if( (sample->dts - sample->cts) > INT32_MAX )
1505 return -1; /* Overflow */
1506 ts_cache->ctd_shift = sample->dts - sample->cts;
1507 if( trun->version == 0 && file->max_isom_version >= 6 )
1508 trun->version = 1;
1511 if( trun->flags )
1513 isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count );
1514 if( !row )
1515 return -1;
1516 row->sample_size = sample->length;
1517 row->sample_flags = sample_flags;
1518 row->sample_composition_time_offset = sample_composition_time_offset;
1520 /* Set up the sample groupings for random access. */
1521 if( isom_group_random_access( (isom_box_t *)traf, sample ) < 0
1522 || isom_group_roll_recovery( (isom_box_t *)traf, sample ) < 0 )
1523 return -1;
1524 /* Set up the previous sample_duration if this sample is not the first sample in the overall movie. */
1525 if( cache->fragment->has_samples )
1527 /* Note: when using for live streaming, it is not good idea to return error (-1) by sample->dts < prev_dts
1528 * since that's trivial for such semi-permanent presentation. */
1529 uint64_t prev_dts = cache->timestamp.dts;
1530 if( sample->dts <= prev_dts
1531 || sample->dts > prev_dts + UINT32_MAX )
1532 return -1;
1533 uint32_t sample_duration = sample->dts - prev_dts;
1534 if( isom_update_fragment_previous_sample_duration( traf, trex, sample_duration ) < 0 )
1535 return -1;
1537 /* Cache */
1538 cache->timestamp.dts = sample->dts;
1539 cache->timestamp.cts = sample->cts;
1540 cache->fragment->largest_cts = LSMASH_MAX( sample->cts, cache->fragment->largest_cts );
1541 isom_subsegment_t *subsegment = &cache->fragment->subsegment;
1542 if( trun->sample_count == 1 && traf->trun_list.entry_count == 1 )
1544 subsegment->first_cts = sample->cts;
1545 subsegment->largest_cts = sample->cts;
1546 subsegment->smallest_cts = sample->cts;
1548 else
1550 subsegment->largest_cts = LSMASH_MAX( sample->cts, subsegment->largest_cts );
1551 subsegment->smallest_cts = LSMASH_MIN( sample->cts, subsegment->smallest_cts );
1553 if( subsegment->first_ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE )
1555 subsegment->first_ra_flags = sample->prop.ra_flags;
1556 subsegment->first_ra_number = cache->fragment->sample_count + 1;
1557 if( sample->prop.ra_flags & (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP) )
1559 subsegment->first_rp_number = subsegment->first_ra_number;
1560 subsegment->first_rp_cts = sample->cts;
1561 subsegment->first_ed_cts = sample->cts;
1562 subsegment->decodable = 1;
1565 else if( subsegment->decodable )
1567 if( (subsegment->first_ra_flags & (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP))
1568 ? (sample->prop.leading == ISOM_SAMPLE_IS_DECODABLE_LEADING)
1569 : (subsegment->first_ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START) )
1570 subsegment->first_ed_cts = LSMASH_MIN( sample->cts, subsegment->first_ed_cts );
1571 else
1572 subsegment->decodable = 0;
1574 return delimit;
1577 static int isom_append_fragment_sample_internal_initial( isom_trak_t *trak, lsmash_sample_t *sample )
1579 /* Update the sample tables of this track fragment.
1580 * If a new chunk was created, append the previous one to the pool of this movie fragment. */
1581 uint32_t samples_per_packet;
1582 int delimit = isom_update_sample_tables( trak, sample, &samples_per_packet );
1583 if( delimit < 0 )
1584 return -1;
1585 else if( delimit == 1 )
1586 isom_append_fragment_track_run( trak->file, &trak->cache->chunk );
1587 /* Add a new sample into the pool of this track fragment. */
1588 if( isom_pool_sample( trak->cache->chunk.pool, sample, samples_per_packet ) < 0 )
1589 return -1;
1590 trak->cache->fragment->has_samples = 1;
1591 trak->cache->fragment->sample_count += 1;
1592 return 0;
1595 static int isom_append_fragment_sample_internal( isom_traf_t *traf, lsmash_sample_t *sample )
1597 /* Update the sample tables of this track fragment.
1598 * If a new track run was created, append the previous one to the pool of this movie fragment. */
1599 int delimit = isom_update_fragment_sample_tables( traf, sample );
1600 if( delimit < 0 )
1601 return -1;
1602 else if( delimit == 1 )
1603 isom_append_fragment_track_run( traf->file, &traf->cache->chunk );
1604 /* Add a new sample into the pool of this track fragment. */
1605 if( isom_pool_sample( traf->cache->chunk.pool, sample, 1 ) < 0 )
1606 return -1;
1607 traf->cache->fragment->has_samples = 1;
1608 traf->cache->fragment->sample_count += 1;
1609 return 0;
1612 int isom_append_fragment_sample
1614 lsmash_file_t *file,
1615 uint32_t track_ID,
1616 lsmash_sample_t *sample
1619 isom_fragment_manager_t *fragment = file->fragment;
1620 assert( fragment && fragment->pool );
1621 isom_trak_t *trak = isom_get_trak( file->initializer, track_ID );
1622 if( !trak
1623 || !trak->file
1624 || !trak->cache
1625 || !trak->cache->fragment
1626 || !trak->tkhd
1627 || !trak->mdia
1628 || !trak->mdia->mdhd
1629 || trak->mdia->mdhd->timescale == 0
1630 || !trak->mdia->minf
1631 || !trak->mdia->minf->stbl
1632 || !trak->mdia->minf->stbl->stsd
1633 || !trak->mdia->minf->stbl->stsc || !trak->mdia->minf->stbl->stsc->list )
1634 return -1;
1635 /* Write the Segment Type Box here if required and if it was not written yet. */
1636 if( !(file->flags & LSMASH_FILE_MODE_INITIALIZATION)
1637 && file->styp_list.head && file->styp_list.head->data )
1639 isom_styp_t *styp = (isom_styp_t *)file->styp_list.head->data;
1640 if( !(styp->manager & LSMASH_WRITTEN_BOX) )
1642 if( isom_write_box( file->bs, (isom_box_t *)styp ) < 0 )
1643 return -1;
1644 file->size += styp->size;
1647 int (*append_sample_func)( void *, lsmash_sample_t * ) = NULL;
1648 void *track_fragment = NULL;
1649 if( !fragment->movie )
1651 /* Forbid adding a sample into the initial movie if requiring compatibility with Media Segment. */
1652 if( file->media_segment )
1653 return -1;
1654 append_sample_func = (int (*)( void *, lsmash_sample_t * ))isom_append_fragment_sample_internal_initial;
1655 track_fragment = trak;
1657 else
1659 isom_traf_t *traf = isom_get_traf( fragment->movie, track_ID );
1660 if( !traf )
1662 traf = isom_add_traf( fragment->movie );
1663 if( !isom_add_tfhd( traf ) )
1664 return -1;
1665 traf->tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */
1666 traf->tfhd->track_ID = trak->tkhd->track_ID;
1667 traf->cache = trak->cache;
1668 traf->cache->fragment->traf_number = fragment->movie->traf_list.entry_count;
1669 if( (traf->cache->fragment->rap_grouping && isom_add_sample_grouping( (isom_box_t *)traf, ISOM_GROUP_TYPE_RAP ) < 0)
1670 || (traf->cache->fragment->roll_grouping && isom_add_sample_grouping( (isom_box_t *)traf, ISOM_GROUP_TYPE_ROLL ) < 0) )
1671 return -1;
1673 else if( !traf->file
1674 || !traf->file->initializer
1675 || !traf->file->initializer->moov
1676 || !traf->file->initializer->moov->mvex
1677 || !traf->cache
1678 || !traf->tfhd )
1679 return -1;
1680 append_sample_func = (int (*)( void *, lsmash_sample_t * ))isom_append_fragment_sample_internal;
1681 track_fragment = traf;
1683 isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)lsmash_get_entry_data( &trak->mdia->minf->stbl->stsd->list, sample->index );
1684 if( !sample_entry )
1685 return -1;
1686 if( isom_is_lpcm_audio( sample_entry ) )
1688 uint32_t frame_size = ((isom_audio_entry_t *)sample_entry)->constBytesPerAudioPacket;
1689 if( sample->length == frame_size )
1690 return append_sample_func( track_fragment, sample );
1691 else if( sample->length < frame_size )
1692 return -1;
1693 /* Append samples splitted into each LPCMFrame. */
1694 uint64_t dts = sample->dts;
1695 uint64_t cts = sample->cts;
1696 for( uint32_t offset = 0; offset < sample->length; offset += frame_size )
1698 lsmash_sample_t *lpcm_sample = lsmash_create_sample( frame_size );
1699 if( !lpcm_sample )
1700 return -1;
1701 memcpy( lpcm_sample->data, sample->data + offset, frame_size );
1702 lpcm_sample->dts = dts++;
1703 lpcm_sample->cts = cts++;
1704 lpcm_sample->prop = sample->prop;
1705 lpcm_sample->index = sample->index;
1706 if( append_sample_func( track_fragment, lpcm_sample ) < 0 )
1708 lsmash_delete_sample( lpcm_sample );
1709 return -1;
1712 lsmash_delete_sample( sample );
1713 return 0;
1715 return append_sample_func( track_fragment, sample );