blob: 1c31b90b5e57a9bde6d71aa49e035a43de6ebefa [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2023
* All rights reserved
*
* This file is part of GPAC / ISO Media File Format sub-project
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/internal/isomedia_dev.h>
#ifndef GPAC_DISABLE_ISOM
//Get the sample number
GF_Err stbl_findEntryForTime(GF_SampleTableBox *stbl, u64 DTS, u8 useCTS, u32 *sampleNumber, u32 *prevSampleNumber)
{
u32 i, j, curSampNum, count;
s32 CTSOffset;
u64 curDTS;
GF_SttsEntry *ent;
(*sampleNumber) = 0;
(*prevSampleNumber) = 0;
if (!stbl->TimeToSample) return GF_ISOM_INVALID_FILE;
/*CTS is ALWAYS disabled for now to make sure samples are fetched in decoding order. useCTS is therefore disabled*/
#if 0
if (!stbl->CompositionOffset) useCTS = 0;
#endif
//our cache
if (stbl->TimeToSample->r_FirstSampleInEntry &&
(DTS >= stbl->TimeToSample->r_CurrentDTS) ) {
//if we're using CTS, we don't really know whether we're in the good entry or not
//(eg, the real DTS of the sample could be in a previous entry
i = stbl->TimeToSample->r_currentEntryIndex;
curDTS = stbl->TimeToSample->r_CurrentDTS;
curSampNum = stbl->TimeToSample->r_FirstSampleInEntry;
} else {
i = 0;
curDTS = stbl->TimeToSample->r_CurrentDTS = 0;
curSampNum = stbl->TimeToSample->r_FirstSampleInEntry = 1;
stbl->TimeToSample->r_currentEntryIndex = 0;
}
#if 0
//we need to validate our cache if we are using CTS because of B-frames and co...
if (i && useCTS) {
while (1) {
stbl_GetSampleCTS(stbl->CompositionOffset, curSampNum, &CTSOffset);
//we're too far, rewind
if ( i && (curDTS + CTSOffset > DTS) ) {
ent = &stbl->TimeToSample->entries[i];
curSampNum -= ent->sampleCount;
curDTS -= (u64)ent->sampleDelta * ent->sampleCount;
i --;
} else if (!i) {
//beginning of the table, no choice
curDTS = stbl->TimeToSample->r_CurrentDTS = 0;
curSampNum = stbl->TimeToSample->r_FirstSampleInEntry = 1;
stbl->TimeToSample->r_currentEntryIndex = 0;
break;
} else {
//OK now we're good
break;
}
}
}
#endif
//look for the DTS from this entry
count = stbl->TimeToSample->nb_entries;
for (; i<count; i++) {
ent = &stbl->TimeToSample->entries[i];
#if 0
if (useCTS) {
stbl_GetSampleCTS(stbl->CompositionOffset, curSampNum, &CTSOffset);
} else
#endif
{
CTSOffset = 0;
}
for (j=0; j<ent->sampleCount; j++) {
if (curDTS + CTSOffset >= DTS) goto entry_found;
curSampNum += 1;
curDTS += ent->sampleDelta;
}
//we're switching to the next entry, update the cache!
stbl->TimeToSample->r_CurrentDTS += (u64)ent->sampleCount * ent->sampleDelta;
stbl->TimeToSample->r_currentEntryIndex += 1;
stbl->TimeToSample->r_FirstSampleInEntry += ent->sampleCount;
}
//return as is
return GF_OK;
entry_found:
//do we have the exact time ?
if (curDTS + CTSOffset == DTS) {
(*sampleNumber) = curSampNum;
}
//if we match the exact DTS also select this sample
else if (curDTS == DTS) {
(*sampleNumber) = curSampNum;
} else {
//exception for the first sample (we need to "load" the playback)
if (curSampNum != 1) {
(*prevSampleNumber) = curSampNum - 1;
} else {
(*prevSampleNumber) = 1;
}
}
return GF_OK;
}
//Get the Size of a given sample
GF_Err stbl_GetSampleSize(GF_SampleSizeBox *stsz, u32 SampleNumber, u32 *Size)
{
if (!stsz || !SampleNumber || (SampleNumber > stsz->sampleCount))
return GF_BAD_PARAM;
if (stsz->sampleSize && (stsz->type != GF_ISOM_BOX_TYPE_STZ2)) {
(*Size) = stsz->sampleSize;
} else if (stsz->sizes) {
(*Size) = stsz->sizes[SampleNumber - 1];
} else {
(*Size) = 0;
}
return GF_OK;
}
//Get the CTS offset of a given sample
GF_Err stbl_GetSampleCTS(GF_CompositionOffsetBox *ctts, u32 SampleNumber, s32 *CTSoffset)
{
u32 i;
(*CTSoffset) = 0;
//test on SampleNumber is done before
if (!ctts || !SampleNumber) return GF_BAD_PARAM;
if (ctts->r_FirstSampleInEntry && (ctts->r_FirstSampleInEntry < SampleNumber) ) {
i = ctts->r_currentEntryIndex;
} else {
ctts->r_FirstSampleInEntry = 1;
ctts->r_currentEntryIndex = 0;
i = 0;
}
for (; i< ctts->nb_entries; i++) {
if (SampleNumber < ctts->r_FirstSampleInEntry + ctts->entries[i].sampleCount) break;
//update our cache
ctts->r_currentEntryIndex += 1;
ctts->r_FirstSampleInEntry += ctts->entries[i].sampleCount;
}
//no ent, set everything to 0...
if (i==ctts->nb_entries) return GF_OK;
/*asked for a sample not in table - this means CTTS is 0 (that's due to out internal packing construction of CTTS)*/
if (SampleNumber >= ctts->r_FirstSampleInEntry + ctts->entries[i].sampleCount) return GF_OK;
(*CTSoffset) = ctts->entries[i].decodingOffset;
return GF_OK;
}
//Get the DTS of a sample
GF_Err stbl_GetSampleDTS_and_Duration(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS, u32 *duration)
{
u32 i, j, count;
GF_SttsEntry *ent;
(*DTS) = 0;
if (duration) {
*duration = 0;
}
if (!stts || !SampleNumber) return GF_BAD_PARAM;
ent = NULL;
//use our cache
count = stts->nb_entries;
if (stts->r_FirstSampleInEntry
&& (stts->r_FirstSampleInEntry <= SampleNumber)
//this is for read/write access
&& (stts->r_currentEntryIndex < count) ) {
i = stts->r_currentEntryIndex;
} else {
i = stts->r_currentEntryIndex = 0;
stts->r_FirstSampleInEntry = 1;
stts->r_CurrentDTS = 0;
}
for (; i < count; i++) {
ent = &stts->entries[i];
//in our entry
if (ent->sampleCount + stts->r_FirstSampleInEntry >= 1 + SampleNumber) {
j = SampleNumber - stts->r_FirstSampleInEntry;
goto found;
}
//update our cache
stts->r_CurrentDTS += (u64)ent->sampleCount * ent->sampleDelta;
stts->r_currentEntryIndex += 1;
stts->r_FirstSampleInEntry += ent->sampleCount;
}
// if (SampleNumber >= stts->r_FirstSampleInEntry + ent->sampleCount) return GF_BAD_PARAM;
//no ent, this is really weird. Let's assume the DTS is then what is written in the table
if (!ent || (i == count)) {
if (SampleNumber>stts->r_FirstSampleInEntry)
return GF_ISOM_INVALID_FILE;
(*DTS) = stts->r_CurrentDTS;
if (duration) *duration = ent ? ent->sampleDelta : 0;
}
return GF_OK;
found:
(*DTS) = stts->r_CurrentDTS + j * (u64) ent->sampleDelta + stts->cumulated_start_dts;
if (duration) *duration = ent->sampleDelta;
return GF_OK;
}
GF_Err stbl_GetSampleDTS(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS)
{
return stbl_GetSampleDTS_and_Duration(stts, SampleNumber, DTS, NULL);
}
//Retrieve closes RAP for a given sample - if sample is RAP, sets the RAP flag
GF_Err stbl_GetSampleRAP(GF_SyncSampleBox *stss, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP)
{
u32 i;
if (prevRAP) *prevRAP = 0;
if (nextRAP) *nextRAP = 0;
(*IsRAP) = RAP_NO;
if (!stss || !SampleNumber) return GF_BAD_PARAM;
if (stss->r_LastSyncSample && (stss->r_LastSyncSample < SampleNumber) ) {
i = stss->r_LastSampleIndex;
} else {
i = 0;
}
for (; i < stss->nb_entries; i++) {
//get the entry
if (stss->sampleNumbers[i] == SampleNumber) {
//update the cache
stss->r_LastSyncSample = SampleNumber;
stss->r_LastSampleIndex = i;
(*IsRAP) = RAP;
if (!nextRAP) return GF_OK;
}
else if (stss->sampleNumbers[i] > SampleNumber) {
if (nextRAP) *nextRAP = stss->sampleNumbers[i];
return GF_OK;
}
if (prevRAP) *prevRAP = stss->sampleNumbers[i];
}
return GF_OK;
}
GF_Err stbl_SearchSAPs(GF_SampleTableBox *stbl, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP)
{
u32 i, j, count, count2;
gf_assert(prevRAP);
gf_assert(nextRAP);
(*prevRAP) = 0;
(*nextRAP) = 0;
(*IsRAP) = RAP_NO;
if (!stbl->sampleGroups || !stbl->sampleGroupsDescription) return GF_OK;
count = gf_list_count(stbl->sampleGroups);
count2 = gf_list_count(stbl->sampleGroupsDescription);
for (i=0; i<count; i++) {
GF_SampleGroupDescriptionBox *sgdp = NULL;
Bool is_rap_group = 0;
s32 roll_distance = 0;
u32 first_sample_in_entry, last_sample_in_entry;
GF_SampleGroupBox *sg = gf_list_get(stbl->sampleGroups, i);
switch (sg->grouping_type) {
case GF_ISOM_SAMPLE_GROUP_RAP:
case GF_ISOM_SAMPLE_GROUP_SYNC:
is_rap_group = 1;
break;
case GF_ISOM_SAMPLE_GROUP_ROLL:
break;
default:
continue;
}
for (j=0; j<count2; j++) {
sgdp = gf_list_get(stbl->sampleGroupsDescription, j);
if (sgdp->grouping_type==sg->grouping_type) break;
sgdp = NULL;
}
if (! sgdp) continue;
first_sample_in_entry=1;
for (j=0; j<sg->entry_count; j++) {
u32 first_rap_in_entry, last_rap_in_entry;
last_sample_in_entry = first_sample_in_entry + sg->sample_entries[j].sample_count - 1;
/*samples in this entry are not RAPs, continue*/
if (! sg->sample_entries[j].group_description_index) {
first_sample_in_entry += sg->sample_entries[j].sample_count;
continue;
}
if (!is_rap_group) {
GF_RollRecoveryEntry *entry = gf_list_get(sgdp->group_descriptions, sg->sample_entries[j].group_description_index - 1);
roll_distance = entry ? entry->roll_distance : 0;
}
/*we consider the first sample in a roll or rap group entry to be the RAP (eg, we have to decode from this sample anyway)
except if roll_distance is strictly negative in which case we have to rewind our sample numbers from roll_distance*/
if (roll_distance < 0) {
if ((s32) first_sample_in_entry + roll_distance>=0) first_rap_in_entry = first_sample_in_entry + roll_distance;
else first_rap_in_entry = 0;
if ((s32) last_sample_in_entry + roll_distance>=0) last_rap_in_entry = last_sample_in_entry + roll_distance;
else last_rap_in_entry = 0;
} else {
first_rap_in_entry = first_sample_in_entry;
last_rap_in_entry = last_sample_in_entry;
}
/*store previous & next sample RAP - note that we do not store the closest previous RAP, only the first of the previous RAP group
as RAPs are usually isolated this should not be an issue*/
if (first_rap_in_entry <= SampleNumber) {
*prevRAP = first_rap_in_entry;
}
*nextRAP = last_rap_in_entry;
/*sample lies in this (rap) group, it is rap*/
if (is_rap_group) {
if ((first_rap_in_entry <= SampleNumber) && (SampleNumber <= last_rap_in_entry)) {
(*IsRAP) = RAP;
return GF_OK;
}
} else {
/*prevRAP or nextRAP matches SampleNumber, sample is RAP*/
if ((*prevRAP == SampleNumber) || (*nextRAP == SampleNumber)) {
(*IsRAP) = RAP;
return GF_OK;
}
}
/*first sample in entry is after our target sample, abort*/
if (first_rap_in_entry > SampleNumber) {
break;
}
first_sample_in_entry += sg->sample_entries[j].sample_count;
}
}
return GF_OK;
}
//get the number of "ghost chunk" (implicit chunks described by an entry)
void GetGhostNum(GF_StscEntry *ent, u32 EntryIndex, u32 count, GF_SampleTableBox *stbl)
{
GF_StscEntry *nextEnt;
u32 ghostNum = 1;
if (!ent) {
stbl->SampleToChunk->ghostNumber = 0;
return;
}
if (!ent->nextChunk) {
if (EntryIndex+1 == count) {
//not specified in the spec, what if the last sample to chunk is no written?
if (stbl->ChunkOffset->type == GF_ISOM_BOX_TYPE_STCO) {
GF_ChunkOffsetBox *stco = (GF_ChunkOffsetBox *)stbl->ChunkOffset;
ghostNum = (stco->nb_entries > ent->firstChunk) ? (1 + stco->nb_entries - ent->firstChunk) : 1;
} else {
GF_ChunkLargeOffsetBox *co64 = (GF_ChunkLargeOffsetBox *)stbl->ChunkOffset;
ghostNum = (co64->nb_entries > ent->firstChunk) ? (1 + co64->nb_entries - ent->firstChunk) : 1;
}
} else {
//this is an unknown case due to edit mode...
nextEnt = &stbl->SampleToChunk->entries[EntryIndex+1];
ghostNum = nextEnt->firstChunk - ent->firstChunk;
}
} else {
ghostNum = (ent->nextChunk > ent->firstChunk) ? (ent->nextChunk - ent->firstChunk) : 1;
}
stbl->SampleToChunk->ghostNumber = ghostNum;
}
//Get the offset, descIndex and chunkNumber of a sample...
GF_Err stbl_GetSampleInfos(GF_SampleTableBox *stbl, u32 sampleNumber, u64 *offset, u32 *chunkNumber, u32 *descIndex, GF_StscEntry **out_ent)
{
GF_Err e;
u32 i, k, offsetInChunk, size, chunk_num;
GF_ChunkOffsetBox *stco;
GF_ChunkLargeOffsetBox *co64;
GF_StscEntry *ent;
(*offset) = 0;
(*chunkNumber) = (*descIndex) = 0;
if (out_ent) (*out_ent) = NULL;
if (!stbl || !sampleNumber) return GF_BAD_PARAM;
if (!stbl->ChunkOffset || !stbl->SampleToChunk || !stbl->SampleSize || !stbl->SampleToChunk->entries) return GF_ISOM_INVALID_FILE;
if (stbl->SampleSize && stbl->SampleToChunk->nb_entries == stbl->SampleSize->sampleCount) {
ent = &stbl->SampleToChunk->entries[sampleNumber-1];
if (!ent) return GF_BAD_PARAM;
(*descIndex) = ent->sampleDescriptionIndex;
(*chunkNumber) = sampleNumber;
if (out_ent) *out_ent = ent;
if (stbl->ChunkOffset->type == GF_ISOM_BOX_TYPE_STCO) {
stco = (GF_ChunkOffsetBox *)stbl->ChunkOffset;
if (!stco->offsets) return GF_ISOM_INVALID_FILE;
if (stco->nb_entries < sampleNumber) return GF_ISOM_INVALID_FILE;
(*offset) = (u64) stco->offsets[sampleNumber - 1];
} else {
co64 = (GF_ChunkLargeOffsetBox *)stbl->ChunkOffset;
if (!co64->offsets) return GF_ISOM_INVALID_FILE;
if (co64->nb_entries < sampleNumber) return GF_ISOM_INVALID_FILE;
(*offset) = co64->offsets[sampleNumber - 1];
}
return GF_OK;
}
//check our cache: if desired sample is at or above current cache entry, start from here
if (stbl->SampleToChunk->firstSampleInCurrentChunk &&
(stbl->SampleToChunk->firstSampleInCurrentChunk <= sampleNumber)) {
i = stbl->SampleToChunk->currentIndex;
ent = &stbl->SampleToChunk->entries[stbl->SampleToChunk->currentIndex];
GetGhostNum(ent, i, stbl->SampleToChunk->nb_entries, stbl);
k = stbl->SampleToChunk->currentChunk;
}
//otherwise start from first entry
else {
i = 0;
stbl->SampleToChunk->currentIndex = 0;
stbl->SampleToChunk->currentChunk = 1;
stbl->SampleToChunk->ghostNumber = 1;
stbl->SampleToChunk->firstSampleInCurrentChunk = 1;
ent = &stbl->SampleToChunk->entries[0];
GetGhostNum(ent, 0, stbl->SampleToChunk->nb_entries, stbl);
k = stbl->SampleToChunk->currentChunk;
}
//first get the chunk
for (; i < stbl->SampleToChunk->nb_entries; i++) {
//corrupted file (less sample2chunk info than sample count
if (stbl->SampleToChunk->firstSampleInCurrentChunk > sampleNumber || k > stbl->SampleToChunk->ghostNumber) {
return GF_ISOM_INVALID_FILE;
}
//check if sample is in current chunk
u32 max_chunks_in_entry = stbl->SampleToChunk->ghostNumber - k;
u32 nb_chunks_for_sample = sampleNumber - stbl->SampleToChunk->firstSampleInCurrentChunk;
if (ent->samplesPerChunk)
nb_chunks_for_sample /= ent->samplesPerChunk;
if (
(nb_chunks_for_sample <= max_chunks_in_entry)
&& (stbl->SampleToChunk->firstSampleInCurrentChunk + (nb_chunks_for_sample+1) * ent->samplesPerChunk > sampleNumber)
) {
stbl->SampleToChunk->firstSampleInCurrentChunk += nb_chunks_for_sample * ent->samplesPerChunk;
stbl->SampleToChunk->currentChunk += nb_chunks_for_sample;
goto sample_found;
}
max_chunks_in_entry += 1;
stbl->SampleToChunk->firstSampleInCurrentChunk += max_chunks_in_entry * ent->samplesPerChunk;
stbl->SampleToChunk->currentChunk += max_chunks_in_entry;
//not in this entry, get the next entry if not the last one
if (i+1 != stbl->SampleToChunk->nb_entries) {
ent = &stbl->SampleToChunk->entries[i+1];
//update the GhostNumber
GetGhostNum(ent, i+1, stbl->SampleToChunk->nb_entries, stbl);
//update the entry in our cache
stbl->SampleToChunk->currentIndex = i+1;
stbl->SampleToChunk->currentChunk = 1;
k = 1;
}
}
//if we get here, gasp, the sample was not found
return GF_ISOM_INVALID_FILE;
sample_found:
(*descIndex) = ent->sampleDescriptionIndex;
(*chunkNumber) = chunk_num = ent->firstChunk + stbl->SampleToChunk->currentChunk - 1;
if (out_ent) *out_ent = ent;
if (! *chunkNumber)
return GF_ISOM_INVALID_FILE;
//ok, get the size of all the previous samples in the chunk
offsetInChunk = 0;
//constant size
if (stbl->SampleSize && stbl->SampleSize->sampleSize) {
u32 diff = sampleNumber - stbl->SampleToChunk->firstSampleInCurrentChunk;
offsetInChunk += diff * stbl->SampleSize->sampleSize;
} else if ((stbl->r_last_chunk_num == chunk_num) && (stbl->r_last_sample_num == sampleNumber)) {
offsetInChunk = stbl->r_last_offset_in_chunk;
} else if ((stbl->r_last_chunk_num == chunk_num) && (stbl->r_last_sample_num + 1 == sampleNumber)) {
e = stbl_GetSampleSize(stbl->SampleSize, stbl->r_last_sample_num, &size);
if (e) return e;
stbl->r_last_offset_in_chunk += size;
stbl->r_last_sample_num = sampleNumber;
offsetInChunk = stbl->r_last_offset_in_chunk;
} else {
//warning, firstSampleInChunk is at least 1 - not 0
for (i = stbl->SampleToChunk->firstSampleInCurrentChunk; i < sampleNumber; i++) {
e = stbl_GetSampleSize(stbl->SampleSize, i, &size);
if (e) return e;
offsetInChunk += size;
}
stbl->r_last_chunk_num = chunk_num;
stbl->r_last_sample_num = sampleNumber;
stbl->r_last_offset_in_chunk = offsetInChunk;
}
//OK, that's the size of our offset in the chunk
//now get the chunk
if ( stbl->ChunkOffset->type == GF_ISOM_BOX_TYPE_STCO) {
stco = (GF_ChunkOffsetBox *)stbl->ChunkOffset;
if (stco->nb_entries < (*chunkNumber) ) return GF_ISOM_INVALID_FILE;
(*offset) = (u64) stco->offsets[(*chunkNumber) - 1] + (u64) offsetInChunk;
} else {
co64 = (GF_ChunkLargeOffsetBox *)stbl->ChunkOffset;
if (co64->nb_entries < (*chunkNumber) ) return GF_ISOM_INVALID_FILE;
(*offset) = co64->offsets[(*chunkNumber) - 1] + (u64) offsetInChunk;
}
return GF_OK;
}
GF_Err stbl_GetSampleShadow(GF_ShadowSyncBox *stsh, u32 *sampleNumber, u32 *syncNum)
{
u32 i, count;
GF_StshEntry *ent;
if (stsh->r_LastFoundSample && (stsh->r_LastFoundSample <= *sampleNumber)) {
i = stsh->r_LastEntryIndex;
} else {
i = 0;
stsh->r_LastFoundSample = 1;
}
ent = NULL;
(*syncNum) = 0;
count = gf_list_count(stsh->entries);
for (; i<count; i++) {
ent = (GF_StshEntry*)gf_list_get(stsh->entries, i);
//we get the exact desired sample !
if (ent->shadowedSampleNumber == *sampleNumber) {
(*syncNum) = ent->syncSampleNumber;
stsh->r_LastFoundSample = *sampleNumber;
stsh->r_LastEntryIndex = i;
return GF_OK;
} else if (ent->shadowedSampleNumber > *sampleNumber) {
//do we have an entry before ? If not, there is no shadowing available
//for this sample
if (!i) return GF_OK;
//ok, indicate the previous ShadowedSample
ent = (GF_StshEntry*)gf_list_get(stsh->entries, i-1);
(*syncNum) = ent->syncSampleNumber;
//change the sample number
(*sampleNumber) = ent->shadowedSampleNumber;
//reset the cache to the last ShadowedSample
stsh->r_LastEntryIndex = i-1;
stsh->r_LastFoundSample = ent->shadowedSampleNumber;
}
}
stsh->r_LastEntryIndex = i-1;
stsh->r_LastFoundSample = ent ? ent->shadowedSampleNumber : 0;
return GF_OK;
}
GF_Err stbl_GetPaddingBits(GF_PaddingBitsBox *padb, u32 SampleNumber, u8 *PadBits)
{
if (!PadBits) return GF_BAD_PARAM;
*PadBits = 0;
if (!padb || !padb->padbits) return GF_OK;
//the spec says "should" not shall. return 0 padding
if (padb->SampleCount < SampleNumber) return GF_OK;
*PadBits = padb->padbits[SampleNumber-1];
return GF_OK;
}
//Set the RAP flag of a sample
GF_Err stbl_GetSampleDepType(GF_SampleDependencyTypeBox *sdep, u32 SampleNumber, u32 *isLeading, u32 *dependsOn, u32 *dependedOn, u32 *redundant)
{
u8 flag;
gf_assert(dependsOn && dependedOn && redundant);
*dependsOn = *dependedOn = *redundant = 0;
if (SampleNumber > sdep->sampleCount) {
return GF_OK;
}
flag = sdep->sample_info[SampleNumber-1];
*isLeading = (flag >> 6) & 3;
*dependsOn = (flag >> 4) & 3;
*dependedOn = (flag >> 2) & 3;
*redundant = (flag) & 3;
return GF_OK;
}
#endif /*GPAC_DISABLE_ISOM*/