// Copyright 2024 Martin Riedl // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gomp4 import ( "encoding/binary" "fmt" ) // TrackFragmentRunBox Track Fragment Run Box struct // // 8.8.8 Track Fragment Run Box // Box Type: ‘trun’ // Container: Track Fragment Box ('traf') // Mandatory: No // Quantity: Zero or more // // Within the Track Fragment Box, there are zero or more Track Run Boxes. If the duration‐is‐empty flag is // set in the tf_flags, there are no track runs. A track run documents a contiguous set of samples for a track. // // The number of optional fields is determined from the number of bits set in the lower byte of the flags, // and the size of a record from the bits set in the second byte of the flags. This procedure shall be // followed, to allow for new fields to be defined. // // If the data‐offset is not present, then the data for this run starts immediately after the data of the // previous run, or at the base‐data‐offset defined by the track fragment header if this is the first run in a // track fragment, If the data‐offset is present, it is relative to the base‐data‐offset established in the track // fragment header. // // The composition offset values in the composition time‐to‐sample box and in the track run box may be // signed or unsigned. The recommendations given in the composition time‐to‐sample box concerning the // use of signed composition offsets also apply here. type TrackFragmentRunBox struct { *FullBox // the number of samples being added in this run; also the number of rows in the // following table (the rows can be empty) SampleCount uint32 // is added to the implicit or explicit data_offset established in the track fragment header. DataOffset int32 // provides a set of flags for the first sample only of this run. FirstSampleFlags uint32 Samples []TrackFragmentRunBoxSample } // BoxTypeTrackFragmentRun Track Fragment Run Box const BoxTypeTrackFragmentRun = "trun" func init() { BoxDefinitions = append(BoxDefinitions, BoxDefinition{ Type: BoxTypeTrackFragmentRun, ParentTypes: []string{BoxTypeTrackFragment}, Parser: ParseTrackFragmentRunBox, }) } const ( // TrackFragmentRunBoxFlagDataOffsetPresent Data Offset Flag TrackFragmentRunBoxFlagDataOffsetPresent uint32 = 0x00000001 // TrackFragmentRunBoxFlagFirstSampleFlagsPresent First Sample Flags // this over‐rides the default flags for the first sample only. This // makes it possible to record a group of frames where the first is a key and the rest are difference // frames, without supplying explicit flags for every sample. If this flag and field are used, sample‐ // flags shall not be present. TrackFragmentRunBoxFlagFirstSampleFlagsPresent uint32 = 0x00000004 // TrackFragmentRunBoxFlagSampleDurationPresent Sample Duration Flag // indicates that each sample has its own duration, otherwise the default is used. TrackFragmentRunBoxFlagSampleDurationPresent uint32 = 0x00000100 // TrackFragmentRunBoxFlagSampleSizePresent Sample Size Flag // each sample has its own size, otherwise the default is used. TrackFragmentRunBoxFlagSampleSizePresent uint32 = 0x00000200 // TrackFragmentRunBoxFlagSampleFlagsPresent Sample Flags Present Flag // each sample has its own flags, otherwise the default is used. TrackFragmentRunBoxFlagSampleFlagsPresent uint32 = 0x00000400 // TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent Sample Composition Time Offset // each sample has a composition time offset // (e.g. as used for I/P/B video in MPEG). TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent uint32 = 0x00000800 ) // ParseTrackFragmentRunBox creates a new Track Fragment Run Box struct func ParseTrackFragmentRunBox(parser *Parser, filePosition uint64, headerSize uint32, content []byte) (any, error) { // build full box with additional 4 bytes header fullBox := newFullBox(&Box{filePosition, headerSize}, content[0:4]) position := 4 // create track fragment run box box := &TrackFragmentRunBox{FullBox: fullBox} // parse sample counter box.SampleCount = binary.BigEndian.Uint32(content[position : position+4]) position += 4 // parse data offset if fullBox.flagUInt32&TrackFragmentRunBoxFlagDataOffsetPresent == TrackFragmentRunBoxFlagDataOffsetPresent { box.DataOffset = int32(binary.BigEndian.Uint32(content[position : position+4])) position += 4 } // parse first sample flags if fullBox.flagUInt32&TrackFragmentRunBoxFlagFirstSampleFlagsPresent == TrackFragmentRunBoxFlagFirstSampleFlagsPresent { box.FirstSampleFlags = binary.BigEndian.Uint32(content[position : position+4]) position += 4 } // parse samples for i := uint32(0); i < box.SampleCount; i++ { sample := TrackFragmentRunBoxSample{} // parse duration if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleDurationPresent == TrackFragmentRunBoxFlagSampleDurationPresent { sample.Duration = binary.BigEndian.Uint32(content[position : position+4]) position += 4 } // parse size if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleSizePresent == TrackFragmentRunBoxFlagSampleSizePresent { sample.Size = binary.BigEndian.Uint32(content[position : position+4]) position += 4 } // parse flags if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleFlagsPresent == TrackFragmentRunBoxFlagSampleFlagsPresent { sample.Flags = binary.BigEndian.Uint32(content[position : position+4]) position += 4 } // parse composition time offset if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent == TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent { if fullBox.Version == 0 { sample.CompositionTimeOffsetV0 = binary.BigEndian.Uint32(content[position : position+4]) position += 4 } else { sample.CompositionTimeOffset = int32(binary.BigEndian.Uint32(content[position : position+4])) position += 4 } } // store new sample box.Samples = append(box.Samples, sample) } // check, if everything has been parsed if position != len(content) { return nil, fmt.Errorf("invalid byte position %d; expected position %d in file box position %d", position, len(content), filePosition) } return box, nil } // TrackFragmentRunBoxSample contains sample information type TrackFragmentRunBoxSample struct { Duration uint32 Size uint32 Flags uint32 CompositionTimeOffsetV0 uint32 CompositionTimeOffset int32 }