From 94aed731f2348c9c17f2cb67d4a5304e158afb88 Mon Sep 17 00:00:00 2001 From: Martin Riedl Date: Tue, 10 Dec 2024 21:20:15 +0100 Subject: [PATCH] feat: new sample size and sample to chunk box --- README.md | 4 +- SampleSizeBox.go | 87 ++++++++++++++++++++++++++++++++++++++++ SampleToChunkBox.go | 97 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 SampleSizeBox.go create mode 100644 SampleToChunkBox.go diff --git a/README.md b/README.md index 0134af7..e318072 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ Implementation progress of ISO/IEC 14496-12:2015: | 8.6.6 Edit List Box | elst | - | | 8.7.1 Data Information Box | dinf | 100% | | 8.7.2 Data Reference Box | dref, url, urn | 100% | -| 8.7.3 Sample Size Boxes | stsz, stz2 | - | -| 8.7.4 Sample To Chunk Box | stsc | - | +| 8.7.3 Sample Size Boxes | stsz, stz2 | 50% | +| 8.7.4 Sample To Chunk Box | stsc | 100% | | 8.7.5 Chunk Offset Box | stco, co64 | - | | 8.7.6 Padding Bits Box | padb | - | | 8.7.7 Sub-Sample Information Box | subs | - | diff --git a/SampleSizeBox.go b/SampleSizeBox.go new file mode 100644 index 0000000..c921582 --- /dev/null +++ b/SampleSizeBox.go @@ -0,0 +1,87 @@ +// 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" +) + +// SampleSizeBox sample size box struct +// +// Box Type: ‘stsz’, ‘stz2’ +// Container: Sample Table Box (‘stbl’) +// Mandatory: Yes +// Quantity: Exactly one variant must be present +// +// This box contains the sample count and a table giving the size in bytes of each sample. This allows the +// media data itself to be unframed. The total number of samples in the media is always indicated in the +// sample count. +// +// There are two variants of the sample size box. The first variant has a fixed size 32‐bit field for +// representing the sample sizes; it permits defining a constant size for all samples in a track. The second +// variant permits smaller size fields, to save space when the sizes are varying but small. One of these +// boxes must be present; the first version is preferred for maximum compatibility. +type SampleSizeBox struct { + *FullBox + // is integer specifying the default sample size. If all the samples are the same size, + // this field contains that size value. If this field is set to 0, then the samples have different sizes, + // and those sizes are stored in the sample size table. If this field is not 0, it specifies the constant + // sample size, and no array follows. + SampleSize uint32 + // is an integer that gives the number of samples in the track; if sample‐size is 0, then + // it is also the number of entries in the following table. + SampleCount uint32 + // is an integer specifying the size of a sample, indexed by its number. + SampleSizeList []SampleSizeEntry +} + +type SampleSizeEntry struct { + EntrySize uint32 +} + +// BoxTypeSampleSizeBox Sample Size Box +const BoxTypeSampleSizeBox = "stsz" + +func init() { + BoxDefinitions = append(BoxDefinitions, BoxDefinition{ + Type: BoxTypeSampleSizeBox, + ParentTypes: []string{BoxTypeSampleTable}, + Parser: ParseSampleSizeBox, + }) +} + +// ParseSampleSizeBox creates a new sample size box struct based on bytes +func ParseSampleSizeBox(parser *Parser, filePosition uint64, headerSize uint32, content []byte) (any, error) { + box := &SampleSizeBox{ + FullBox: newFullBox(&Box{filePosition, headerSize}, content[0:4]), + } + + // parse fields + box.SampleSize = binary.BigEndian.Uint32(content[4:8]) + box.SampleCount = binary.BigEndian.Uint32(content[8:12]) + position := 12 + + // parse sample sizes + if box.SampleSize == 0 { + for i := 0; i < int(box.SampleCount); i++ { + sampleEntry := SampleSizeEntry{ + EntrySize: binary.BigEndian.Uint32(content[position+(i*4) : position+4+(i*4)]), + } + box.SampleSizeList = append(box.SampleSizeList, sampleEntry) + } + } + + return box, nil +} diff --git a/SampleToChunkBox.go b/SampleToChunkBox.go new file mode 100644 index 0000000..083716c --- /dev/null +++ b/SampleToChunkBox.go @@ -0,0 +1,97 @@ +// 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" +) + +// SampleToChunkBox sample to chunk box struct +// +// 8.7.4 Sample To Chunk Box +// +// Box Type: ‘stsc’ +// Container: Sample Table Box (‘stbl’) +// Mandatory: Yes +// Quantity: Exactly one +// +// Samples within the media data are grouped into chunks. Chunks can be of different sizes, and the +// samples within a chunk can have different sizes. This table can be used to find the chunk that contains a +// sample, its position, and the associated sample description. +// +// The table is compactly coded. Each entry gives the index of the first chunk of a run of chunks with the +// same characteristics. By subtracting one entry here from the previous one, you can compute how many +// chunks are in this run. You can convert this to a sample count by multiplying by the appropriate +// samples‐per‐chunk. +type SampleToChunkBox struct { + *FullBox + // is an integer that gives the number of entries in the following table + EntryCount uint32 + Entries []SampleToChunkBoxEntry +} + +type SampleToChunkBoxEntry struct { + // is an integer that gives the index of the first chunk in this run of chunks that share + // the same samples‐per‐chunk and sample‐description‐index; the index of the first chunk in a + // track has the value 1 (the first_chunk field in the first record of this box has the value 1, + // identifying that the first sample maps to the first chunk). + FirstChunk uint32 + // is an integer that gives the number of samples in each of these chunks + SamplesPerChunk uint32 + // is an integer that gives the index of the sample entry that + // describes the samples in this chunk. The index ranges from 1 to the number of sample entries in + // the Sample Description Box + SampleDescriptionIndex uint32 +} + +// BoxTypeSampleToChunk Sample To Chunk Box +const BoxTypeSampleToChunk = "stsc" + +func init() { + BoxDefinitions = append(BoxDefinitions, BoxDefinition{ + Type: BoxTypeSampleToChunk, + ParentTypes: []string{BoxTypeSampleTable}, + Parser: ParseSampleToChunkBox, + }) +} + +// ParseSampleToChunkBox creates a new sample to chunk box struct based on bytes +func ParseSampleToChunkBox(parser *Parser, filePosition uint64, headerSize uint32, content []byte) (any, error) { + box := &SampleToChunkBox{ + FullBox: newFullBox(&Box{filePosition, headerSize}, content[0:4]), + } + + // parse entry counter + box.EntryCount = binary.BigEndian.Uint32(content[4:8]) + + // parse entries + position := 8 + for i := 0; i < int(box.EntryCount); i++ { + newSampleEntry := SampleToChunkBoxEntry{ + FirstChunk: binary.BigEndian.Uint32(content[position+(i*12) : position+4+(i*12)]), + SamplesPerChunk: binary.BigEndian.Uint32(content[position+4+(i*12) : position+8+(i*12)]), + SampleDescriptionIndex: binary.BigEndian.Uint32(content[position+8+(i*12) : position+12+(i*12)]), + } + box.Entries = append(box.Entries, newSampleEntry) + } + + // validate entries amount + if len(box.Entries) != int(box.EntryCount) { + return box, fmt.Errorf("invalid amount of entries at %d; got %d but expected %d", filePosition, len(box.Entries), box.EntryCount) + } + + return box, nil +}