//WARNING: MOVIETIME IS EXPRESSED IN MEDIA TS 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) { #if 0 GF_Err e; u32 sampleNumber, prevSampleNumber; u64 firstDTS; #endif u32 i, count; Bool last_is_empty = 0; u64 time, lastSampleTime; s64 mtime; GF_EdtsEntry *ent; Double scale_ts; GF_SampleTableBox *stbl = trak->Media->information->sampleTable; if (next_edit_start_plus_one) *next_edit_start_plus_one = 0; *useEdit = 1; *MediaTime = 0; //no segment yet... *SegmentStartTime = -1; *MediaOffset = -1; if (!trak->moov->mvhd->timeScale || !trak->Media->mediaHeader->timeScale || !stbl->SampleSize) { return GF_ISOM_INVALID_FILE; } //no samples... if (!stbl->SampleSize->sampleCount) { lastSampleTime = 0; } else { lastSampleTime = trak->Media->mediaHeader->duration; } //No edits, 1 to 1 mapping if (! trak->editBox || !trak->editBox->editList) { *MediaTime = movieTime; //check this is in our media time line if ((*MediaTime > lastSampleTime) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS && !trak->moov->mov->moof #endif ) { *MediaTime = lastSampleTime; } *useEdit = 0; return GF_OK; } //browse the edit list and get the time scale_ts = trak->Media->mediaHeader->timeScale; scale_ts /= trak->moov->mvhd->timeScale; time = 0; ent = NULL; count=gf_list_count(trak->editBox->editList->entryList); for (i=0; i<count; i++) { ent = (GF_EdtsEntry *)gf_list_get(trak->editBox->editList->entryList, i); if ( (time + ent->segmentDuration) * scale_ts > movieTime) { if (!force_non_empty || (ent->mediaTime >= 0)) { if (next_edit_start_plus_one) *next_edit_start_plus_one = 1 + (u64) ((time + ent->segmentDuration) * scale_ts); goto ent_found; } } time += ent->segmentDuration; last_is_empty = ent->segmentDuration ? 0 : 1; } if (last_is_empty) { ent = (GF_EdtsEntry *)gf_list_last(trak->editBox->editList->entryList); if (ent->mediaRate==1) { *MediaTime = movieTime + ent->mediaTime; } else { ent = (GF_EdtsEntry *)gf_list_get(trak->editBox->editList->entryList, 0); if (ent->mediaRate==-1) { u64 dur = (u64) (ent->segmentDuration * scale_ts); *MediaTime = (movieTime > dur) ? (movieTime-dur) : 0; } } *useEdit = 0; return GF_OK; } //we had nothing in the list (strange file but compliant...) //return the 1 to 1 mapped vale of the last media sample if (!ent) { *MediaTime = movieTime; //check this is in our media time line if (*MediaTime > lastSampleTime) *MediaTime = lastSampleTime; *useEdit = 0; return GF_OK; } //request for a bigger time that what we can give: return the last sample (undefined behavior...) *MediaTime = lastSampleTime; return GF_OK; ent_found: //OK, we found our entry, set the SegmentTime *SegmentStartTime = time; //we request an empty list, there's no media here... if (ent->mediaTime < 0) { *MediaTime = 0; return GF_OK; } //we request a dwell edit if (! ent->mediaRate) { *MediaTime = ent->mediaTime; //no media offset *MediaOffset = 0; *useEdit = 2; return GF_OK; } /*WARNING: this can be "-1" when doing searchForward mode (to prevent jumping to next entry)*/ mtime = ent->mediaTime + movieTime - (time * trak->Media->mediaHeader->timeScale / trak->moov->mvhd->timeScale); if (mtime<0) mtime = 0; *MediaTime = (u64) mtime; *MediaOffset = ent->mediaTime; #if 0 // //Sanity check: is the requested time valid ? This is to cope with wrong EditLists //we have the translated time, but we need to make sure we have a sample at this time ... //we have to find a COMPOSITION time e = findEntryForTime(stbl, (u32) *MediaTime, 1, &sampleNumber, &prevSampleNumber); if (e) return e; //first case: our time is after the last sample DTS (it's a broken editList somehow) //set the media time to the last sample if (!sampleNumber && !prevSampleNumber) { *MediaTime = lastSampleTime; return GF_OK; } //get the appropriated sample if (!sampleNumber) sampleNumber = prevSampleNumber; stbl_GetSampleDTS(stbl->TimeToSample, sampleNumber, &DTS); CTS = 0; if (stbl->CompositionOffset) stbl_GetSampleCTS(stbl->CompositionOffset, sampleNumber, &CTS); //now get the entry sample (the entry time gives the CTS, and we need the DTS e = findEntryForTime(stbl, (u32) ent->mediaTime, 0, &sampleNumber, &prevSampleNumber); if (e) return e; //oops, the mediaTime indicates a sample that is not in our media ! if (!sampleNumber && !prevSampleNumber) { *MediaTime = lastSampleTime; return GF_ISOM_INVALID_FILE; } if (!sampleNumber) sampleNumber = prevSampleNumber; stbl_GetSampleDTS(stbl->TimeToSample, sampleNumber, &firstDTS); //and store the "time offset" of the desired sample in this segment //this is weird, used to rebuild the timeStamp when reading from the track, not the //media ... *MediaOffset = firstDTS; #endif return GF_OK; }
GF_Err Media_GetSample(GF_MediaBox *mdia, u32 sampleNumber, GF_ISOSample **samp, u32 *sIDX, Bool no_data, u64 *out_offset) { GF_Err e; u32 bytesRead; u32 dataRefIndex, chunkNumber; u64 offset, new_size; u8 isEdited; GF_SampleEntryBox *entry; if (!mdia || !mdia->information->sampleTable) return GF_BAD_PARAM; //OK, here we go.... if (sampleNumber > mdia->information->sampleTable->SampleSize->sampleCount) return GF_BAD_PARAM; //get the DTS e = stbl_GetSampleDTS(mdia->information->sampleTable->TimeToSample, sampleNumber, &(*samp)->DTS); if (e) return e; //the CTS offset if (mdia->information->sampleTable->CompositionOffset) { e = stbl_GetSampleCTS(mdia->information->sampleTable->CompositionOffset , sampleNumber, &(*samp)->CTS_Offset); if (e) return e; } else { (*samp)->CTS_Offset = 0; } //the size e = stbl_GetSampleSize(mdia->information->sampleTable->SampleSize, sampleNumber, &(*samp)->dataLength); if (e) return e; //the RAP if (mdia->information->sampleTable->SyncSample) { e = stbl_GetSampleRAP(mdia->information->sampleTable->SyncSample, sampleNumber, &(*samp)->IsRAP, NULL, NULL); if (e) return e; } else { //if no SyncSample, all samples are sync (cf spec) (*samp)->IsRAP = 1; } /*overwrite sync sample with sample dep if any*/ if (mdia->information->sampleTable->SampleDep) { u32 dependsOn, dependedOn, redundant; e = stbl_GetSampleDepType(mdia->information->sampleTable->SampleDep, sampleNumber, &dependsOn, &dependedOn, &redundant); if (!e) { if (dependsOn==1) (*samp)->IsRAP = 0; else if (dependsOn==2) (*samp)->IsRAP = 1; /*if not depended upon and redundant, mark as carousel sample*/ if ((dependedOn==2) && (redundant==1)) (*samp)->IsRAP = 2; /*TODO FIXME - we must enhance the IsRAP semantics to carry disposable info ... */ } } /*get sync shadow*/ if (Media_IsSampleSyncShadow(mdia->information->sampleTable->ShadowSync, sampleNumber)) (*samp)->IsRAP = 2; //the data info if (!sIDX && !no_data) return GF_BAD_PARAM; if (!sIDX && !out_offset) return GF_OK; (*sIDX) = 0; e = stbl_GetSampleInfos(mdia->information->sampleTable, sampleNumber, &offset, &chunkNumber, sIDX, &isEdited); if (e) return e; //then get the DataRef e = Media_GetSampleDesc(mdia, *sIDX, &entry, &dataRefIndex); if (e) return e; // Open the data handler - check our mode, don't reopen in read only if this is //the same entry. In other modes we have no choice because the main data map is //divided into the original and the edition files if (mdia->mediaTrack->moov->mov->openMode == GF_ISOM_OPEN_READ) { //same as last call in read mode if (!mdia->information->dataHandler) { e = gf_isom_datamap_open(mdia, dataRefIndex, isEdited); if (e) return e; } if (mdia->information->dataEntryIndex != dataRefIndex) mdia->information->dataEntryIndex = dataRefIndex; } else { e = gf_isom_datamap_open(mdia, dataRefIndex, isEdited); if (e) return e; } if (out_offset) *out_offset = offset; if (no_data) return GF_OK; /*and finally get the data, include padding if needed*/ (*samp)->data = (char *) gf_malloc(sizeof(char) * ( (*samp)->dataLength + mdia->mediaTrack->padding_bytes) ); if (mdia->mediaTrack->padding_bytes) memset((*samp)->data + (*samp)->dataLength, 0, sizeof(char) * mdia->mediaTrack->padding_bytes); //check if we can get the sample (make sure we have enougth data...) new_size = gf_bs_get_size(mdia->information->dataHandler->bs); if (offset + (*samp)->dataLength > new_size) { //always refresh the size to avoid wrong info on http/ftp new_size = gf_bs_get_refreshed_size(mdia->information->dataHandler->bs); if (offset + (*samp)->dataLength > new_size) { mdia->BytesMissing = offset + (*samp)->dataLength - new_size; return GF_ISOM_INCOMPLETE_FILE; } } bytesRead = gf_isom_datamap_get_data(mdia->information->dataHandler, (*samp)->data, (*samp)->dataLength, offset); //if bytesRead != sampleSize, we have an IO err if (bytesRead < (*samp)->dataLength) { return GF_IO_ERR; } mdia->BytesMissing = 0; //finally rewrite the sample if this is an OD Access Unit if (mdia->handler->handlerType == GF_ISOM_MEDIA_OD) { e = Media_RewriteODFrame(mdia, *samp); if (e) return e; } /*FIXME: we don NOT rewrite sample if we have a encrypted track*/ else if (gf_isom_is_nalu_based_entry(mdia, entry) && !gf_isom_is_track_encrypted(mdia->mediaTrack->moov->mov, gf_isom_get_tracknum_from_id(mdia->mediaTrack->moov, mdia->mediaTrack->Header->trackID)) ) { e = gf_isom_nalu_sample_rewrite(mdia, *samp, sampleNumber, (GF_MPEGVisualSampleEntryBox *)entry); if (e) return e; } else if (mdia->mediaTrack->moov->mov->convert_streaming_text && ((mdia->handler->handlerType == GF_ISOM_MEDIA_TEXT) || (mdia->handler->handlerType == GF_ISOM_MEDIA_SUBT)) && (entry->type == GF_ISOM_BOX_TYPE_TX3G || entry->type == GF_ISOM_BOX_TYPE_TEXT) ) { u64 dur; if (sampleNumber == mdia->information->sampleTable->SampleSize->sampleCount) { dur = mdia->mediaHeader->duration - (*samp)->DTS; } else { stbl_GetSampleDTS(mdia->information->sampleTable->TimeToSample, sampleNumber+1, &dur); dur -= (*samp)->DTS; } e = gf_isom_rewrite_text_sample(*samp, *sIDX, (u32) dur); if (e) return e; } return GF_OK; }
M4Err Media_GetSample(MediaAtom *mdia, u32 sampleNumber, M4Sample **samp, u32 *sampleDescriptionIndex, Bool no_data, u64 *out_offset) { M4Err e; u32 bytesRead; u32 dataRefIndex, chunkNumber; u64 offset, new_size; u8 isEdited; SampleEntryAtom *entry; (*sampleDescriptionIndex) = 0; if (!mdia || !mdia->information->sampleTable) return M4BadParam; //OK, here we go.... if (sampleNumber > mdia->information->sampleTable->SampleSize->sampleCount) return M4BadParam; //get the DTS e = stbl_GetSampleDTS(mdia->information->sampleTable->TimeToSample, sampleNumber, &(*samp)->DTS); if (e) return e; //the CTS offset if (mdia->information->sampleTable->CompositionOffset) { e = stbl_GetSampleCTS(mdia->information->sampleTable->CompositionOffset , sampleNumber, &(*samp)->CTS_Offset); if (e) return e; } else { (*samp)->CTS_Offset = 0; } //the size e = stbl_GetSampleSize(mdia->information->sampleTable->SampleSize, sampleNumber, &(*samp)->dataLength); if (e) return e; //the RAP if (mdia->information->sampleTable->SyncSample) { e = stbl_GetSampleRAP(mdia->information->sampleTable->SyncSample, sampleNumber, &(*samp)->IsRAP, NULL, NULL); if (e) return e; } else { //if no SyncSample, all samples are sync (cf spec) (*samp)->IsRAP = 1; } /*get sync shadow*/ if (Media_IsSampleSyncShadow(mdia->information->sampleTable->ShadowSync, sampleNumber)) (*samp)->IsRAP = 2; //the data info e = stbl_GetSampleInfos(mdia->information->sampleTable, sampleNumber, &offset, &chunkNumber, sampleDescriptionIndex, &isEdited); if (e) return e; //then get the DataRef e = Media_GetSampleDesc(mdia, *sampleDescriptionIndex, &entry, &dataRefIndex); if (e) return e; // Open the data handler - check our mode, don't reopen in read only if this is //the same entry. In other modes we have no choice because the main data map is //divided into the original and the edition files if (mdia->mediaTrack->moov->mov->openMode == M4_OPEN_READ) { //same as last call in read mode if (!mdia->information->dataHandler || (mdia->information->dataEntryIndex != dataRefIndex)) { e = DataMap_Open(mdia, dataRefIndex, isEdited); if (e) return e; } } else { e = DataMap_Open(mdia, dataRefIndex, isEdited); if (e) return e; } if (out_offset) *out_offset = offset; if (no_data) return M4OK; /*and finally get the data, include padding if needed*/ (*samp)->data = (char *) malloc(sizeof(char) * ( (*samp)->dataLength + mdia->mediaTrack->padding_bytes) ); if (mdia->mediaTrack->padding_bytes) memset((*samp)->data + (*samp)->dataLength, 0, sizeof(char) * mdia->mediaTrack->padding_bytes); //check if we can get the sample (make sure we have enougth data...) new_size = BS_GetSize(mdia->information->dataHandler->bs); if (offset + (*samp)->dataLength > new_size) { //always refresh the size to avoid wrong info on http/ftp new_size = BS_GetRefreshedSize(mdia->information->dataHandler->bs); if (offset + (*samp)->dataLength > new_size) { mdia->BytesMissing = offset + (*samp)->dataLength - new_size; return M4UncompleteFile; } } bytesRead = DataMap_GetData(mdia->information->dataHandler, (*samp)->data, (*samp)->dataLength, offset); //if bytesRead != sampleSize, we have an IO err if (bytesRead < (*samp)->dataLength) { return M4IOErr; } mdia->BytesMissing = 0; //finally rewrite the sample if this is an OD Access Unit if (mdia->handler->handlerType == M4_ODMediaType) { e = Media_RewriteODFrame(mdia, *samp); if (e) return e; } else if (mdia->mediaTrack->moov->mov->convert_streaming_text && (mdia->handler->handlerType == M4_TimedTextMediaType) ) { u32 dur; if (sampleNumber == mdia->information->sampleTable->SampleSize->sampleCount) { dur = (u32) mdia->mediaHeader->duration - (*samp)->DTS; } else { stbl_GetSampleDTS(mdia->information->sampleTable->TimeToSample, sampleNumber+1, &dur); dur -= (*samp)->DTS; } e = M4_RewriteTextSample(*samp, *sampleDescriptionIndex, dur); if (e) return e; } return M4OK; }
//Get the sample number M4Err findEntryForTime(SampleTableAtom *stbl, u32 DTS, u8 useCTS, u32 *sampleNumber, u32 *prevSampleNumber) { u32 i, j, curDTS, curSampNum, CTSOffset; sttsEntry *ent; (*sampleNumber) = 0; (*prevSampleNumber) = 0; if (!stbl->CompositionOffset) useCTS = 0; //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; } //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 = (sttsEntry*)ChainGetEntry(stbl->TimeToSample->entryList, i); curSampNum -= ent->sampleCount; curDTS -= ent->sampleDelta * ent->sampleCount; i --; } else if (!i) { //begining 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; } } } //look for the DTS from this entry for (; i<ChainGetCount(stbl->TimeToSample->entryList); i++) { ent = (sttsEntry*)ChainGetEntry(stbl->TimeToSample->entryList, i); if (useCTS) { stbl_GetSampleCTS(stbl->CompositionOffset, curSampNum, &CTSOffset); } else { 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 += ent->sampleCount * ent->sampleDelta; stbl->TimeToSample->r_currentEntryIndex += 1; stbl->TimeToSample->r_FirstSampleInEntry += ent->sampleCount; } //return as is return M4OK; 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 M4OK; }