Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2000-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / ISO Media File Format sub-project
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 : #include <gpac/internal/isomedia_dev.h>
27 : #include <gpac/network.h>
28 : #include <gpac/thread.h>
29 :
30 : #ifndef GPAC_DISABLE_ISOM
31 :
32 : /**************************************************************
33 : Some Local functions for movie creation
34 : **************************************************************/
35 : GF_Err gf_isom_parse_root_box(GF_Box **outBox, GF_BitStream *bs, u32 *boxType, u64 *bytesExpected, Bool progressive_mode);
36 :
37 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
38 3938 : GF_Err MergeFragment(GF_MovieFragmentBox *moof, GF_ISOFile *mov)
39 : {
40 : GF_Err e;
41 : u32 i, j;
42 : u64 MaxDur;
43 : GF_TrackFragmentBox *traf;
44 : GF_TrackBox *trak;
45 : u64 base_data_offset;
46 :
47 : MaxDur = 0;
48 :
49 : //we shall have a MOOV and its MVEX BEFORE any MOOF
50 3938 : if (!mov->moov || !mov->moov->mvex) {
51 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Error: %s not received before merging fragment\n", mov->moov ? "mvex" : "moov" ));
52 : return GF_ISOM_INVALID_FILE;
53 : }
54 : //and all fragments should be continous but:
55 : //- dash with dependent representations may likely give R1(moofSN 1, 3, 5, 7) plus R2(moofSN 2, 4, 6, 8)
56 : //- smooth muxed in a single file may end up with V(1),A(1), V(2),A(2) ...
57 : //we do not throw an error if not as we may still want to be able to concatenate dependent representations in DASH and
58 3938 : if (mov->NextMoofNumber && moof->mfhd && (mov->NextMoofNumber >= moof->mfhd->sequence_number)) {
59 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] wrong sequence number: got %d but last one was %d\n", moof->mfhd->sequence_number, mov->NextMoofNumber));
60 : }
61 :
62 3938 : base_data_offset = mov->current_top_box_start;
63 3938 : if (moof->compressed_diff)
64 10 : base_data_offset -= moof->compressed_diff;
65 :
66 3938 : i=0;
67 11955 : while ((traf = (GF_TrackFragmentBox*)gf_list_enum(moof->TrackList, &i))) {
68 4079 : if (!traf->tfhd) {
69 : trak = NULL;
70 0 : traf->trex = NULL;
71 4079 : } else if (mov->is_smooth) {
72 25 : trak = gf_list_get(mov->moov->trackList, 0);
73 25 : traf->trex = (GF_TrackExtendsBox*)gf_list_get(mov->moov->mvex->TrackExList, 0);
74 : assert(traf->trex);
75 25 : traf->trex->trackID = trak->Header->trackID = traf->tfhd->trackID;
76 : } else {
77 4054 : trak = gf_isom_get_track_from_id(mov->moov, traf->tfhd->trackID);
78 4054 : j=0;
79 15483 : while ((traf->trex = (GF_TrackExtendsBox*)gf_list_enum(mov->moov->mvex->TrackExList, &j))) {
80 11429 : if (traf->trex->trackID == traf->tfhd->trackID) break;
81 7375 : traf->trex = NULL;
82 : }
83 : }
84 :
85 4079 : if (!trak || !traf->trex) {
86 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Error: Cannot find fragment track with ID %d\n", traf->tfhd ? traf->tfhd->trackID : 0));
87 : return GF_ISOM_INVALID_FILE;
88 : }
89 :
90 4079 : e = MergeTrack(trak, traf, moof, mov->current_top_box_start, moof->compressed_diff, &base_data_offset, !trak->first_traf_merged);
91 4079 : if (e) return e;
92 :
93 4079 : trak->present_in_scalable_segment = 1;
94 :
95 : //update trak duration
96 4079 : SetTrackDuration(trak);
97 4079 : if (trak->Header->duration > MaxDur)
98 : MaxDur = trak->Header->duration;
99 :
100 4079 : trak->first_traf_merged = GF_TRUE;
101 : }
102 :
103 3938 : if (moof->child_boxes) {
104 : GF_Box *a;
105 3938 : i = 0;
106 15893 : while ((a = (GF_Box *)gf_list_enum(moof->child_boxes, &i))) {
107 8017 : if (a->type == GF_ISOM_BOX_TYPE_PSSH) {
108 0 : GF_ProtectionSystemHeaderBox *pssh = (GF_ProtectionSystemHeaderBox *)gf_isom_box_new_parent(&mov->moov->child_boxes, GF_ISOM_BOX_TYPE_PSSH);
109 0 : if (!pssh) return GF_OUT_OF_MEM;
110 0 : memmove(pssh->SystemID, ((GF_ProtectionSystemHeaderBox *)a)->SystemID, 16);
111 0 : if (((GF_ProtectionSystemHeaderBox *)a)->KIDs && ((GF_ProtectionSystemHeaderBox *)a)->KID_count > 0) {
112 0 : pssh->KID_count = ((GF_ProtectionSystemHeaderBox *)a)->KID_count;
113 0 : pssh->KIDs = (bin128 *)gf_malloc(pssh->KID_count*sizeof(bin128));
114 0 : if (!pssh->KIDs) return GF_OUT_OF_MEM;
115 :
116 0 : memmove(pssh->KIDs, ((GF_ProtectionSystemHeaderBox *)a)->KIDs, pssh->KID_count*sizeof(bin128));
117 : }
118 0 : pssh->private_data_size = ((GF_ProtectionSystemHeaderBox *)a)->private_data_size;
119 0 : pssh->private_data = (u8 *)gf_malloc(pssh->private_data_size*sizeof(char));
120 0 : if (!pssh->private_data) return GF_OUT_OF_MEM;
121 0 : memmove(pssh->private_data, ((GF_ProtectionSystemHeaderBox *)a)->private_data, pssh->private_data_size);
122 : }
123 : }
124 : }
125 :
126 3938 : mov->NextMoofNumber = moof->mfhd ? moof->mfhd->sequence_number : 0;
127 : //update movie duration
128 3938 : if (mov->moov->mvhd->duration < MaxDur) mov->moov->mvhd->duration = MaxDur;
129 : return GF_OK;
130 : }
131 :
132 3957 : static void FixTrackID(GF_ISOFile *mov)
133 : {
134 3957 : if (!mov->moov) return;
135 :
136 3956 : if (gf_list_count(mov->moov->trackList) == 1 && gf_list_count(mov->moof->TrackList) == 1) {
137 2208 : GF_TrackFragmentBox *traf = (GF_TrackFragmentBox*)gf_list_get(mov->moof->TrackList, 0);
138 2208 : GF_TrackBox *trak = (GF_TrackBox*)gf_list_get(mov->moov->trackList, 0);
139 2208 : if (!traf->tfhd || !trak->Header) return;
140 2208 : if ((traf->tfhd->trackID != trak->Header->trackID)) {
141 9 : if (!mov->is_smooth) {
142 6 : GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Warning: trackID of MOOF/TRAF(%u) is not the same as MOOV/TRAK(%u). Trying to fix.\n", traf->tfhd->trackID, trak->Header->trackID));
143 : } else {
144 3 : trak->Header->trackID = traf->tfhd->trackID;
145 : }
146 9 : traf->tfhd->trackID = trak->Header->trackID;
147 : }
148 : }
149 : }
150 :
151 3938 : static void FixSDTPInTRAF(GF_MovieFragmentBox *moof)
152 : {
153 : u32 k;
154 3938 : if (!moof)
155 : return;
156 :
157 4079 : for (k = 0; k < gf_list_count(moof->TrackList); k++) {
158 4079 : GF_TrackFragmentBox *traf = gf_list_get(moof->TrackList, k);
159 4079 : if (traf->sdtp) {
160 : GF_TrackFragmentRunBox *trun;
161 29 : u32 j = 0, sample_index = 0;
162 :
163 29 : if (traf->sdtp->sampleCount == gf_list_count(traf->TrackRuns)) {
164 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Warning: TRAF box of track id=%u contains a SDTP. Converting to TRUN sample flags.\n", traf->tfhd->trackID));
165 : }
166 :
167 58 : while ((trun = (GF_TrackFragmentRunBox*)gf_list_enum(traf->TrackRuns, &j))) {
168 : u32 i;
169 29 : trun->flags |= GF_ISOM_TRUN_FLAGS;
170 1853 : for (i=0; i<trun->nb_samples; i++) {
171 1824 : GF_TrunEntry *entry = &trun->samples[i];
172 1824 : const u8 info = traf->sdtp->sample_info[sample_index];
173 1824 : entry->flags |= GF_ISOM_GET_FRAG_DEPEND_FLAGS(info >> 6, info >> 4, info >> 2, info);
174 1824 : sample_index++;
175 1824 : if (sample_index > traf->sdtp->sampleCount) {
176 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Error: TRAF box of track id=%u contained an inconsistent SDTP.\n", traf->tfhd->trackID));
177 0 : return;
178 : }
179 : }
180 : }
181 29 : if (sample_index < traf->sdtp->sampleCount) {
182 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Error: TRAF box of track id=%u list less samples than SDTP.\n", traf->tfhd->trackID));
183 : }
184 29 : gf_isom_box_del_parent(&traf->child_boxes, (GF_Box*)traf->sdtp);
185 29 : traf->sdtp = NULL;
186 : }
187 : }
188 : }
189 :
190 366 : void gf_isom_push_mdat_end(GF_ISOFile *mov, u64 mdat_end)
191 : {
192 : u32 i, count;
193 366 : if (!mov || !mov->moov) return;
194 :
195 366 : count = gf_list_count(mov->moov->trackList);
196 732 : for (i=0; i<count; i++) {
197 : u32 j;
198 : GF_TrafToSampleMap *traf_map;
199 366 : GF_TrackBox *trak = gf_list_get(mov->moov->trackList, i);
200 366 : if (!trak->Media->information->sampleTable->traf_map) continue;
201 :
202 : traf_map = trak->Media->information->sampleTable->traf_map;
203 594 : for (j=traf_map->nb_entries; j>0; j--) {
204 195 : if (!traf_map->frag_starts[j-1].mdat_end) {
205 195 : traf_map->frag_starts[j-1].mdat_end = mdat_end;
206 195 : break;
207 : }
208 : }
209 : }
210 : }
211 :
212 : #ifdef GF_ENABLE_CTRN
213 : static void gf_isom_setup_traf_inheritance(GF_ISOFile *mov)
214 : {
215 : u32 i, count;
216 : if (!mov->moov->mvex)
217 : return;
218 : count = gf_list_count(mov->moov->trackList);
219 :
220 : for (i=0; i<count; i++) {
221 : u32 refTrackNum=0;
222 : gf_isom_get_reference(mov, i+1, GF_ISOM_REF_TRIN, 1, &refTrackNum);
223 : if (refTrackNum) {
224 : GF_ISOTrackID tkid = gf_isom_get_track_id(mov, i+1);
225 : GF_ISOTrackID reftkid = gf_isom_get_track_id(mov, refTrackNum);
226 : GF_TrackExtendsBox *trex = GetTrex(mov->moov, tkid);
227 : if (trex) trex->inherit_from_traf_id = reftkid;
228 : }
229 : }
230 : }
231 : #endif
232 :
233 : #endif
234 :
235 : //for now we only use regular sample to group internally (except when dumping), not the pattern version
236 : //we unrill the pattern and replace the compact version with a regular one
237 68 : static void convert_compact_sample_groups(GF_List *child_boxes, GF_List *sampleGroups)
238 : {
239 : u32 i;
240 136 : for (i=0; i<gf_list_count(sampleGroups); i++) {
241 : u32 j;
242 : GF_SampleGroupBox *sbgp;
243 68 : GF_CompactSampleGroupBox *csgp = gf_list_get(sampleGroups, i);
244 68 : if (csgp->type != GF_ISOM_BOX_TYPE_CSGP) continue;
245 :
246 0 : gf_list_rem(sampleGroups, i);
247 0 : gf_list_del_item(child_boxes, csgp);
248 :
249 0 : sbgp = (GF_SampleGroupBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_SBGP);
250 0 : gf_list_insert(sampleGroups, sbgp, i);
251 0 : gf_list_add(child_boxes, sbgp);
252 0 : i--;
253 :
254 0 : sbgp->grouping_type = csgp->grouping_type;
255 0 : if (csgp->grouping_type_parameter) {
256 0 : sbgp->grouping_type_parameter = csgp->grouping_type_parameter;
257 0 : sbgp->version = 1;
258 : }
259 0 : sbgp->entry_count = 0;
260 0 : for (j=0; j<csgp->pattern_count; j++) {
261 : u32 k=0;
262 0 : u32 nb_samples = csgp->patterns[j].sample_count;
263 : //unroll the pattern
264 0 : while (nb_samples) {
265 : u32 nb_same_index=1;
266 0 : u32 sg_idx = csgp->patterns[j].sample_group_description_indices[k];
267 0 : while (nb_same_index+k<csgp->patterns[j].length) {
268 0 : if (csgp->patterns[j].sample_group_description_indices[k+nb_same_index] != sg_idx)
269 : break;
270 0 : nb_same_index++;
271 : }
272 0 : sbgp->sample_entries = gf_realloc(sbgp->sample_entries, sizeof(GF_SampleGroupEntry) * (sbgp->entry_count+1));
273 0 : if (nb_same_index>nb_samples)
274 : nb_same_index = nb_samples;
275 :
276 0 : sbgp->sample_entries[sbgp->entry_count].sample_count = nb_same_index;
277 0 : sbgp->sample_entries[sbgp->entry_count].group_description_index = sg_idx;
278 0 : nb_samples -= nb_same_index;
279 0 : sbgp->entry_count++;
280 0 : k+= nb_same_index;
281 0 : if (k==csgp->patterns[j].length)
282 : k = 0;
283 : }
284 : }
285 : }
286 68 : }
287 :
288 :
289 9203 : static GF_Err gf_isom_parse_movie_boxes_internal(GF_ISOFile *mov, u32 *boxType, u64 *bytesMissing, Bool progressive_mode)
290 : {
291 : GF_Box *a;
292 : u64 totSize, mdat_end=0;
293 : GF_Err e = GF_OK;
294 :
295 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
296 9203 : if (mov->single_moof_mode && mov->single_moof_state == 2) {
297 : return e;
298 : }
299 :
300 : /*restart from where we stopped last*/
301 9203 : totSize = mov->current_top_box_start;
302 9203 : if (mov->bytes_removed) {
303 : assert(totSize >= mov->bytes_removed);
304 3756 : totSize -= mov->bytes_removed;
305 : }
306 9203 : gf_bs_seek(mov->movieFileMap->bs, totSize);
307 : #endif
308 :
309 :
310 : /*while we have some data, parse our boxes*/
311 38053 : while (gf_bs_available(mov->movieFileMap->bs)) {
312 24139 : *bytesMissing = 0;
313 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
314 24139 : mov->current_top_box_start = gf_bs_get_position(mov->movieFileMap->bs) + mov->bytes_removed;
315 24139 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] Parsing a top-level box at position %d\n", mov->current_top_box_start));
316 : #endif
317 :
318 24139 : e = gf_isom_parse_root_box(&a, mov->movieFileMap->bs, boxType, bytesMissing, progressive_mode);
319 :
320 24139 : if (e >= 0) {
321 :
322 4492 : } else if (e == GF_ISOM_INCOMPLETE_FILE) {
323 : /*our mdat is uncomplete, only valid for READ ONLY files...*/
324 4492 : if (mov->openMode != GF_ISOM_OPEN_READ) {
325 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Incomplete MDAT while file is not read-only\n"));
326 : return GF_ISOM_INVALID_FILE;
327 : }
328 4492 : if ((mov->openMode == GF_ISOM_OPEN_READ) && !progressive_mode) {
329 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Incomplete file while reading for dump - aborting parsing\n"));
330 : break;
331 : }
332 : return e;
333 : } else {
334 : return e;
335 : }
336 :
337 19647 : switch (a->type) {
338 : /*MOOV box*/
339 1664 : case GF_ISOM_BOX_TYPE_MOOV:
340 1664 : if (mov->moov) {
341 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Duplicate MOOV detected!\n"));
342 0 : gf_isom_box_del(a);
343 0 : return GF_ISOM_INVALID_FILE;
344 : }
345 1664 : mov->moov = (GF_MovieBox *)a;
346 1664 : mov->original_moov_offset = mov->current_top_box_start;
347 : /*set our pointer to the movie*/
348 1664 : mov->moov->mov = mov;
349 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
350 1664 : if (mov->moov->mvex) mov->moov->mvex->mov = mov;
351 :
352 : #ifdef GF_ENABLE_CTRN
353 : if (! (mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG)) {
354 : gf_isom_setup_traf_inheritance(mov);
355 : }
356 : #endif
357 :
358 : #endif
359 1664 : e = gf_list_add(mov->TopBoxes, a);
360 1664 : if (e) return e;
361 :
362 : totSize += a->size;
363 :
364 1664 : if (!mov->moov->mvhd) {
365 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing MovieHeaderBox\n"));
366 : return GF_ISOM_INVALID_FILE;
367 : }
368 :
369 1664 : if (mov->meta) {
370 17 : gf_isom_meta_restore_items_ref(mov, mov->meta);
371 : }
372 :
373 : //dump senc info in dump mode
374 1664 : if (mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) {
375 : u32 k;
376 343 : for (k=0; k<gf_list_count(mov->moov->trackList); k++) {
377 343 : GF_TrackBox *trak = (GF_TrackBox *)gf_list_get(mov->moov->trackList, k);
378 :
379 343 : if (trak->sample_encryption) {
380 76 : e = senc_Parse(mov->movieFileMap->bs, trak, NULL, trak->sample_encryption);
381 76 : if (e) return e;
382 : }
383 : }
384 : } else {
385 : u32 k;
386 2041 : for (k=0; k<gf_list_count(mov->moov->trackList); k++) {
387 2041 : GF_TrackBox *trak = (GF_TrackBox *)gf_list_get(mov->moov->trackList, k);
388 2041 : if (trak->Media->information->sampleTable->sampleGroups) {
389 68 : convert_compact_sample_groups(trak->Media->information->sampleTable->child_boxes, trak->Media->information->sampleTable->sampleGroups);
390 : }
391 : }
392 : }
393 :
394 1664 : if (mdat_end && mov->signal_frag_bounds && !(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) ) {
395 0 : gf_isom_push_mdat_end(mov, mdat_end);
396 : mdat_end=0;
397 : }
398 : break;
399 :
400 : /*META box*/
401 69 : case GF_ISOM_BOX_TYPE_META:
402 69 : if (mov->meta) {
403 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Duplicate META detected!\n"));
404 0 : gf_isom_box_del(a);
405 0 : return GF_ISOM_INVALID_FILE;
406 : }
407 69 : mov->meta = (GF_MetaBox *)a;
408 69 : mov->original_meta_offset = mov->current_top_box_start;
409 69 : e = gf_list_add(mov->TopBoxes, a);
410 69 : if (e) {
411 : return e;
412 : }
413 : totSize += a->size;
414 69 : if (mov->moov) {
415 0 : gf_isom_meta_restore_items_ref(mov, mov->meta);
416 : }
417 : break;
418 :
419 : /*we only keep the MDAT in READ for dump purposes*/
420 5402 : case GF_ISOM_BOX_TYPE_MDAT:
421 5402 : if (!mov->first_data_toplevel_offset) {
422 1543 : mov->first_data_toplevel_offset = mov->current_top_box_start;
423 1543 : mov->first_data_toplevel_size = a->size;
424 : }
425 : totSize += a->size;
426 5402 : if (mov->openMode == GF_ISOM_OPEN_READ) {
427 5180 : if (!mov->mdat) {
428 1485 : mov->mdat = (GF_MediaDataBox *) a;
429 1485 : e = gf_list_add(mov->TopBoxes, mov->mdat);
430 1485 : if (e) {
431 : return e;
432 : }
433 : }
434 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
435 3695 : else if (mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) gf_list_add(mov->TopBoxes, a);
436 : #endif
437 3683 : else gf_isom_box_del(a); //in other modes we don't care
438 :
439 :
440 5180 : if (mov->signal_frag_bounds && !(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) ) {
441 195 : mdat_end = gf_bs_get_position(mov->movieFileMap->bs);
442 195 : if (mov->moov) {
443 195 : gf_isom_push_mdat_end(mov, mdat_end);
444 : mdat_end=0;
445 : }
446 : }
447 : }
448 : /*if we don't have any MDAT yet, create one (edit-write mode)
449 : We only work with one mdat, but we're puting it at the place
450 : of the first mdat found when opening a file for editing*/
451 222 : else if (!mov->mdat && (mov->openMode != GF_ISOM_OPEN_READ) && (mov->openMode != GF_ISOM_OPEN_KEEP_FRAGMENTS)) {
452 222 : gf_isom_box_del(a);
453 222 : mov->mdat = (GF_MediaDataBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MDAT);
454 222 : if (!mov->mdat) return GF_OUT_OF_MEM;
455 222 : e = gf_list_add(mov->TopBoxes, mov->mdat);
456 222 : if (e) {
457 : return e;
458 : }
459 : } else {
460 0 : gf_isom_box_del(a);
461 : }
462 : break;
463 1739 : case GF_ISOM_BOX_TYPE_FTYP:
464 : /*ONE AND ONLY ONE FTYP*/
465 1739 : if (mov->brand) {
466 0 : gf_isom_box_del(a);
467 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Duplicate 'ftyp' detected!\n"));
468 : return GF_ISOM_INVALID_FILE;
469 : }
470 1739 : mov->brand = (GF_FileTypeBox *)a;
471 : totSize += a->size;
472 1739 : e = gf_list_add(mov->TopBoxes, a);
473 1739 : if (e) return e;
474 : break;
475 :
476 0 : case GF_ISOM_BOX_TYPE_OTYP:
477 : /*ONE AND ONLY ONE FTYP*/
478 0 : if (mov->otyp) {
479 0 : gf_isom_box_del(a);
480 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Duplicate 'otyp' detected!\n"));
481 : return GF_ISOM_INVALID_FILE;
482 : }
483 :
484 0 : if (mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) {
485 0 : mov->otyp = (GF_Box *)a;
486 : totSize += a->size;
487 0 : e = gf_list_add(mov->TopBoxes, a);
488 0 : if (e) return e;
489 : } else {
490 0 : GF_FileTypeBox *brand = (GF_FileTypeBox *) gf_isom_box_find_child(a->child_boxes, GF_ISOM_BOX_TYPE_FTYP);
491 0 : if (brand) {
492 : s32 pos;
493 0 : gf_list_del_item(a->child_boxes, brand);
494 0 : pos = gf_list_del_item(mov->TopBoxes, mov->brand);
495 0 : gf_isom_box_del((GF_Box *) mov->brand);
496 0 : mov->brand = brand;
497 0 : if (pos<0) pos=0;
498 0 : gf_list_insert(mov->TopBoxes, brand, pos);
499 : }
500 : }
501 : break;
502 :
503 0 : case GF_ISOM_BOX_TYPE_PDIN:
504 : /*ONE AND ONLY ONE PDIN*/
505 0 : if (mov->pdin) {
506 0 : gf_isom_box_del(a);
507 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Duplicate 'pdin'' detected!\n"));
508 : return GF_ISOM_INVALID_FILE;
509 : }
510 0 : mov->pdin = (GF_ProgressiveDownloadBox *) a;
511 : totSize += a->size;
512 0 : e = gf_list_add(mov->TopBoxes, a);
513 0 : if (e) return e;
514 : break;
515 :
516 :
517 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
518 2620 : case GF_ISOM_BOX_TYPE_STYP:
519 : {
520 2620 : u32 brand = ((GF_FileTypeBox *)a)->majorBrand;
521 2620 : switch (brand) {
522 0 : case GF_ISOM_BRAND_SISX:
523 : case GF_ISOM_BRAND_RISX:
524 : case GF_ISOM_BRAND_SSSS:
525 0 : mov->is_index_segment = GF_TRUE;
526 0 : break;
527 : default:
528 : break;
529 : }
530 5010 : }
531 : /*fall-through*/
532 :
533 : case GF_ISOM_BOX_TYPE_SIDX:
534 : case GF_ISOM_BOX_TYPE_SSIX:
535 5010 : if (mov->moov && !mov->first_data_toplevel_offset) {
536 369 : mov->first_data_toplevel_offset = mov->current_top_box_start;
537 369 : mov->first_data_toplevel_size = a->size;
538 : }
539 : totSize += a->size;
540 5010 : if (mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) {
541 6 : e = gf_list_add(mov->TopBoxes, a);
542 6 : if (e) return e;
543 5004 : } else if (mov->signal_frag_bounds && !(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) && (mov->openMode!=GF_ISOM_OPEN_KEEP_FRAGMENTS)
544 : ) {
545 171 : if (a->type==GF_ISOM_BOX_TYPE_SIDX) {
546 87 : if (mov->root_sidx) gf_isom_box_del( (GF_Box *) mov->root_sidx);
547 87 : mov->root_sidx = (GF_SegmentIndexBox *) a;
548 87 : mov->sidx_start_offset = mov->current_top_box_start;
549 87 : mov->sidx_end_offset = gf_bs_get_position(mov->movieFileMap->bs);
550 :
551 : }
552 84 : else if (a->type==GF_ISOM_BOX_TYPE_STYP) {
553 84 : mov->styp_start_offset = mov->current_top_box_start;
554 :
555 84 : if (mov->seg_styp) gf_isom_box_del(mov->seg_styp);
556 84 : mov->seg_styp = a;
557 0 : } else if (a->type==GF_ISOM_BOX_TYPE_SSIX) {
558 0 : if (mov->seg_ssix) gf_isom_box_del(mov->seg_ssix);
559 0 : mov->seg_ssix = a;
560 : } else {
561 0 : gf_isom_box_del(a);
562 : }
563 171 : gf_isom_push_mdat_end(mov, mov->current_top_box_start);
564 4833 : } else if (!mov->NextMoofNumber && (a->type==GF_ISOM_BOX_TYPE_SIDX)) {
565 2300 : if (mov->main_sidx) gf_isom_box_del( (GF_Box *) mov->main_sidx);
566 2300 : mov->main_sidx = (GF_SegmentIndexBox *) a;
567 2300 : mov->main_sidx_end_pos = mov->current_top_box_start + a->size;
568 : } else {
569 2533 : gf_isom_box_del(a);
570 : }
571 : break;
572 :
573 3957 : case GF_ISOM_BOX_TYPE_MOOF:
574 : //no support for inplace rewrite for fragmented files
575 3957 : gf_isom_disable_inplace_rewrite(mov);
576 3957 : if (!mov->moov) {
577 1 : GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Movie fragment but no moov (yet) - possibly broken parsing!\n"));
578 : }
579 3957 : if (mov->single_moof_mode) {
580 0 : mov->single_moof_state++;
581 0 : if (mov->single_moof_state > 1) {
582 0 : gf_isom_box_del(a);
583 0 : return GF_OK;
584 : }
585 : }
586 3957 : ((GF_MovieFragmentBox *)a)->mov = mov;
587 :
588 : totSize += a->size;
589 3957 : mov->moof = (GF_MovieFragmentBox *) a;
590 :
591 : /*some smooth streaming streams contain a SDTP under the TRAF: this is incorrect, convert it*/
592 3957 : FixTrackID(mov);
593 3957 : if (! (mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG)) {
594 3938 : FixSDTPInTRAF(mov->moof);
595 : } else {
596 : u32 k;
597 19 : for (k=0; k<gf_list_count(mov->moof->TrackList); k++) {
598 19 : GF_TrackFragmentBox *traf = (GF_TrackFragmentBox *)gf_list_get(mov->moof->TrackList, k);
599 19 : if (traf->sampleGroups) {
600 0 : convert_compact_sample_groups(traf->child_boxes, traf->sampleGroups);
601 : }
602 : }
603 : }
604 :
605 : /*read & debug: store at root level*/
606 3957 : if (mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) {
607 : u32 k;
608 19 : gf_list_add(mov->TopBoxes, a);
609 : /*also update pointers to trex for debug*/
610 19 : if (mov->moov) {
611 18 : for (k=0; k<gf_list_count(mov->moof->TrackList); k++) {
612 18 : GF_TrackFragmentBox *traf = gf_list_get(mov->moof->TrackList, k);
613 18 : if (traf->tfhd && mov->moov->mvex && mov->moov->mvex->TrackExList) {
614 18 : GF_TrackBox *trak = gf_isom_get_track_from_id(mov->moov, traf->tfhd->trackID);
615 18 : u32 j=0;
616 42 : while ((traf->trex = (GF_TrackExtendsBox*)gf_list_enum(mov->moov->mvex->TrackExList, &j))) {
617 24 : if (traf->trex->trackID == traf->tfhd->trackID) {
618 18 : if (!traf->trex->track) traf->trex->track = trak;
619 : break;
620 : }
621 6 : traf->trex = NULL;
622 : }
623 : }
624 : //we should only parse senc/psec when no saiz/saio is present, otherwise we fetch the info directly
625 18 : if (traf->trex && traf->tfhd && traf->trex->track && traf->sample_encryption) {
626 7 : GF_TrackBox *trak = GetTrackbyID(mov->moov, traf->tfhd->trackID);
627 7 : trak->current_traf_stsd_idx = traf->tfhd->sample_desc_index ? traf->tfhd->sample_desc_index : traf->trex->def_sample_desc_index;
628 7 : e = senc_Parse(mov->movieFileMap->bs, trak, traf, traf->sample_encryption);
629 7 : if (e) return e;
630 7 : trak->current_traf_stsd_idx = 0;
631 : }
632 : }
633 : } else {
634 1 : for (k=0; k<gf_list_count(mov->moof->TrackList); k++) {
635 1 : GF_TrackFragmentBox *traf = gf_list_get(mov->moof->TrackList, k);
636 1 : if (traf->sample_encryption) {
637 0 : e = senc_Parse(mov->movieFileMap->bs, NULL, traf, traf->sample_encryption);
638 0 : if (e) return e;
639 : }
640 : }
641 :
642 : }
643 3938 : } else if (mov->openMode==GF_ISOM_OPEN_KEEP_FRAGMENTS) {
644 0 : mov->NextMoofNumber = mov->moof->mfhd->sequence_number+1;
645 0 : mov->moof = NULL;
646 0 : gf_isom_box_del(a);
647 : } else {
648 : /*merge all info*/
649 3938 : e = MergeFragment((GF_MovieFragmentBox *)a, mov);
650 3938 : gf_isom_box_del(a);
651 3938 : if (e) return e;
652 : }
653 :
654 : //done with moov
655 3957 : if (mov->root_sidx) {
656 87 : gf_isom_box_del((GF_Box *) mov->root_sidx);
657 87 : mov->root_sidx = NULL;
658 : }
659 3957 : if (mov->root_ssix) {
660 0 : gf_isom_box_del(mov->seg_ssix);
661 0 : mov->root_ssix = NULL;
662 : }
663 3957 : if (mov->seg_styp) {
664 84 : gf_isom_box_del(mov->seg_styp);
665 84 : mov->seg_styp = NULL;
666 : }
667 3957 : mov->sidx_start_offset = 0;
668 3957 : mov->sidx_end_offset = 0;
669 3957 : mov->styp_start_offset = 0;
670 3957 : break;
671 : #endif
672 7 : case GF_ISOM_BOX_TYPE_UNKNOWN:
673 : {
674 : GF_UnknownBox *box = (GF_UnknownBox*)a;
675 7 : if (box->original_4cc == GF_ISOM_BOX_TYPE_JP) {
676 3 : u8 *c = (u8 *) box->data;
677 3 : if ((box->dataSize==4) && (GF_4CC(c[0],c[1],c[2],c[3])==(u32)0x0D0A870A))
678 3 : mov->is_jp2 = 1;
679 3 : gf_isom_box_del(a);
680 : } else {
681 4 : e = gf_list_add(mov->TopBoxes, a);
682 4 : if (e) return e;
683 : }
684 : }
685 : break;
686 :
687 0 : case GF_ISOM_BOX_TYPE_PRFT:
688 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
689 0 : if (!(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG)) {
690 : //keep the last one read
691 0 : if (mov->last_producer_ref_time)
692 0 : gf_isom_box_del(a);
693 : else
694 0 : mov->last_producer_ref_time = (GF_ProducerReferenceTimeBox *)a;
695 : break;
696 : }
697 : #endif
698 : //fallthrough
699 :
700 : default:
701 : totSize += a->size;
702 1799 : e = gf_list_add(mov->TopBoxes, a);
703 1799 : if (e) return e;
704 : break;
705 : }
706 :
707 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
708 : /*remember where we left, in case we append an entire number of movie fragments*/
709 19647 : mov->current_top_box_start = gf_bs_get_position(mov->movieFileMap->bs) + mov->bytes_removed;
710 : #endif
711 : }
712 :
713 : /*we need at least moov or meta*/
714 4711 : if (!mov->moov && !mov->meta
715 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
716 4 : && !mov->moof && !mov->is_index_segment
717 : #endif
718 : ) {
719 : return GF_ISOM_INCOMPLETE_FILE;
720 : }
721 : /*we MUST have movie header*/
722 4708 : if (!gf_opts_get_bool("core", "no-check")) {
723 4708 : if (mov->moov && !mov->moov->mvhd) {
724 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing MVHD in MOOV!\n"));
725 : return GF_ISOM_INVALID_FILE;
726 : }
727 :
728 : /*we MUST have meta handler*/
729 4708 : if (mov->meta && !mov->meta->handler) {
730 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing handler in META!\n"));
731 : return GF_ISOM_INVALID_FILE;
732 : }
733 : }
734 :
735 : #ifndef GPAC_DISABLE_ISOM_WRITE
736 :
737 4708 : if (mov->moov) {
738 : /*set the default interleaving time*/
739 4655 : mov->interleavingTime = mov->moov->mvhd->timeScale;
740 :
741 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
742 : /*in edit mode with successfully loaded fragments, delete all fragment signaling since
743 : file is no longer fragmented*/
744 4655 : if ((mov->openMode > GF_ISOM_OPEN_READ) && (mov->openMode != GF_ISOM_OPEN_KEEP_FRAGMENTS) && mov->moov->mvex) {
745 2 : gf_isom_box_del_parent(&mov->moov->child_boxes, (GF_Box *)mov->moov->mvex);
746 2 : mov->moov->mvex = NULL;
747 : }
748 : #endif
749 :
750 : }
751 :
752 : //create a default mdat if none was found
753 4708 : if (!mov->mdat && (mov->openMode != GF_ISOM_OPEN_READ) && (mov->openMode != GF_ISOM_OPEN_KEEP_FRAGMENTS)) {
754 0 : mov->mdat = (GF_MediaDataBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MDAT);
755 0 : if (!mov->mdat) return GF_OUT_OF_MEM;
756 0 : e = gf_list_add(mov->TopBoxes, mov->mdat);
757 0 : if (e) return e;
758 : }
759 : #endif /*GPAC_DISABLE_ISOM_WRITE*/
760 :
761 : return GF_OK;
762 : }
763 :
764 9203 : GF_Err gf_isom_parse_movie_boxes(GF_ISOFile *mov, u32 *boxType, u64 *bytesMissing, Bool progressive_mode)
765 : {
766 : GF_Err e;
767 : GF_Blob *blob = NULL;
768 :
769 : //if associated file is a blob, lock blob before parsing !
770 9203 : if (mov->movieFileMap && ((mov->movieFileMap->type == GF_ISOM_DATA_MEM) || (mov->movieFileMap->type == GF_ISOM_DATA_FILE))) {
771 9203 : blob = ((GF_FileDataMap *)mov->movieFileMap)->blob;
772 : }
773 :
774 9203 : if (blob)
775 5048 : gf_mx_p(blob->mx);
776 :
777 9203 : e = gf_isom_parse_movie_boxes_internal(mov, boxType, bytesMissing, progressive_mode);
778 :
779 9203 : if (blob)
780 5048 : gf_mx_v(blob->mx);
781 9203 : return e;
782 :
783 : }
784 :
785 3054 : GF_ISOFile *gf_isom_new_movie()
786 : {
787 3054 : GF_ISOFile *mov = (GF_ISOFile*)gf_malloc(sizeof(GF_ISOFile));
788 3054 : if (mov == NULL) {
789 0 : gf_isom_set_last_error(NULL, GF_OUT_OF_MEM);
790 0 : return NULL;
791 : }
792 : memset(mov, 0, sizeof(GF_ISOFile));
793 :
794 : /*init the boxes*/
795 3054 : mov->TopBoxes = gf_list_new();
796 3054 : if (!mov->TopBoxes) {
797 0 : gf_isom_set_last_error(NULL, GF_OUT_OF_MEM);
798 0 : gf_free(mov);
799 0 : return NULL;
800 : }
801 :
802 : /*default storage mode is flat*/
803 3054 : mov->storageMode = GF_ISOM_STORE_FLAT;
804 3054 : mov->es_id_default_sync = -1;
805 3054 : return mov;
806 : }
807 :
808 : //Create and parse the movie for READ - EDIT only
809 1020 : GF_ISOFile *gf_isom_open_file(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir)
810 : {
811 : GF_Err e;
812 : u64 bytes;
813 1020 : GF_ISOFile *mov = gf_isom_new_movie();
814 1020 : if (!mov || !fileName) return NULL;
815 :
816 1020 : mov->fileName = gf_strdup(fileName);
817 1020 : mov->openMode = OpenMode;
818 :
819 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
820 1020 : if (OpenMode==GF_ISOM_OPEN_READ_DUMP)
821 307 : mov->store_traf_map = GF_TRUE;
822 : #endif
823 :
824 1020 : if ( (OpenMode == GF_ISOM_OPEN_READ) || (OpenMode == GF_ISOM_OPEN_READ_DUMP) || (OpenMode == GF_ISOM_OPEN_READ_EDIT) ) {
825 799 : if (OpenMode == GF_ISOM_OPEN_READ_EDIT) {
826 1 : mov->openMode = GF_ISOM_OPEN_READ_EDIT;
827 :
828 : // create a memory edit map in case we add samples, typically during import
829 1 : e = gf_isom_datamap_new(NULL, tmp_dir, GF_ISOM_DATA_MAP_WRITE, & mov->editFileMap);
830 1 : if (e) {
831 0 : gf_isom_set_last_error(NULL, e);
832 0 : gf_isom_delete_movie(mov);
833 0 : return NULL;
834 : }
835 : } else {
836 798 : mov->openMode = GF_ISOM_OPEN_READ;
837 : }
838 799 : mov->es_id_default_sync = -1;
839 : //for open, we do it the regular way and let the GF_DataMap assign the appropriate struct
840 : //this can be FILE (the only one supported...) as well as remote
841 : //(HTTP, ...),not suported yet
842 : //the bitstream IS PART OF the GF_DataMap
843 : //as this is read-only, use a FileMapping. this is the only place where
844 : //we use file mapping
845 799 : e = gf_isom_datamap_new(fileName, NULL, GF_ISOM_DATA_MAP_READ_ONLY, &mov->movieFileMap);
846 799 : if (e) {
847 2 : gf_isom_set_last_error(NULL, e);
848 2 : gf_isom_delete_movie(mov);
849 2 : return NULL;
850 : }
851 :
852 797 : if (OpenMode == GF_ISOM_OPEN_READ_DUMP) {
853 307 : mov->FragmentsFlags |= GF_ISOM_FRAG_READ_DEBUG;
854 : }
855 : } else {
856 :
857 : #ifdef GPAC_DISABLE_ISOM_WRITE
858 : //not allowed for READ_ONLY lib
859 : gf_isom_delete_movie(mov);
860 : gf_isom_set_last_error(NULL, GF_ISOM_INVALID_MODE);
861 : return NULL;
862 :
863 : #else
864 :
865 : //set a default output name for edited file
866 221 : mov->finalName = (char*)gf_malloc(strlen(fileName) + 5);
867 221 : if (!mov->finalName) {
868 0 : gf_isom_set_last_error(NULL, GF_OUT_OF_MEM);
869 0 : gf_isom_delete_movie(mov);
870 0 : return NULL;
871 : }
872 : strcpy(mov->finalName, "out_");
873 221 : strcat(mov->finalName, fileName);
874 :
875 : //open the original file with edit tag
876 221 : e = gf_isom_datamap_new(fileName, NULL, GF_ISOM_DATA_MAP_EDIT, &mov->movieFileMap);
877 : //if the file doesn't exist, we assume it's wanted and create one from scratch
878 221 : if (e) {
879 0 : gf_isom_set_last_error(NULL, e);
880 0 : gf_isom_delete_movie(mov);
881 0 : return NULL;
882 : }
883 : //and create a temp fileName for the edit
884 221 : e = gf_isom_datamap_new("_gpac_isobmff_tmp_edit", tmp_dir, GF_ISOM_DATA_MAP_WRITE, & mov->editFileMap);
885 221 : if (e) {
886 0 : gf_isom_set_last_error(NULL, e);
887 0 : gf_isom_delete_movie(mov);
888 0 : return NULL;
889 : }
890 :
891 221 : mov->es_id_default_sync = -1;
892 :
893 : #endif
894 : }
895 :
896 : //OK, let's parse the movie...
897 1018 : mov->LastError = gf_isom_parse_movie_boxes(mov, NULL, &bytes, 0);
898 :
899 : #if 0
900 : if (!mov->LastError && (OpenMode == GF_ISOM_OPEN_CAT_FRAGMENTS)) {
901 : gf_isom_datamap_del(mov->movieFileMap);
902 : /*reopen the movie file map in cat mode*/
903 : mov->LastError = gf_isom_datamap_new(fileName, tmp_dir, GF_ISOM_DATA_MAP_CAT, & mov->movieFileMap);
904 : }
905 : #endif
906 :
907 1018 : if (mov->LastError) {
908 3 : gf_isom_set_last_error(NULL, mov->LastError);
909 3 : gf_isom_delete_movie(mov);
910 3 : return NULL;
911 : }
912 :
913 1015 : mov->nb_box_init_seg = gf_list_count(mov->TopBoxes);
914 1015 : return mov;
915 : }
916 :
917 724 : GF_Err gf_isom_set_write_callback(GF_ISOFile *mov,
918 : GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size),
919 : GF_Err (*on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert),
920 : void *usr_data,
921 : u32 block_size)
922 : {
923 : #ifndef GPAC_DISABLE_ISOM_WRITE
924 724 : if (mov->finalName && !strcmp(mov->finalName, "_gpac_isobmff_redirect")) {}
925 341 : else if (mov->fileName && !strcmp(mov->fileName, "_gpac_isobmff_redirect")) {}
926 : else return GF_BAD_PARAM;
927 724 : mov->on_block_out = on_block_out;
928 724 : mov->on_block_patch = on_block_patch;
929 724 : mov->on_block_out_usr_data = usr_data;
930 724 : mov->on_block_out_block_size = block_size;
931 724 : return GF_OK;
932 : #else
933 : return GF_NOT_SUPPORTED;
934 : #endif
935 : }
936 :
937 :
938 586345 : u64 gf_isom_get_mp4time()
939 : {
940 : u32 calctime, msec;
941 : u64 ret;
942 586346 : gf_utc_time_since_1970(&calctime, &msec);
943 586346 : calctime += GF_ISOM_MAC_TIME_OFFSET;
944 586346 : ret = calctime;
945 586345 : return ret;
946 : }
947 :
948 3080 : void gf_isom_delete_movie(GF_ISOFile *mov)
949 : {
950 3080 : if (!mov) return;
951 :
952 : //these are our two main files
953 3054 : if (mov->movieFileMap) gf_isom_datamap_del(mov->movieFileMap);
954 :
955 : #ifndef GPAC_DISABLE_ISOM_WRITE
956 3054 : if (mov->editFileMap) {
957 1502 : gf_isom_datamap_del(mov->editFileMap);
958 : }
959 3054 : if (mov->finalName) gf_free(mov->finalName);
960 : #endif
961 :
962 3054 : gf_isom_box_array_del(mov->TopBoxes);
963 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
964 3054 : gf_isom_box_array_del(mov->moof_list);
965 3054 : if (mov->mfra)
966 1 : gf_isom_box_del((GF_Box*)mov->mfra);
967 3054 : if (mov->sidx_pts_store)
968 266 : gf_free(mov->sidx_pts_store);
969 3054 : if (mov->sidx_pts_next_store)
970 266 : gf_free(mov->sidx_pts_next_store);
971 :
972 3054 : if (mov->main_sidx)
973 126 : gf_isom_box_del((GF_Box*)mov->main_sidx);
974 :
975 3054 : if (mov->block_buffer)
976 339 : gf_free(mov->block_buffer);
977 : #endif
978 3054 : if (mov->last_producer_ref_time)
979 0 : gf_isom_box_del((GF_Box *) mov->last_producer_ref_time);
980 3054 : if (mov->fileName) gf_free(mov->fileName);
981 3054 : gf_free(mov);
982 : }
983 :
984 7842 : GF_TrackBox *gf_isom_get_track_from_id(GF_MovieBox *moov, GF_ISOTrackID trackID)
985 : {
986 : u32 i, count;
987 7842 : if (!moov || !trackID) return NULL;
988 :
989 7841 : count = gf_list_count(moov->trackList);
990 16150 : for (i = 0; i<count; i++) {
991 16139 : GF_TrackBox *trak = (GF_TrackBox*)gf_list_get(moov->trackList, i);
992 16139 : if (trak->Header->trackID == trackID) return trak;
993 : }
994 : return NULL;
995 : }
996 :
997 11 : GF_TrackBox *gf_isom_get_track_from_original_id(GF_MovieBox *moov, u32 originalID, u32 originalFile)
998 : {
999 : u32 i, count;
1000 11 : if (!moov || !originalID) return NULL;
1001 :
1002 11 : count = gf_list_count(moov->trackList);
1003 34 : for (i = 0; i<count; i++) {
1004 33 : GF_TrackBox *trak = (GF_TrackBox*)gf_list_get(moov->trackList, i);
1005 33 : if ((trak->originalFile == originalFile) && (trak->originalID == originalID)) return trak;
1006 : }
1007 : return NULL;
1008 : }
1009 :
1010 6110945 : GF_TrackBox *gf_isom_get_track_from_file(GF_ISOFile *movie, u32 trackNumber)
1011 : {
1012 : GF_TrackBox *trak;
1013 6155060 : if (!movie) return NULL;
1014 6155055 : trak = gf_isom_get_track(movie->moov, trackNumber);
1015 6155055 : if (!trak) movie->LastError = GF_BAD_PARAM;
1016 : return trak;
1017 : }
1018 :
1019 :
1020 : //WARNING: MOVIETIME IS EXPRESSED IN MEDIA TS
1021 127 : GF_Err GetMediaTime(GF_TrackBox *trak, Bool force_non_empty, u64 movieTime, u64 *MediaTime, s64 *SegmentStartTime, s64 *MediaOffset, u8 *useEdit, u64 *next_edit_start_plus_one)
1022 : {
1023 : #if 0
1024 : GF_Err e;
1025 : u32 sampleNumber, prevSampleNumber;
1026 : u64 firstDTS;
1027 : #endif
1028 : u32 i, count;
1029 : Bool last_is_empty = 0;
1030 : u64 time, lastSampleTime;
1031 : s64 mtime;
1032 : GF_EdtsEntry *ent;
1033 : Double scale_ts;
1034 127 : GF_SampleTableBox *stbl = trak->Media->information->sampleTable;
1035 :
1036 127 : if (next_edit_start_plus_one) *next_edit_start_plus_one = 0;
1037 127 : *useEdit = 1;
1038 127 : *MediaTime = 0;
1039 : //no segment yet...
1040 127 : *SegmentStartTime = -1;
1041 127 : *MediaOffset = -1;
1042 127 : if (!trak->moov->mvhd->timeScale || !trak->Media->mediaHeader->timeScale || !stbl->SampleSize) {
1043 : return GF_ISOM_INVALID_FILE;
1044 : }
1045 :
1046 : //no samples...
1047 127 : if (!stbl->SampleSize->sampleCount) {
1048 : lastSampleTime = 0;
1049 : } else {
1050 114 : lastSampleTime = trak->Media->mediaHeader->duration;
1051 : }
1052 :
1053 : //No edits, 1 to 1 mapping
1054 127 : if (! trak->editBox || !trak->editBox->editList) {
1055 96 : *MediaTime = movieTime;
1056 : //check this is in our media time line
1057 96 : if ((*MediaTime > lastSampleTime)
1058 : #ifndef GPAC_DISABLE_ISOM_FRAGMENTS
1059 42 : && !trak->moov->mov->moof
1060 : #endif
1061 : ) {
1062 23 : *MediaTime = lastSampleTime;
1063 : }
1064 96 : *useEdit = 0;
1065 96 : return GF_OK;
1066 : }
1067 : //browse the edit list and get the time
1068 31 : scale_ts = trak->Media->mediaHeader->timeScale;
1069 31 : scale_ts /= trak->moov->mvhd->timeScale;
1070 :
1071 : time = 0;
1072 : ent = NULL;
1073 31 : count=gf_list_count(trak->editBox->editList->entryList);
1074 32 : for (i=0; i<count; i++) {
1075 31 : ent = (GF_EdtsEntry *)gf_list_get(trak->editBox->editList->entryList, i);
1076 31 : if ( (time + ent->segmentDuration) * scale_ts > movieTime) {
1077 30 : if (!force_non_empty || (ent->mediaTime >= 0)) {
1078 30 : if (next_edit_start_plus_one) *next_edit_start_plus_one = 1 + (u64) ((time + ent->segmentDuration) * scale_ts);
1079 : goto ent_found;
1080 : }
1081 : }
1082 : time += ent->segmentDuration;
1083 1 : last_is_empty = ent->segmentDuration ? 0 : 1;
1084 : }
1085 :
1086 1 : if (last_is_empty) {
1087 0 : ent = (GF_EdtsEntry *)gf_list_last(trak->editBox->editList->entryList);
1088 0 : if (ent->mediaRate == 0x10000) {
1089 0 : *MediaTime = movieTime + ent->mediaTime;
1090 : } else {
1091 0 : ent = (GF_EdtsEntry *)gf_list_get(trak->editBox->editList->entryList, 0);
1092 0 : if (ent->mediaRate == -0x10000) {
1093 0 : u64 dur = (u64) (ent->segmentDuration * scale_ts);
1094 0 : *MediaTime = (movieTime > dur) ? (movieTime-dur) : 0;
1095 : }
1096 : }
1097 0 : *useEdit = 0;
1098 0 : return GF_OK;
1099 : }
1100 :
1101 :
1102 : //we had nothing in the list (strange file but compliant...)
1103 : //return the 1 to 1 mapped vale of the last media sample
1104 1 : if (!ent) {
1105 0 : *MediaTime = movieTime;
1106 : //check this is in our media time line
1107 0 : if (*MediaTime > lastSampleTime) *MediaTime = lastSampleTime;
1108 0 : *useEdit = 0;
1109 0 : return GF_OK;
1110 : }
1111 : //request for a bigger time that what we can give: return the last sample (undefined behavior...)
1112 1 : *MediaTime = lastSampleTime;
1113 1 : return GF_OK;
1114 :
1115 30 : ent_found:
1116 : //OK, we found our entry, set the SegmentTime
1117 30 : *SegmentStartTime = time;
1118 :
1119 : //we request an empty list, there's no media here...
1120 30 : if (ent->mediaTime < 0) {
1121 0 : *MediaTime = 0;
1122 0 : return GF_OK;
1123 : }
1124 : //we request a dwell edit
1125 30 : if (! ent->mediaRate) {
1126 0 : *MediaTime = ent->mediaTime;
1127 : //no media offset
1128 0 : *MediaOffset = 0;
1129 0 : *useEdit = 2;
1130 0 : return GF_OK;
1131 : }
1132 :
1133 : /*WARNING: this can be "-1" when doing searchForward mode (to prevent jumping to next entry)*/
1134 30 : mtime = ent->mediaTime + movieTime - (time * trak->Media->mediaHeader->timeScale / trak->moov->mvhd->timeScale);
1135 30 : if (mtime<0) mtime = 0;
1136 30 : *MediaTime = (u64) mtime;
1137 30 : *MediaOffset = ent->mediaTime;
1138 :
1139 : #if 0
1140 : //
1141 : //Sanity check: is the requested time valid ? This is to cope with wrong EditLists
1142 : //we have the translated time, but we need to make sure we have a sample at this time ...
1143 : //we have to find a COMPOSITION time
1144 : e = stbl_findEntryForTime(stbl, (u32) *MediaTime, 1, &sampleNumber, &prevSampleNumber);
1145 : if (e) return e;
1146 :
1147 : //first case: our time is after the last sample DTS (it's a broken editList somehow)
1148 : //set the media time to the last sample
1149 : if (!sampleNumber && !prevSampleNumber) {
1150 : *MediaTime = lastSampleTime;
1151 : return GF_OK;
1152 : }
1153 : //get the appropriated sample
1154 : if (!sampleNumber) sampleNumber = prevSampleNumber;
1155 :
1156 : stbl_GetSampleDTS(stbl->TimeToSample, sampleNumber, &DTS);
1157 : CTS = 0;
1158 : if (stbl->CompositionOffset) stbl_GetSampleCTS(stbl->CompositionOffset, sampleNumber, &CTS);
1159 :
1160 : //now get the entry sample (the entry time gives the CTS, and we need the DTS
1161 : e = stbl_findEntryForTime(stbl, (u32) ent->mediaTime, 0, &sampleNumber, &prevSampleNumber);
1162 : if (e) return e;
1163 :
1164 : //oops, the mediaTime indicates a sample that is not in our media !
1165 : if (!sampleNumber && !prevSampleNumber) {
1166 : *MediaTime = lastSampleTime;
1167 : return GF_ISOM_INVALID_FILE;
1168 : }
1169 : if (!sampleNumber) sampleNumber = prevSampleNumber;
1170 :
1171 : stbl_GetSampleDTS(stbl->TimeToSample, sampleNumber, &firstDTS);
1172 :
1173 : //and store the "time offset" of the desired sample in this segment
1174 : //this is weird, used to rebuild the timeStamp when reading from the track, not the
1175 : //media ...
1176 : *MediaOffset = firstDTS;
1177 : #endif
1178 30 : return GF_OK;
1179 : }
1180 :
1181 1 : GF_Err GetNextMediaTime(GF_TrackBox *trak, u64 movieTime, u64 *OutMovieTime)
1182 : {
1183 : u32 i;
1184 : u64 time;
1185 : GF_EdtsEntry *ent;
1186 :
1187 1 : *OutMovieTime = 0;
1188 1 : if (! trak->editBox || !trak->editBox->editList) return GF_BAD_PARAM;
1189 :
1190 : time = 0;
1191 : ent = NULL;
1192 1 : i=0;
1193 3 : while ((ent = (GF_EdtsEntry *)gf_list_enum(trak->editBox->editList->entryList, &i))) {
1194 1 : if (time * trak->Media->mediaHeader->timeScale >= movieTime * trak->moov->mvhd->timeScale) {
1195 : /*skip empty edits*/
1196 0 : if (ent->mediaTime >= 0) {
1197 0 : *OutMovieTime = time * trak->Media->mediaHeader->timeScale / trak->moov->mvhd->timeScale;
1198 0 : if (*OutMovieTime>0) *OutMovieTime -= 1;
1199 : return GF_OK;
1200 : }
1201 : }
1202 1 : time += ent->segmentDuration;
1203 : }
1204 : //request for a bigger time that what we can give: return the last sample (undefined behavior...)
1205 1 : *OutMovieTime = trak->moov->mvhd->duration;
1206 1 : return GF_EOS;
1207 : }
1208 :
1209 0 : GF_Err GetPrevMediaTime(GF_TrackBox *trak, u64 movieTime, u64 *OutMovieTime)
1210 : {
1211 : u32 i;
1212 : u64 time;
1213 : GF_EdtsEntry *ent;
1214 :
1215 0 : *OutMovieTime = 0;
1216 0 : if (! trak->editBox || !trak->editBox->editList) return GF_BAD_PARAM;
1217 :
1218 : time = 0;
1219 : ent = NULL;
1220 0 : i=0;
1221 0 : while ((ent = (GF_EdtsEntry *)gf_list_enum(trak->editBox->editList->entryList, &i))) {
1222 0 : if (ent->mediaTime == -1) {
1223 0 : if ( (time + ent->segmentDuration) * trak->Media->mediaHeader->timeScale >= movieTime * trak->moov->mvhd->timeScale) {
1224 0 : *OutMovieTime = time * trak->Media->mediaHeader->timeScale / trak->moov->mvhd->timeScale;
1225 0 : return GF_OK;
1226 : }
1227 0 : continue;
1228 : }
1229 : /*get the first entry whose end is greater than or equal to the desired time*/
1230 0 : time += ent->segmentDuration;
1231 0 : if ( time * trak->Media->mediaHeader->timeScale >= movieTime * trak->moov->mvhd->timeScale) {
1232 0 : *OutMovieTime = time * trak->Media->mediaHeader->timeScale / trak->moov->mvhd->timeScale;
1233 0 : return GF_OK;
1234 : }
1235 : }
1236 0 : *OutMovieTime = 0;
1237 0 : return GF_OK;
1238 : }
1239 :
1240 : #ifndef GPAC_DISABLE_ISOM_WRITE
1241 :
1242 4978 : GF_Err gf_isom_insert_moov(GF_ISOFile *file)
1243 : {
1244 : GF_MovieHeaderBox *mvhd;
1245 4978 : if (file->moov) return GF_OK;
1246 :
1247 : //OK, create our boxes (mvhd, iods, ...)
1248 1286 : file->moov = (GF_MovieBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MOOV);
1249 1286 : if (!file->moov) return GF_OUT_OF_MEM;
1250 1286 : file->moov->mov = file;
1251 : //Header SetUp
1252 1286 : mvhd = (GF_MovieHeaderBox *) gf_isom_box_new_parent(&file->moov->child_boxes, GF_ISOM_BOX_TYPE_MVHD);
1253 1286 : if (!mvhd) return GF_OUT_OF_MEM;
1254 :
1255 1286 : if (gf_sys_is_test_mode() ) {
1256 1285 : mvhd->creationTime = mvhd->modificationTime = 0;
1257 : } else {
1258 : u64 now = gf_isom_get_mp4time();
1259 1 : mvhd->creationTime = now;
1260 1 : if (!file->keep_utc)
1261 1 : mvhd->modificationTime = now;
1262 : }
1263 :
1264 1286 : mvhd->nextTrackID = 1;
1265 : //600 is our default movie TimeScale
1266 1286 : mvhd->timeScale = 600;
1267 :
1268 1286 : file->interleavingTime = mvhd->timeScale;
1269 1286 : moov_on_child_box((GF_Box*)file->moov, (GF_Box *)mvhd, GF_FALSE);
1270 1286 : gf_list_add(file->TopBoxes, file->moov);
1271 1286 : return GF_OK;
1272 : }
1273 :
1274 : //Create the movie for WRITE only
1275 1297 : GF_ISOFile *gf_isom_create_movie(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir)
1276 : {
1277 : GF_Err e;
1278 :
1279 1297 : GF_ISOFile *mov = gf_isom_new_movie();
1280 1297 : if (!mov) return NULL;
1281 1297 : mov->openMode = OpenMode;
1282 : //then set up our movie
1283 :
1284 : //in WRITE, the input dataMap is ALWAYS NULL
1285 1297 : mov->movieFileMap = NULL;
1286 :
1287 : //but we have the edit one
1288 1297 : if (OpenMode == GF_ISOM_OPEN_WRITE) {
1289 : const char *ext;
1290 : //THIS IS NOT A TEMP FILE, WRITE mode is used for "live capture"
1291 : //this file will be the final file...
1292 349 : mov->fileName = fileName ? gf_strdup(fileName) : NULL;
1293 349 : e = gf_isom_datamap_new(fileName, NULL, GF_ISOM_DATA_MAP_WRITE, &mov->editFileMap);
1294 349 : if (e) goto err_exit;
1295 :
1296 : /*brand is set to ISOM or QT by default - it may be touched until sample data is added to track*/
1297 349 : ext = gf_file_ext_start(fileName);
1298 349 : if (ext && (!strnicmp(ext, ".mov", 4) || !strnicmp(ext, ".qt", 3))) {
1299 0 : gf_isom_set_brand_info((GF_ISOFile *) mov, GF_ISOM_BRAND_QT, 512);
1300 : } else {
1301 349 : gf_isom_set_brand_info((GF_ISOFile *) mov, GF_ISOM_BRAND_ISOM, 1);
1302 : }
1303 : } else {
1304 : //we are in EDIT mode but we are creating the file -> temp file
1305 948 : mov->finalName = fileName ? gf_strdup(fileName) : NULL;
1306 948 : e = gf_isom_datamap_new("_gpac_isobmff_tmp_edit", tmp_dir, GF_ISOM_DATA_MAP_WRITE, &mov->editFileMap);
1307 948 : if (e) {
1308 0 : gf_isom_set_last_error(NULL, e);
1309 0 : gf_isom_delete_movie(mov);
1310 0 : return NULL;
1311 : }
1312 : //brand is set to ISOM by default
1313 948 : gf_isom_set_brand_info( (GF_ISOFile *) mov, GF_ISOM_BRAND_ISOM, 1);
1314 : }
1315 :
1316 : //create an MDAT
1317 1297 : mov->mdat = (GF_MediaDataBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MDAT);
1318 1297 : if (!mov->mdat) {
1319 0 : gf_isom_set_last_error(NULL, GF_OUT_OF_MEM);
1320 0 : gf_isom_delete_movie(mov);
1321 0 : return NULL;
1322 : }
1323 1297 : gf_list_add(mov->TopBoxes, mov->mdat);
1324 :
1325 : //default behavior is capture mode, no interleaving (eg, no rewrite of mdat)
1326 1297 : mov->storageMode = GF_ISOM_STORE_FLAT;
1327 1297 : return mov;
1328 :
1329 0 : err_exit:
1330 0 : gf_isom_set_last_error(NULL, e);
1331 0 : if (mov) gf_isom_delete_movie(mov);
1332 0 : return NULL;
1333 : }
1334 :
1335 555 : GF_EdtsEntry *CreateEditEntry(u64 EditDuration, u64 MediaTime, u8 EditMode)
1336 : {
1337 : GF_EdtsEntry *ent;
1338 :
1339 555 : ent = (GF_EdtsEntry*)gf_malloc(sizeof(GF_EdtsEntry));
1340 555 : if (!ent) return NULL;
1341 :
1342 555 : switch (EditMode) {
1343 82 : case GF_ISOM_EDIT_EMPTY:
1344 82 : ent->mediaRate = 0x10000;
1345 82 : ent->mediaTime = -1;
1346 82 : break;
1347 :
1348 0 : case GF_ISOM_EDIT_DWELL:
1349 0 : ent->mediaRate = 0;
1350 0 : ent->mediaTime = MediaTime;
1351 0 : break;
1352 473 : default:
1353 473 : ent->mediaRate = 0x10000;
1354 473 : ent->mediaTime = MediaTime;
1355 473 : break;
1356 : }
1357 555 : ent->segmentDuration = EditDuration;
1358 555 : return ent;
1359 : }
1360 :
1361 3066 : GF_Err gf_isom_add_subsample_info(GF_SubSampleInformationBox *sub_samples, u32 sampleNumber, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable)
1362 : {
1363 : u32 i, count, last_sample;
1364 : GF_SubSampleInfoEntry *pSamp;
1365 : GF_SubSampleEntry *pSubSamp;
1366 :
1367 : pSamp = NULL;
1368 : last_sample = 0;
1369 3066 : count = gf_list_count(sub_samples->Samples);
1370 623672 : for (i=0; i<count; i++) {
1371 622140 : pSamp = (GF_SubSampleInfoEntry*) gf_list_get(sub_samples->Samples, i);
1372 : /*TODO - do we need to support insertion of subsample info ?*/
1373 622140 : if (last_sample + pSamp->sample_delta > sampleNumber) return GF_NOT_SUPPORTED;
1374 622140 : if (last_sample + pSamp->sample_delta == sampleNumber) break;
1375 : last_sample += pSamp->sample_delta;
1376 : pSamp = NULL;
1377 : }
1378 :
1379 3066 : if (!pSamp) {
1380 1532 : GF_SAFEALLOC(pSamp, GF_SubSampleInfoEntry);
1381 1532 : if (!pSamp) return GF_OUT_OF_MEM;
1382 1532 : pSamp->SubSamples = gf_list_new();
1383 1532 : if (!pSamp->SubSamples ) {
1384 0 : gf_free(pSamp);
1385 0 : return GF_OUT_OF_MEM;
1386 : }
1387 1532 : pSamp->sample_delta = sampleNumber - last_sample;
1388 1532 : gf_list_add(sub_samples->Samples, pSamp);
1389 : }
1390 :
1391 3066 : if ((subSampleSize>0xFFFF) && !sub_samples->version) {
1392 2 : sub_samples->version = 1;
1393 : }
1394 : /*remove last subsample info*/
1395 3066 : if (!subSampleSize) {
1396 0 : pSubSamp = gf_list_last(pSamp->SubSamples);
1397 0 : gf_list_rem_last(pSamp->SubSamples);
1398 0 : gf_free(pSubSamp);
1399 0 : if (!gf_list_count(pSamp->SubSamples)) {
1400 0 : gf_list_del_item(sub_samples->Samples, pSamp);
1401 0 : gf_list_del(pSamp->SubSamples);
1402 0 : gf_free(pSamp);
1403 : }
1404 : return GF_OK;
1405 : }
1406 : /*add subsample*/
1407 3066 : GF_SAFEALLOC(pSubSamp, GF_SubSampleEntry);
1408 3066 : if (!pSubSamp) return GF_OUT_OF_MEM;
1409 3066 : pSubSamp->subsample_size = subSampleSize;
1410 3066 : pSubSamp->subsample_priority = priority;
1411 3066 : pSubSamp->reserved = reserved;
1412 3066 : pSubSamp->discardable = discardable;
1413 3066 : return gf_list_add(pSamp->SubSamples, pSubSamp);
1414 : }
1415 :
1416 : #endif /*GPAC_DISABLE_ISOM_WRITE*/
1417 :
1418 : #if 0 //unused
1419 : u32 gf_isom_sample_get_subsamples_count(GF_ISOFile *movie, u32 track)
1420 : {
1421 : GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track);
1422 : if (!track) return 0;
1423 : if (!trak->Media || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->sub_samples) return 0;
1424 : return gf_list_count(trak->Media->information->sampleTable->sub_samples);
1425 : }
1426 : #endif
1427 :
1428 44114 : Bool gf_isom_get_subsample_types(GF_ISOFile *movie, u32 track, u32 subs_index, u32 *flags)
1429 : {
1430 : GF_SubSampleInformationBox *sub_samples=NULL;
1431 : GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track);
1432 :
1433 44114 : if (!track || !subs_index) return GF_FALSE;
1434 44114 : if (!trak->Media || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->sub_samples) return GF_FALSE;
1435 0 : sub_samples = gf_list_get(trak->Media->information->sampleTable->sub_samples, subs_index-1);
1436 0 : if (!sub_samples) return GF_FALSE;
1437 0 : *flags = sub_samples->flags;
1438 0 : return GF_TRUE;
1439 : }
1440 :
1441 1 : u32 gf_isom_sample_get_subsample_entry(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 flags, GF_SubSampleInfoEntry **sub_sample)
1442 : {
1443 : u32 i, count, last_sample;
1444 : GF_SubSampleInformationBox *sub_samples=NULL;
1445 : GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track);
1446 1 : if (sub_sample) *sub_sample = NULL;
1447 1 : if (!track) return 0;
1448 1 : if (!trak->Media || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->sub_samples) return 0;
1449 0 : count = gf_list_count(trak->Media->information->sampleTable->sub_samples);
1450 0 : for (i=0; i<count; i++) {
1451 0 : sub_samples = gf_list_get(trak->Media->information->sampleTable->sub_samples, i);
1452 0 : if (sub_samples->flags==flags) break;
1453 : sub_samples = NULL;
1454 : }
1455 0 : if (!sub_samples) return 0;
1456 :
1457 : last_sample = 0;
1458 0 : count = gf_list_count(sub_samples->Samples);
1459 0 : for (i=0; i<count; i++) {
1460 0 : GF_SubSampleInfoEntry *pSamp = (GF_SubSampleInfoEntry *) gf_list_get(sub_samples->Samples, i);
1461 0 : if (last_sample + pSamp->sample_delta == sampleNumber) {
1462 0 : if (sub_sample) *sub_sample = pSamp;
1463 0 : return gf_list_count(pSamp->SubSamples);
1464 : }
1465 : last_sample += pSamp->sample_delta;
1466 : }
1467 : return 0;
1468 : }
1469 : #endif /*GPAC_DISABLE_ISOM*/
|