/// <summary> /// Read - read in the SampleTableBox from the input MP4 file. /// Sub-boxes can come in in any order. /// </summary> /// <param name="reader">BoxReader</param> public override void Read(BoxReader reader) { using (new SizeChecker(this, reader)) { base.Read(reader); while (reader.BaseStream.Position < (long)(this.Size + this.Offset)) { long pos = reader.BaseStream.Position; Box test = new Box(BoxTypes.Any); test.Read(reader); reader.BaseStream.Position = pos; if (test.Type == BoxTypes.TimeToSample) { this.DecodingTimeToSampleBox = new DecodingTimeToSampleBox(this); DecodingTimeToSampleBox.Read(reader); } else if (test.Type == BoxTypes.CompositionOffset) { this.CompositionTimeToSample = new CompositionTimeToSample(this); CompositionTimeToSample.Read(reader); } else if (test.Type == BoxTypes.SampleDescription) { this.SampleDescriptionsBox = new SampleDescriptionsBox(this); SampleDescriptionsBox.Read(reader); } else if (test.Type == BoxTypes.SampleToChunk) { this.SampleToChunkBox = new SampleToChunkBox(this); SampleToChunkBox.Read(reader); } else if (test.Type == BoxTypes.SampleSize) { this.SampleSizeBox = new SampleSizeBox(this); SampleSizeBox.Read(reader); } else if (test.Type == BoxTypes.ChunkOffset) // FIXME: this can be a "co64" box { this.ChunkOffSetBox = new ChunkOffSetBox(this); ChunkOffSetBox.Read(reader); } else if (test.Type == BoxTypes.SyncSampleMap) { this.SyncSampleMapBox = new SyncSampleMapBox(); SyncSampleMapBox.Read(reader); } else { reader.BaseStream.Position = (long)(test.Size + test.Offset); // skip unknown box Debug.WriteLine(string.Format("Unknown box type {0} in SampleTableBox (stbl), skipped", test.Type.ToString())); } } // end of while if (SampleToChunkBox != null) { SampleToChunkBox.CheckIntegrityOfChunkData(); } } }
/// <summary> /// Read - read in the SampleTableBox from the input MP4 file. /// Sub-boxes can come in in any order. /// </summary> /// <param name="reader">BoxReader</param> public override void Read(BoxReader reader) { using (new SizeChecker(this, reader)) { base.Read(reader); while (reader.BaseStream.Position < (long)(this.Size + this.Offset)) { long pos = reader.BaseStream.Position; Box test = new Box(BoxTypes.Any); test.Read(reader); reader.BaseStream.Position = pos; if (test.Type == BoxTypes.TimeToSample) { this.DecodingTimeToSampleBox = new DecodingTimeToSampleBox(this); DecodingTimeToSampleBox.Read(reader); } else if (test.Type == BoxTypes.CompositionOffset) { this.CompositionTimeToSample = new CompositionTimeToSample(this); CompositionTimeToSample.Read(reader); } else if (test.Type == BoxTypes.SampleDescription) { this.SampleDescriptionsBox = new SampleDescriptionsBox(this); SampleDescriptionsBox.Read(reader); } else if (test.Type == BoxTypes.SampleToChunk) { this.SampleToChunkBox = new SampleToChunkBox(this); SampleToChunkBox.Read(reader); } else if (test.Type == BoxTypes.SampleSize) { this.SampleSizeBox = new SampleSizeBox(this); SampleSizeBox.Read(reader); } else if (test.Type == BoxTypes.ChunkOffset) { // FIXME: this can be a "co64" box this.ChunkOffSetBox = new ChunkOffSetBox(this); ChunkOffSetBox.Read(reader); } else if (test.Type == BoxTypes.SyncSampleMap) { this.SyncSampleMapBox = new SyncSampleMapBox(); SyncSampleMapBox.Read(reader); } else { reader.BaseStream.Position = (long)(test.Size + test.Offset); // skip unknown box Debug.WriteLine(string.Format("Unknown box type {0} in SampleTableBox (stbl), skipped", test.Type.ToString())); } } // end of while if (SampleToChunkBox != null) SampleToChunkBox.CheckIntegrityOfChunkData(); } }
private uint SampleCountInLastBatch = 0; // last batch sample count for this trak box (this is independent of the other trak box) /// <summary> /// InitSampleTableBoxFromStreamLocations /// Initialize the boxes that point to where the payload bits are, without writing them out to final destination file yet. /// Major change (06/06/2012): favor creating new chunks over accumulating slices in a chunk. /// We take it to the extreme here, like VLC does it: we create a new chunk for every slice/sample. /// What this does is make the stsc box small, but the stco box very large. The advantage is that /// every slice now has an offset into mdat (and the slice crawler can't possibly go out of sync). /// </summary> /// <param name="streamLocations">List of StreamDataBlockInfo extracted from source stream, possibly using InitSampleStreamFromSampleTableBox above.</param> public void InitSampleTableBoxFromStreamLocations(List <StreamDataBlockInfo> streamLocations, ref ulong currMdatOffset) { // if this is the first call, create temp files if (SttsCountsWriter == null) { CreateTempFiles(); } if (CompositionTimeToSample == null && (CTTSOut) && (streamLocations.Any(d => (d.CTS > 0UL) || (d.SliceType == SliceType.BFrame)))) { CompositionTimeToSample = new CompositionTimeToSample(this); } if (streamLocations.Count == 0) { throw new Exception("InitSampleTableBoxFromStreamLocations: SampleStreamLocations list empty."); } bool needNewChunk = true; foreach (StreamDataBlockInfo sample in streamLocations) { uint scaledDuration = (uint)TimeArithmetic.ConvertToTimeScale(parent.parent.MediaHeaderBox.TimeScale, sample.SliceDuration); if (LastDuration == 0) { sampleCountInStts = 1; } else if (LastDuration == scaledDuration) { sampleCountInStts++; } else { WriteToSttsTempFile(); sampleCountInStts = 1; // this one for which duration is different counts as one } LastDuration = scaledDuration; //TimeTicks += sample.SliceDuration; if (sample.SliceType == SliceType.IFrame) { SyncSampleMapWriter.Write(SampleIndex); // if the SyncSampleMapStream has zero length when all is done, then its box should be null CurrSyncSampleMapCount++; } // compute CTTS from TimeStamp and CTS if (CompositionTimeToSample != null) { // CTS = Composition Time of the Sample, so these values are ever-increasing // CTTS = Composition Time relative to Time of the Sample, so these are really either 0 or some multiple of the typical sample duration // CTTS values for an i-frame, for example, is always zero, as its composition time relative to the sample: // CTTS-iframe = SampleTime - CTS = Always 0 if (sample.SliceType == SliceType.IFrame) { // relative time for an iframe is always 0 CompositionTimeToSample.AddEntry(0); LastSynchTime = 0; } else { //if (sample.TimeStampNew.HasValue) { // // relative time for a d-frame is always 0 // uint TimeFromLastSample = (uint)TimeArithmetic.ConvertToTimeScale(parent.parent.MediaHeaderBox.TimeScale, sample.SliceDuration); // CompositionTimeToSample.AddEntry((uint)TimeFromLastSample); //} else { // // this means we are a b-frame // CompositionTimeToSample.AddEntry((uint)uint.MaxValue); //} if (!sample.TimeStampNew.HasValue || sample.SliceType == SliceType.BFrame) { // this means we are a b-frame uint TimeFromLastSample = (uint)TimeArithmetic.ConvertToTimeScale(parent.parent.MediaHeaderBox.TimeScale, sample.SliceDuration); // we get further from a sync time for each consecutive b-frame we have // as you can see above if we encounter an i or d frame we snap back to a delta of 0 LastSynchTime += TimeFromLastSample; CompositionTimeToSample.AddEntry((uint)LastSynchTime); } else if (sample.TimeStampNew.HasValue) { // relative time for a d-frame is always 0 CompositionTimeToSample.AddEntry(0); LastSynchTime = 0; } } } // determine which chunk to put this sample in SampleSizeWriter.Write((uint)sample.SliceSize); SampleToChunkBox.SetFileOffsetForChunk(SampleIndex, (uint)sample.SliceSize, 1u /* (uint)streamLocations.Count */, needNewChunk, ref currMdatOffset); needNewChunk = true; // always create a new chunk, thereby having only a single slice in every chunk (as in VLC output) SampleIndex++; } // set last count SampleCountInLastBatch = (uint)streamLocations.Count; }
/// <summary> /// InitSampleTableBoxFromStreamLocations /// Initialize the boxes that point to where the payload bits are, without writing them out to final destination file yet. /// Major change (06/06/2012): favor creating new chunks over accumulating slices in a chunk. /// We take it to the extreme here, like VLC does it: we create a new chunk for every slice/sample. /// What this does is make the stsc box small, but the stco box very large. The advantage is that /// every slice now has an offset into mdat (and the slice crawler can't possibly go out of sync). /// </summary> /// <param name="streamLocations">List of StreamDataBlockInfo extracted from source stream, possibly using InitSampleStreamFromSampleTableBox above.</param> public void InitSampleTableBoxFromStreamLocations(List<StreamDataBlockInfo> streamLocations, ref ulong currMdatOffset) { // if this is the first call, create temp files if (SttsCountsWriter == null) CreateTempFiles(); if (CompositionTimeToSample == null && (CTTSOut) && (streamLocations.Any(d => (d.CTS > 0UL) || (d.SliceType == SliceType.BFrame)))) { CompositionTimeToSample = new CompositionTimeToSample(this); } if (streamLocations.Count == 0) throw new Exception("InitSampleTableBoxFromStreamLocations: SampleStreamLocations list empty."); bool needNewChunk = true; foreach (StreamDataBlockInfo sample in streamLocations) { uint scaledDuration = (uint)TimeArithmetic.ConvertToTimeScale(parent.parent.MediaHeaderBox.TimeScale, sample.SliceDuration); if (LastDuration == 0) { sampleCountInStts = 1; } else if (LastDuration == scaledDuration) { sampleCountInStts++; } else { WriteToSttsTempFile(); sampleCountInStts = 1; // this one for which duration is different counts as one } LastDuration = scaledDuration; //TimeTicks += sample.SliceDuration; if (sample.SliceType == SliceType.IFrame) { SyncSampleMapWriter.Write(SampleIndex); // if the SyncSampleMapStream has zero length when all is done, then its box should be null CurrSyncSampleMapCount++; } // compute CTTS from TimeStamp and CTS if (CompositionTimeToSample != null) { // CTS = Composition Time of the Sample, so these values are ever-increasing // CTTS = Composition Time relative to Time of the Sample, so these are really either 0 or some multiple of the typical sample duration // CTTS values for an i-frame, for example, is always zero, as its composition time relative to the sample: // CTTS-iframe = SampleTime - CTS = Always 0 if (sample.SliceType == SliceType.IFrame) { // relative time for an iframe is always 0 CompositionTimeToSample.AddEntry(0); LastSynchTime = 0; } else { //if (sample.TimeStampNew.HasValue) { // // relative time for a d-frame is always 0 // uint TimeFromLastSample = (uint)TimeArithmetic.ConvertToTimeScale(parent.parent.MediaHeaderBox.TimeScale, sample.SliceDuration); // CompositionTimeToSample.AddEntry((uint)TimeFromLastSample); //} else { // // this means we are a b-frame // CompositionTimeToSample.AddEntry((uint)uint.MaxValue); //} if (!sample.TimeStampNew.HasValue || sample.SliceType == SliceType.BFrame) { // this means we are a b-frame uint TimeFromLastSample = (uint)TimeArithmetic.ConvertToTimeScale(parent.parent.MediaHeaderBox.TimeScale, sample.SliceDuration); // we get further from a sync time for each consecutive b-frame we have // as you can see above if we encounter an i or d frame we snap back to a delta of 0 LastSynchTime += TimeFromLastSample; CompositionTimeToSample.AddEntry((uint)LastSynchTime); } else if (sample.TimeStampNew.HasValue) { // relative time for a d-frame is always 0 CompositionTimeToSample.AddEntry(0); LastSynchTime = 0; } } } // determine which chunk to put this sample in SampleSizeWriter.Write((uint)sample.SliceSize); SampleToChunkBox.SetFileOffsetForChunk(SampleIndex, (uint)sample.SliceSize, 1u /* (uint)streamLocations.Count */, needNewChunk, ref currMdatOffset); needNewChunk = true; // always create a new chunk, thereby having only a single slice in every chunk (as in VLC output) SampleIndex++; } // set last count SampleCountInLastBatch = (uint)streamLocations.Count; }