Skip to content

Commit

Permalink
Merge pull request #355 from Eyevinn/improved-url-box
Browse files Browse the repository at this point in the history
Improved url box parsing
  • Loading branch information
tobbee authored May 26, 2024
2 parents ffc56f6 + 462fad2 commit c0bae2c
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 32 deletions.
11 changes: 5 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- minimum Go version 1.16.
- ioutil imports replaced by io and os imports
- Info (mp4ff-info) output for esds boxes
- API of descriptors
- Parsing and info output for url boxes

### Fixed

- support for parsing of hierarchical sidx boxes
- handling of partially bad descriptors

### Changed

- Info (mp4ff-info) output for esds boxes
- API of descriptors
- handle url boxes missing mandatory zero-ending byte

### Added

- support for ssix box
- support for leva box
- details for descriptors as Info outout
- details of descriptors as Info outout (mp4ff-info)

## [0.44.0] - 2024-04-19

Expand Down
25 changes: 24 additions & 1 deletion bits/fixedslicereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (s *FixedSliceReader) ReadFixedLengthString(n int) string {
}

// ReadZeroTerminatedString - read string until zero byte but at most maxLen
// Set err and return empty string if no zero byte found
// Set err and return empty string if no zero byte found.
func (s *FixedSliceReader) ReadZeroTerminatedString(maxLen int) string {
if s.err != nil {
return ""
Expand All @@ -182,6 +182,29 @@ func (s *FixedSliceReader) ReadZeroTerminatedString(maxLen int) string {
}
}

// ReadPossiblyZeroTerminatedString - read string until zero byte but at most maxLen.
// If maxLen is reached and no zero-byte, return string and ok = false
func (s *FixedSliceReader) ReadPossiblyZeroTerminatedString(maxLen int) (str string, ok bool) {
startPos := s.pos
maxPos := startPos + maxLen
for {
if s.pos == maxPos {
return string(s.slice[startPos:s.pos]), true
}
if s.pos > maxPos {
s.err = errors.New("did not find terminating zero")
return "", false
}
c := s.slice[s.pos]
if c == 0 {
str = string(s.slice[startPos:s.pos])
s.pos++ // Next position to read
return str, true
}
s.pos++
}
}

// ReadBytes - read a slice of n bytes
// Return empty slice if n bytes not available
func (s *FixedSliceReader) ReadBytes(n int) []byte {
Expand Down
2 changes: 2 additions & 0 deletions bits/fixedslicereader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ func TestFixedSliceReader(t *testing.T) {
}

func verifyAccErrorInt(t *testing.T, sr *bits.FixedSliceReader, val int) {
t.Helper()
if sr.AccError() == nil {
t.Errorf("should have had an accumulated error")
}
Expand All @@ -246,6 +247,7 @@ func verifyAccErrorInt(t *testing.T, sr *bits.FixedSliceReader, val int) {
}

func verifyAccErrorString(t *testing.T, sr *bits.FixedSliceReader, val string) {
t.Helper()
if sr.AccError() == nil {
t.Errorf("should have had an accumulated error")
}
Expand Down
1 change: 1 addition & 0 deletions bits/slicereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type SliceReader interface {
ReadInt64() int64
ReadFixedLengthString(n int) string
ReadZeroTerminatedString(maxLen int) string
ReadPossiblyZeroTerminatedString(maxLen int) (str string, ok bool)
ReadBytes(n int) []byte
RemainingBytes() []byte
NrRemainingBytes() int
Expand Down
21 changes: 20 additions & 1 deletion mp4/descriptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ func (e *ESDescriptor) Type() string {
return TagType(e.Tag())
}

// Size is size of payload after tag and size field
func (e *ESDescriptor) Size() uint64 {
var size uint64 = 2 + 1
streamDependenceFlag := e.FlagsAndPriority >> 7
Expand All @@ -238,6 +239,7 @@ func (e *ESDescriptor) Size() uint64 {
return size
}

// SizeSize is size of size field.
func (e *ESDescriptor) SizeSize() uint64 {
return 1 + uint64(e.sizeFieldSizeMinus1) + 1 + e.Size()
}
Expand Down Expand Up @@ -324,6 +326,19 @@ func (e *ESDescriptor) Info(w io.Writer, specificLevels, indent, indentStep stri
return bd.err
}

// DecoderConfigDescriptor is defined in ISO/IEC 14496-1 Section 7.2.6.6.1
//
// class DecoderConfigDescriptor extends BaseDescriptor : bit(8) tag=DecoderConfigDescrTag {
// bit(8) objectTypeIndication;
// bit(6) streamType;
// bit(1) upStream;
// const bit(1) reserved=1;
// bit(24) bufferSizeDB;
// bit(32) maxBitrate;
// bit(32) avgBitrate;
// DecoderSpecificInfo decSpecificInfo[0 .. 1];
// profileLevelIndicationIndexDescriptor profileLevelIndicationIndexDescr [0..255];
// }
type DecoderConfigDescriptor struct {
ObjectType byte
StreamType byte
Expand All @@ -333,7 +348,7 @@ type DecoderConfigDescriptor struct {
AvgBitrate uint32
DecSpecificInfo *DecSpecificInfoDescriptor
OtherDescriptors []Descriptor
UnknownData []byte
UnknownData []byte // Data, probably erronous, that we don't understand
}

func exceedsMaxNrBytes(sizeFieldSizeMinus1 byte, size uint64, maxNrBytes int) bool {
Expand Down Expand Up @@ -464,6 +479,10 @@ func (d *DecoderConfigDescriptor) Info(w io.Writer, specificLevels, indent, inde
return bd.err
}

// DecSpecificInfoDescriptor is a generic DecoderSpecificInfoDescriptor.
//
// The meaning of the MPEG-4 audio descriptor is defined in ISO/IEC 14496-3 Section 1.6.2.1.

type DecSpecificInfoDescriptor struct {
sizeFieldSizeMinus1 byte
DecConfig []byte
Expand Down
20 changes: 20 additions & 0 deletions mp4/dref_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
package mp4

import (
"encoding/hex"
"testing"

"github.com/Eyevinn/mp4ff/bits"
)

const data = `000000326472656600000000000000010000002275726c200000000168747470733a2f2f666c7573736f6e69632e636f6d2f`

func TestDref(t *testing.T) {
dref := CreateDref()
boxDiffAfterEncodeAndDecode(t, dref)
}

func TestDrefDecode(t *testing.T) {
d, err := hex.DecodeString(data)
if err != nil {
t.Error(err)
}
sr := bits.NewFixedSliceReader(d)
box, err := DecodeBoxSR(0, sr)
if err != nil {
t.Error(err)
}
if box.Type() != "dref" {
t.Errorf("Expected 'dref', got %s", box.Type())
}
}
3 changes: 1 addition & 2 deletions mp4/testdata/golden_init_cenc_cmfv_dump.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
[vmhd] size=20 version=0 flags=000000
[dinf] size=36
[dref] size=28 version=0 flags=000000
[url ] size=12
- location: ""
[url ] size=12 version=0 flags=000001
[stbl] size=319
[stsd] size=243 version=0 flags=000000
[encv] size=227
Expand Down
3 changes: 1 addition & 2 deletions mp4/testdata/golden_init_prog_mp4_dump.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
[vmhd] size=20 version=0 flags=000001
[dinf] size=36
[dref] size=28 version=0 flags=000000
[url ] size=12
- location: ""
[url ] size=12 version=0 flags=000001
[stbl] size=4655
[stsd] size=183 version=0 flags=000000
[avc1] size=167
Expand Down
3 changes: 1 addition & 2 deletions mp4/testdata/golden_init_video_mp4_dump.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
[vmhd] size=20 version=0 flags=000001
[dinf] size=36
[dref] size=28 version=0 flags=000000
[url ] size=12
- location: ""
[url ] size=12 version=0 flags=000001
[stbl] size=237
[stsd] size=161 version=0 flags=000000
[avc3] size=145
Expand Down
54 changes: 36 additions & 18 deletions mp4/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
//
// Contained in : DrefBox (dref
type URLBox struct {
Version byte
Flags uint32
Location string // Zero-terminated string
Version byte
Flags uint32
Location string // Zero-terminated string
NoLocation bool
NoZeroTermination bool
}

const dataIsSelfContainedFlag = 0x000001
Expand All @@ -32,25 +34,32 @@ func DecodeURLBoxSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, e
versionAndFlags := sr.ReadUint32()
version := byte(versionAndFlags >> 24)
flags := versionAndFlags & flagsMask
location := ""
if flags != dataIsSelfContainedFlag {
location = sr.ReadZeroTerminatedString(hdr.payloadLen() - 4)
}

maxLen := hdr.payloadLen() - 4
b := URLBox{
Version: version,
Flags: flags,
Location: location,
Version: version,
Flags: flags,
NoLocation: true,
}
if maxLen > 0 {
b.NoLocation = false
var ok bool
b.Location, ok = sr.ReadPossiblyZeroTerminatedString(maxLen)
b.NoZeroTermination = !ok
if len(b.Location) == int(maxLen) {
b.NoZeroTermination = true
}
}
return &b, sr.AccError()
}

// CreateURLBox - Create a self-referencing URL box
func CreateURLBox() *URLBox {
return &URLBox{
Version: 0,
Flags: dataIsSelfContainedFlag,
Location: "",
Version: 0,
Flags: dataIsSelfContainedFlag,
Location: "",
NoLocation: true,
NoZeroTermination: false,
}
}

Expand All @@ -62,8 +71,11 @@ func (b *URLBox) Type() string {
// Size - return calculated size
func (b *URLBox) Size() uint64 {
size := uint64(boxHeaderSize + 4)
if b.Flags != uint32(dataIsSelfContainedFlag) {
if !b.NoLocation {
size += uint64(len(b.Location) + 1)
if b.NoZeroTermination {
size--
}
}
return size
}
Expand All @@ -87,15 +99,21 @@ func (b *URLBox) EncodeSW(sw bits.SliceWriter) error {
}
versionAndFlags := (uint32(b.Version) << 24) + b.Flags
sw.WriteUint32(versionAndFlags)
if b.Flags != dataIsSelfContainedFlag {
sw.WriteString(b.Location, true)
if !b.NoLocation {
sw.WriteString(b.Location, !b.NoZeroTermination)
}
return sw.AccError()
}

// Info - write specific box information
func (b *URLBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string) error {
bd := newInfoDumper(w, indent, b, -1, 0)
bd := newInfoDumper(w, indent, b, int(b.Version), b.Flags)
if b.NoLocation {
return bd.err
}
bd.write(" - location: %q", b.Location)
if b.NoZeroTermination {
bd.write(" - Warning: no zero termination")
}
return bd.err
}
65 changes: 65 additions & 0 deletions mp4/url_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package mp4

import (
"bytes"
"encoding/hex"
"testing"

"github.com/Eyevinn/mp4ff/bits"
)

func TestUrl(t *testing.T) {
Expand All @@ -14,3 +18,64 @@ func TestUrl(t *testing.T) {

boxDiffAfterEncodeAndDecode(t, urlBox)
}

func TestUrlDecode(t *testing.T) {
cases := []struct {
desc string
data string
wantedFlags uint32
wantedLocation string
NoLocation bool
NoZeroTermination bool
}{
{
desc: "self-contained, with location and zero termination",
data: `0000002375726c200000000168747470733a2f2f666c7573736f6e69632e636f6d2f00`,
wantedFlags: 0x00001,
wantedLocation: "",
NoLocation: false,
NoZeroTermination: false,
},
{
desc: "self-contained, with location but no zero termination",
data: `0000002275726c200000000168747470733a2f2f666c7573736f6e69632e636f6d2f`,
wantedFlags: 0x00001,
wantedLocation: "",
NoLocation: false,
NoZeroTermination: true,
},
{
desc: "self-contained, without location",
data: `0000000c75726c2000000001`,
wantedFlags: 0x00001,
wantedLocation: "",
NoLocation: false,
NoZeroTermination: true,
},
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
d, err := hex.DecodeString(c.data)
if err != nil {
t.Error(err)
}
sr := bits.NewFixedSliceReader(d)
box, err := DecodeBoxSR(0, sr)
if err != nil {
t.Error(err)
}
if box.Type() != "url " {
t.Errorf("Expected 'url ', got %s", box.Type())
}
urlBox := box.(*URLBox)
o := bytes.Buffer{}
err = urlBox.Encode(&o)
if err != nil {
t.Error(err)
}
if !bytes.Equal(d, o.Bytes()) {
t.Errorf("Encode mismatch: got %s, wanted %s", hex.EncodeToString(o.Bytes()), c.data)
}
})
}
}

0 comments on commit c0bae2c

Please sign in to comment.