Repository: Eyevinn/mp4ff
Pull request #351 (2024-05-10 08:46:26)

feat: handle bad descriptors and provide detailed info
As mentioned in #350, some mp4 samples in the wild have erroneous descriptors.

This PR extends the descriptor handling to become more general, but also to handle
and store data corresponding to bad descriptor data at the end of ES descriptor,
and DecoderConfig descriptor. This makes it possible to Decode and Encode such
assets without change of the data.

The Info() interface as used by mp4ff-info has been extended to reveal more information about encoders.
In particular, a higher level for `esds` boxes propagates into descriptors.

This should solve #350 and #348.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a28437..e42ff04 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,11 +15,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Fixed
 
 - support for parsing of hierarchical sidx boxes
+- handling of partially bad descriptors
+
+### Changed
+
+- Info (mp4ff-info) output for esds boxes
+- API of descriptors
 
 ### Added
 
 - support for ssix box
 - support for leva box
+- details for descriptors as Info outout
 
 ## [0.44.0] - 2024-04-19
 
diff --git a/mp4/descriptors.go b/mp4/descriptors.go
index 4a0d54c..ffbdca5 100644
--- a/mp4/descriptors.go
+++ b/mp4/descriptors.go
@@ -1,7 +1,9 @@
 package mp4
 
 import (
+	"encoding/hex"
 	"fmt"
+	"io"
 
 	"github.com/Eyevinn/mp4ff/bits"
 )
@@ -22,16 +24,41 @@ const (
 	minimalEsDescrSize = 25
 )
 
+func TagType(tag byte) string {
+	switch tag {
+	case ObjectDescrTag:
+		return "tag=1 Object"
+	case InitialObjectDescrTag:
+		return "tag=2 InitialObject"
+	case ES_DescrTag:
+		return "tag=3 ES"
+	case DecoderConfigDescrTag:
+		return "tag=4 DecoderConfig"
+	case DecSpecificInfoTag:
+		return "tag=5 DecoderSpecificInfo"
+	case SLConfigDescrTag:
+		return "tag=6 SLConfig"
+	default:
+		return fmt.Sprintf("tag=%d Unknown", tag)
+	}
+}
+
 type Descriptor interface {
 	// Tag - descriptor tag. Fixed for each descriptor type
 	Tag() byte
+	// Type is string describing Tag, making descriptor fullfill boxLike interface
+	Type() string
 	// Size - size of descriptor, excluding tag byte and size field
-	Size() uint32
+	Size() uint64
 	// SizeSize - size of descriptor including tag byte and size field
-	SizeSize() uint32
+	SizeSize() uint64
 	// EncodeSW - Write descriptor to slice writer
 	EncodeSW(sw bits.SliceWriter) error
-	// Info - provide information about descriptor
+	// Info - write information about descriptor
+	//   Higher levels give more details. 0 is default
+	//   indent is indent at this box level.
+	//   indentStep is how much to indent at each level
+	Info(w io.Writer, specificLevels, indent, indentStep string) error
 }
 
 /*
@@ -74,14 +101,36 @@ type ESDescriptor struct {
 	FlagsAndPriority    byte
 	sizeFieldSizeMinus1 byte
 	URLString           string
-	DecConfigDescriptor DecoderConfigDescriptor
-	SLConfigDescriptor  SLConfigDescriptor
-	OtherDescriptors    []RawDescriptor
+	DecConfigDescriptor *DecoderConfigDescriptor
+	SLConfigDescriptor  *SLConfigDescriptor
+	OtherDescriptors    []Descriptor
+	UnknownData         []byte // Data, probably erronous, that we don't understand
+}
+
+func DecodeDescriptor(sr bits.SliceReader, maxNrBytes int) (Descriptor, error) {
+	if maxNrBytes < 2 {
+		return nil, fmt.Errorf("descriptor size %d too small", maxNrBytes)
+	}
+	tag := sr.ReadUint8()
+	if sr.AccError() != nil {
+		return nil, sr.AccError()
+	}
+	switch tag {
+	case ES_DescrTag:
+		return nil, fmt.Errorf("use DecodeESDescriptor instead")
+	case DecoderConfigDescrTag:
+		return DecodeDecoderConfigDescriptor(tag, sr, maxNrBytes)
+	case DecSpecificInfoTag:
+		return DecodeDecSpecificInfoDescriptor(tag, sr, maxNrBytes)
+	case SLConfigDescrTag:
+		return DecodeSLConfigDescriptor(tag, sr, maxNrBytes)
+	default:
+		return DecodeRawDescriptor(tag, sr, maxNrBytes)
+	}
 }
 
 func DecodeESDescriptor(sr bits.SliceReader, descSize uint32) (ESDescriptor, error) {
 	ed := ESDescriptor{}
-	srStart := sr.GetPos()
 	tag := sr.ReadUint8()
 	if tag != ES_DescrTag {
 		return ed, fmt.Errorf("got tag %d instead of ESDescriptorTag %d", tag, ES_DescrTag)
@@ -92,6 +141,7 @@ func DecodeESDescriptor(sr bits.SliceReader, descSize uint32) (ESDescriptor, err
 		return ed, err
 	}
 	ed.sizeFieldSizeMinus1 = sizeFieldSizeMinus1
+	dataStart := sr.GetPos()
 	ed.EsID = sr.ReadUint16()
 	ed.FlagsAndPriority = sr.ReadUint8()
 	streamDependenceFlag := ed.FlagsAndPriority >> 7
@@ -109,32 +159,49 @@ func DecodeESDescriptor(sr bits.SliceReader, descSize uint32) (ESDescriptor, err
 	if ocrStreamFlag == 1 {
 		ed.OCResID = sr.ReadUint16()
 	}
-	ed.DecConfigDescriptor, err = DecodeDecoderConfigDescriptor(sr)
+	currPos := sr.GetPos()
+	nrBytesLeft := int(size) - (currPos - dataStart)
+	desc, err := DecodeDescriptor(sr, nrBytesLeft)
 	if err != nil {
 		return ed, err
 	}
-	ed.SLConfigDescriptor, err = DecodeSLConfigDescriptor(sr)
+	var ok bool
+	ed.DecConfigDescriptor, ok = desc.(*DecoderConfigDescriptor)
+	if !ok {
+		return ed, fmt.Errorf("expected DecoderConfigDescriptor")
+	}
+	currPos = sr.GetPos()
+	nrBytesLeft = int(size) - (currPos - dataStart)
+	desc, err = DecodeDescriptor(sr, nrBytesLeft)
 	if err != nil {
-		return ed, err
+		sr.SetPos(currPos)
+		ed.UnknownData = sr.ReadBytes(nrBytesLeft)
+		return ed, nil
+	}
+	ed.SLConfigDescriptor, ok = desc.(*SLConfigDescriptor)
+	if !ok {
+		ed.OtherDescriptors = append(ed.OtherDescriptors, desc)
 	}
 	for {
-		nrBytesLeft := int(descSize) - (sr.GetPos() - srStart)
+		currPos = sr.GetPos()
+		nrBytesLeft := int(size) - (currPos - dataStart)
 		if nrBytesLeft == 0 {
 			break
 		}
 		if nrBytesLeft < 0 {
 			return ed, fmt.Errorf("read too far in ESDescriptor")
 		}
-		desc, err := DecodeRawDescriptor(sr)
+		desc, err := DecodeDescriptor(sr, nrBytesLeft)
 		if err != nil {
-			return ed, err
+			sr.SetPos(currPos)
+			ed.UnknownData = sr.ReadBytes(nrBytesLeft)
+			return ed, nil
 		}
 		ed.OtherDescriptors = append(ed.OtherDescriptors, desc)
 	}
 	if size != ed.Size() {
 		return ed, fmt.Errorf("read size %d differs from calculated size %d", size, ed.Size())
 	}
-
 	return ed, sr.AccError()
 }
 
@@ -142,8 +209,12 @@ func (e *ESDescriptor) Tag() byte {
 	return ES_DescrTag
 }
 
-func (e *ESDescriptor) Size() uint32 {
-	var size uint32 = 2 + 1
+func (e *ESDescriptor) Type() string {
+	return TagType(e.Tag())
+}
+
+func (e *ESDescriptor) Size() uint64 {
+	var size uint64 = 2 + 1
 	streamDependenceFlag := e.FlagsAndPriority >> 7
 	urlFlag := (e.FlagsAndPriority >> 6) & 0x1
 	ocrStreamFlag := (e.FlagsAndPriority >> 5) & 0x1
@@ -151,21 +222,24 @@ func (e *ESDescriptor) Size() uint32 {
 		size += 2
 	}
 	if urlFlag == 1 {
-		size += 1 + uint32(len(e.URLString))
+		size += 1 + uint64(len(e.URLString))
 	}
 	if ocrStreamFlag == 1 {
 		size += 2
 	}
 	size += e.DecConfigDescriptor.SizeSize()
-	size += e.SLConfigDescriptor.SizeSize()
+	if e.SLConfigDescriptor != nil {
+		size += e.SLConfigDescriptor.SizeSize()
+	}
 	for _, od := range e.OtherDescriptors {
 		size += od.SizeSize()
 	}
+	size += uint64(len(e.UnknownData))
 	return size
 }
 
-func (e *ESDescriptor) SizeSize() uint32 {
-	return 1 + uint32(e.sizeFieldSizeMinus1) + 1 + e.Size()
+func (e *ESDescriptor) SizeSize() uint64 {
+	return 1 + uint64(e.sizeFieldSizeMinus1) + 1 + e.Size()
 }
 
 func (e *ESDescriptor) EncodeSW(sw bits.SliceWriter) error {
@@ -187,19 +261,69 @@ func (e *ESDescriptor) EncodeSW(sw bits.SliceWriter) error {
 	if ocrStreamFlag == 1 {
 		sw.WriteUint16(e.OCResID)
 	}
-
+	if e.DecConfigDescriptor == nil {
+		return fmt.Errorf("missing DecoderConfigDescriptor")
+	}
 	err := e.DecConfigDescriptor.EncodeSW(sw)
 	if err != nil {
 		return err
 	}
-
-	err = e.SLConfigDescriptor.EncodeSW(sw)
-	if err != nil {
-		return err
+	if e.SLConfigDescriptor != nil {
+		err = e.SLConfigDescriptor.EncodeSW(sw)
+		if err != nil {
+			return err
+		}
+	}
+	for _, od := range e.OtherDescriptors {
+		err = od.EncodeSW(sw)
+		if err != nil {
+			return err
+		}
+	}
+	if len(e.UnknownData) > 0 {
+		sw.WriteBytes(e.UnknownData)
 	}
 	return sw.AccError()
 }
 
+func (e *ESDescriptor) Info(w io.Writer, specificLevels, indent, indentStep string) error {
+	bd := newInfoDumper(w, indent, e, infoVersionDescriptor, 0)
+	level := getInfoLevel(e, specificLevels)
+	if level > 0 {
+		bd.write(" - EsID: %d", e.EsID)
+		bd.write(" - DependsOnEsID: %d", e.DependsOnEsID)
+		bd.write(" - OCResID: %d", e.OCResID)
+		bd.write(" - FlagsAndPriority: %d", e.FlagsAndPriority)
+		bd.write(" - URLString: %s", e.URLString)
+	}
+	if e.DecConfigDescriptor != nil {
+		err := e.DecConfigDescriptor.Info(w, specificLevels, indent+indentStep, indentStep)
+		if err != nil {
+			return err
+		}
+	} else {
+		bd.write(" - Missing DecoderConfigDescriptor")
+	}
+	if e.SLConfigDescriptor != nil {
+		err := e.SLConfigDescriptor.Info(w, specificLevels, indent+indentStep, indentStep)
+		if err != nil {
+			return err
+		}
+	} else {
+		bd.write(" - Missing SLConfigDescriptor")
+	}
+	for _, od := range e.OtherDescriptors {
+		err := od.Info(w, specificLevels, indent+indentStep, indentStep)
+		if err != nil {
+			return err
+		}
+	}
+	if len(e.UnknownData) > 0 {
+		bd.write(" - UnknownData (%dB): %s", len(e.UnknownData), hex.EncodeToString(e.UnknownData))
+	}
+	return bd.err
+}
+
 type DecoderConfigDescriptor struct {
 	ObjectType          byte
 	StreamType          byte
@@ -207,21 +331,29 @@ type DecoderConfigDescriptor struct {
 	BufferSizeDB        uint32
 	MaxBitrate          uint32
 	AvgBitrate          uint32
-	DecSpecificInfo     DecSpecificInfoDescriptor
+	DecSpecificInfo     *DecSpecificInfoDescriptor
+	OtherDescriptors    []Descriptor
+	UnknownData         []byte
 }
 
-func DecodeDecoderConfigDescriptor(sr bits.SliceReader) (DecoderConfigDescriptor, error) {
+func exceedsMaxNrBytes(sizeFieldSizeMinus1 byte, size uint64, maxNrBytes int) bool {
+	return 1+uint64(sizeFieldSizeMinus1)+1+size > uint64(maxNrBytes)
+}
+
+func DecodeDecoderConfigDescriptor(tag byte, sr bits.SliceReader, maxNrBytes int) (Descriptor, error) {
 	dd := DecoderConfigDescriptor{}
-	tag := sr.ReadUint8()
 	if tag != DecoderConfigDescrTag {
-		return dd, fmt.Errorf("got tag %d instead of DecoderConfigDescrTag %d", tag, DecoderConfigDescrTag)
+		return nil, fmt.Errorf("got tag %d instead of DecoderConfigDescrTag %d", tag, DecoderConfigDescrTag)
 	}
-
 	sizeFieldSizeMinus1, size, err := readSizeSize(sr)
 	if err != nil {
-		return dd, err
+		return nil, err
 	}
 	dd.sizeFieldSizeMinus1 = sizeFieldSizeMinus1
+	if exceedsMaxNrBytes(sizeFieldSizeMinus1, size, maxNrBytes) {
+		return nil, fmt.Errorf("DecoderConfigDescriptor size %d exceeds maxNrBytes %d", size, maxNrBytes)
+	}
+	dataStart := sr.GetPos()
 	dd.ObjectType = sr.ReadUint8()
 
 	streamTypeAndBufferSizeDB := sr.ReadUint32()
@@ -229,26 +361,57 @@ func DecodeDecoderConfigDescriptor(sr bits.SliceReader) (DecoderConfigDescriptor
 	dd.BufferSizeDB = streamTypeAndBufferSizeDB & 0xffffff
 	dd.MaxBitrate = sr.ReadUint32()
 	dd.AvgBitrate = sr.ReadUint32()
-	dd.DecSpecificInfo, err = DecodeDecSpecificInfoDescriptor(sr)
+
+	currPos := sr.GetPos()
+	nrBytesLeft := int(size) - (currPos - dataStart)
+	desc, err := DecodeDescriptor(sr, nrBytesLeft)
 	if err != nil {
-		return dd, err
+		return nil, fmt.Errorf("failed to decode DecSpecificInfoDescriptor: %w", err)
 	}
-	if size != dd.Size() {
-		return dd, fmt.Errorf("read size %d differs from calculated size %d", size, dd.Size())
+	var ok bool
+	dd.DecSpecificInfo, ok = desc.(*DecSpecificInfoDescriptor)
+	if !ok {
+		return nil, fmt.Errorf("expected DecSpecificInfoDescriptor")
 	}
-	return dd, nil
+	for {
+		currPos := sr.GetPos()
+		nrBytesLeft := int(size) - (currPos - dataStart)
+		if nrBytesLeft == 0 {
+			break
+		}
+		if nrBytesLeft < 0 {
+			return nil, fmt.Errorf("read too far in DecoderConfigDescriptor")
+		}
+		desc, err := DecodeDescriptor(sr, nrBytesLeft)
+		if err != nil {
+			sr.SetPos(currPos)
+			dd.UnknownData = sr.ReadBytes(nrBytesLeft)
+			return &dd, nil
+		}
+		dd.OtherDescriptors = append(dd.OtherDescriptors, desc)
+	}
+	return &dd, nil
 }
 
 func (d *DecoderConfigDescriptor) Tag() byte {
 	return DecoderConfigDescrTag
 }
 
-func (d *DecoderConfigDescriptor) Size() uint32 {
-	return 13 + d.DecSpecificInfo.SizeSize()
+func (d *DecoderConfigDescriptor) Type() string {
+	return TagType(d.Tag())
 }
 
-func (d *DecoderConfigDescriptor) SizeSize() uint32 {
-	return 1 + uint32(d.sizeFieldSizeMinus1) + 1 + d.Size()
+func (d *DecoderConfigDescriptor) Size() uint64 {
+	size := 13 + d.DecSpecificInfo.SizeSize()
+	for _, od := range d.OtherDescriptors {
+		size += od.SizeSize()
+	}
+	size += uint64(len(d.UnknownData))
+	return uint64(size)
+}
+
+func (d *DecoderConfigDescriptor) SizeSize() uint64 {
+	return 1 + uint64(d.sizeFieldSizeMinus1) + 1 + d.Size()
 }
 
 func (d *DecoderConfigDescriptor) EncodeSW(sw bits.SliceWriter) error {
@@ -263,40 +426,87 @@ func (d *DecoderConfigDescriptor) EncodeSW(sw bits.SliceWriter) error {
 	if err != nil {
 		return err
 	}
+	for _, desc := range d.OtherDescriptors {
+		err = desc.EncodeSW(sw)
+		if err != nil {
+			return err
+		}
+	}
+	if len(d.UnknownData) > 0 {
+		sw.WriteBytes(d.UnknownData)
+	}
 	return sw.AccError()
 }
 
+func (d *DecoderConfigDescriptor) Info(w io.Writer, specificLevels, indent, indentStep string) error {
+	bd := newInfoDumper(w, indent, d, infoVersionDescriptor, 0)
+	level := getInfoLevel(d, specificLevels)
+	if level > 0 {
+		bd.write(" - ObjectType: %d", d.ObjectType)
+		bd.write(" - StreamType: %d", d.StreamType)
+	}
+	bd.write(" - BufferSizeDB: %d", d.BufferSizeDB)
+	bd.write(" - MaxBitrate: %d", d.MaxBitrate)
+	bd.write(" - AvgBitrate: %d", d.AvgBitrate)
+	err := d.DecSpecificInfo.Info(w, specificLevels, indent+indentStep, indentStep)
+	if err != nil {
+		return err
+	}
+	for _, od := range d.OtherDescriptors {
+		err = od.Info(w, specificLevels, indent+indentStep, indentStep)
+		if err != nil {
+			return err
+		}
+	}
+	if len(d.UnknownData) > 0 {
+		bd.write(" - UnknownData (%dB): %s", len(d.UnknownData), hex.EncodeToString(d.UnknownData))
+	}
+	return bd.err
+}
+
 type DecSpecificInfoDescriptor struct {
 	sizeFieldSizeMinus1 byte
 	DecConfig           []byte
 }
 
-func DecodeDecSpecificInfoDescriptor(sr bits.SliceReader) (DecSpecificInfoDescriptor, error) {
+func DecodeDecSpecificInfoDescriptor(tag byte, sr bits.SliceReader, maxNrBytes int) (Descriptor, error) {
 	dd := DecSpecificInfoDescriptor{}
-	tag := sr.ReadUint8()
 	if tag != DecSpecificInfoTag {
-		return dd, fmt.Errorf("got tag %d instead of DecSpecificInfoTag %d", tag, DecSpecificInfoTag)
+		return nil, fmt.Errorf("got tag %d instead of DecSpecificInfoTag %d", tag, DecSpecificInfoTag)
 	}
 
 	sizeFieldSizeMinus1, size, err := readSizeSize(sr)
 	if err != nil {
-		return dd, err
+		return nil, err
+	}
+	if exceedsMaxNrBytes(sizeFieldSizeMinus1, size, maxNrBytes) {
+		return nil, fmt.Errorf("DecSpecificInfoDescriptor size %d exceeds maxNrBytes %d", size, maxNrBytes)
 	}
 	dd.sizeFieldSizeMinus1 = sizeFieldSizeMinus1
+
+	dataStart := sr.GetPos()
 	dd.DecConfig = sr.ReadBytes(int(size))
-	return dd, sr.AccError()
+	bytesLeft := int(size) - (sr.GetPos() - dataStart)
+	if bytesLeft > 0 {
+		return nil, fmt.Errorf("DecSpecificInfoDescriptor has %d bytes left", bytesLeft)
+	}
+	return &dd, sr.AccError()
 }
 
 func (d *DecSpecificInfoDescriptor) Tag() byte {
 	return DecSpecificInfoTag
 }
 
-func (d *DecSpecificInfoDescriptor) Size() uint32 {
-	return uint32(len(d.DecConfig))
+func (d *DecSpecificInfoDescriptor) Type() string {
+	return TagType(d.Tag())
+}
+
+func (d *DecSpecificInfoDescriptor) Size() uint64 {
+	return uint64(len(d.DecConfig))
 }
 
-func (d *DecSpecificInfoDescriptor) SizeSize() uint32 {
-	return 1 + uint32(d.sizeFieldSizeMinus1) + 1 + d.Size()
+func (d *DecSpecificInfoDescriptor) SizeSize() uint64 {
+	return 1 + uint64(d.sizeFieldSizeMinus1) + 1 + d.Size()
 }
 
 func (d *DecSpecificInfoDescriptor) EncodeSW(sw bits.SliceWriter) error {
@@ -306,40 +516,53 @@ func (d *DecSpecificInfoDescriptor) EncodeSW(sw bits.SliceWriter) error {
 	return sw.AccError()
 }
 
+func (d *DecSpecificInfoDescriptor) Info(w io.Writer, specificLevels, indent, indentStep string) error {
+	bd := newInfoDumper(w, indent, d, infoVersionDescriptor, 0)
+	bd.write(" - DecConfig (%dB): %s", len(d.DecConfig), hex.EncodeToString(d.DecConfig))
+	return bd.err
+}
+
 type SLConfigDescriptor struct {
 	sizeFieldSizeMinus1 byte
 	ConfigValue         byte
 	MoreData            []byte
 }
 
-func DecodeSLConfigDescriptor(sr bits.SliceReader) (SLConfigDescriptor, error) {
+func DecodeSLConfigDescriptor(tag byte, sr bits.SliceReader, maxNrBytes int) (Descriptor, error) {
 	d := SLConfigDescriptor{}
-	tag := sr.ReadUint8()
 	if tag != SLConfigDescrTag {
-		return d, fmt.Errorf("got tag %d instead of SLConfigDescrTag %d", tag, SLConfigDescrTag)
+		return nil, fmt.Errorf("got tag %d instead of SLConfigDescrTag %d", tag, SLConfigDescrTag)
 	}
 	sizeFieldSizeMinus1, size, err := readSizeSize(sr)
 	if err != nil {
-		return d, err
+		return nil, err
+	}
+	if exceedsMaxNrBytes(sizeFieldSizeMinus1, size, maxNrBytes) {
+		return nil, fmt.Errorf("DecodeSLConfigDescriptor size %d exceeds maxNrBytes %d", size, maxNrBytes)
 	}
 	d.sizeFieldSizeMinus1 = sizeFieldSizeMinus1
+
 	d.ConfigValue = sr.ReadUint8()
 	if size > 1 {
 		d.MoreData = sr.ReadBytes(int(size - 1))
 	}
-	return d, sr.AccError()
+	return &d, sr.AccError()
 }
 
 func (d *SLConfigDescriptor) Tag() byte {
 	return SLConfigDescrTag
 }
 
-func (d *SLConfigDescriptor) Size() uint32 {
-	return uint32(1 + len(d.MoreData))
+func (d *SLConfigDescriptor) Type() string {
+	return TagType(d.Tag())
+}
+
+func (d *SLConfigDescriptor) Size() uint64 {
+	return uint64(1 + len(d.MoreData))
 }
 
-func (d *SLConfigDescriptor) SizeSize() uint32 {
-	return 1 + uint32(d.sizeFieldSizeMinus1) + 1 + d.Size()
+func (d *SLConfigDescriptor) SizeSize() uint64 {
+	return 1 + uint64(d.sizeFieldSizeMinus1) + 1 + d.Size()
 }
 
 func (d *SLConfigDescriptor) EncodeSW(sw bits.SliceWriter) error {
@@ -352,6 +575,18 @@ func (d *SLConfigDescriptor) EncodeSW(sw bits.SliceWriter) error {
 	return sw.AccError()
 }
 
+func (d *SLConfigDescriptor) Info(w io.Writer, specificLevels, indent, indentStep string) error {
+	bd := newInfoDumper(w, indent, d, infoVersionDescriptor, 0)
+	level := getInfoLevel(d, specificLevels)
+	if level > 0 {
+		bd.write(" - ConfigValue: %d", d.ConfigValue)
+		if len(d.MoreData) > 0 {
+			bd.write(" - MoreData: (%dB) %s", len(d.MoreData), hex.EncodeToString(d.MoreData))
+		}
+	}
+	return bd.err
+}
+
 // RawDescriptor - raw representation of any descriptor
 type RawDescriptor struct {
 	tag                 byte
@@ -359,19 +594,20 @@ type RawDescriptor struct {
 	data                []byte
 }
 
-func DecodeRawDescriptor(sr bits.SliceReader) (RawDescriptor, error) {
-	tag := sr.ReadUint8()
+func DecodeRawDescriptor(tag byte, sr bits.SliceReader, maxNrBytes int) (Descriptor, error) {
 	sizeFieldSizeMinus1, size, err := readSizeSize(sr)
+	if err != nil {
+		return nil, err
+	}
+	if exceedsMaxNrBytes(sizeFieldSizeMinus1, size, maxNrBytes) {
+		return nil, fmt.Errorf("DecRawDescriptor size %d exceeds maxNrBytes %d", size, maxNrBytes)
+	}
 	d := RawDescriptor{
 		tag:                 tag,
 		sizeFieldSizeMinus1: sizeFieldSizeMinus1,
 	}
-	if err != nil {
-		return d, err
-	}
-
 	d.data = sr.ReadBytes(int(size))
-	return d, sr.AccError()
+	return &d, sr.AccError()
 }
 
 func CreateRawDescriptor(tag, sizeFieldSizeMinus1 byte, data []byte) (RawDescriptor, error) {
@@ -385,12 +621,16 @@ func (s *RawDescriptor) Tag() byte {
 	return s.tag
 }
 
-func (d *RawDescriptor) Size() uint32 {
-	return uint32(len(d.data))
+func (d *RawDescriptor) Type() string {
+	return TagType(d.Tag())
+}
+
+func (d *RawDescriptor) Size() uint64 {
+	return uint64(len(d.data))
 }
 
-func (d *RawDescriptor) SizeSize() uint32 {
-	return 1 + uint32(d.sizeFieldSizeMinus1) + 1 + uint32(len(d.data))
+func (d *RawDescriptor) SizeSize() uint64 {
+	return 1 + uint64(d.sizeFieldSizeMinus1) + 1 + d.Size()
 }
 
 func (d *RawDescriptor) EncodeSW(sw bits.SliceWriter) error {
@@ -400,17 +640,27 @@ func (d *RawDescriptor) EncodeSW(sw bits.SliceWriter) error {
 	return sw.AccError()
 }
 
+func (d *RawDescriptor) Info(w io.Writer, specificLevels, indent, indentStep string) error {
+	bd := newInfoDumper(w, indent, d, infoVersionDescriptor, 0)
+	level := getInfoLevel(d, specificLevels)
+	if level > 0 {
+		bd.write(" - data (%dB): %s", len(d.data), hex.EncodeToString(d.data))
+	}
+	return bd.err
+}
+
+// CreateESDescriptor creats an ESDescriptor with a DecoderConfigDescriptor for audio.
 func CreateESDescriptor(decConfig []byte) ESDescriptor {
 	e := ESDescriptor{
 		EsID: 0x01,
-		DecConfigDescriptor: DecoderConfigDescriptor{
+		DecConfigDescriptor: &DecoderConfigDescriptor{
 			ObjectType: 0x40, // Audio ISO/IEC 14496-3,
 			StreamType: 0x15, // 0x5 << 2 + 0x01 (audioType + upstreamFlag + reserved)
-			DecSpecificInfo: DecSpecificInfoDescriptor{
+			DecSpecificInfo: &DecSpecificInfoDescriptor{
 				DecConfig: decConfig,
 			},
 		},
-		SLConfigDescriptor: SLConfigDescriptor{
+		SLConfigDescriptor: &SLConfigDescriptor{
 			ConfigValue: 0x02,
 		},
 	}
@@ -419,22 +669,22 @@ func CreateESDescriptor(decConfig []byte) ESDescriptor {
 
 // readTagAndSize - get size by accumulate 7 bits from each byte. MSB = 1 indicates more bytes.
 // Defined in ISO 14496-1 Section 8.3.3
-func readSizeSize(sr bits.SliceReader) (sizeFieldSizeMinus1 byte, size uint32, err error) {
+func readSizeSize(sr bits.SliceReader) (sizeFieldSizeMinus1 byte, size uint64, err error) {
 	tmp := sr.ReadUint8()
-	sizeOfInstance := uint32(tmp & 0x7f)
+	sizeOfInstance := uint64(tmp & 0x7f)
 	for {
 		if (tmp >> 7) == 0 {
 			break // Last byte of size field
 		}
 		tmp = sr.ReadUint8()
 		sizeFieldSizeMinus1++
-		sizeOfInstance = sizeOfInstance<<7 | uint32(tmp&0x7f)
+		sizeOfInstance = sizeOfInstance<<7 | uint64(tmp&0x7f)
 	}
 	return sizeFieldSizeMinus1, sizeOfInstance, sr.AccError()
 }
 
 // writeDescriptorSize - write descriptor size 7-bit at a time in as many bytes as prescribed
-func writeDescriptorSize(sw bits.SliceWriter, size uint32, sizeFieldSizeMinus1 byte) {
+func writeDescriptorSize(sw bits.SliceWriter, size uint64, sizeFieldSizeMinus1 byte) {
 	for pos := int(sizeFieldSizeMinus1); pos >= 0; pos-- {
 		value := byte(size>>uint32(7*pos)) & 0x7f
 		if pos > 0 {
diff --git a/mp4/esds.go b/mp4/esds.go
index 8e4d348..d1eced5 100644
--- a/mp4/esds.go
+++ b/mp4/esds.go
@@ -1,7 +1,6 @@
 package mp4
 
 import (
-	"encoding/hex"
 	"fmt"
 	"io"
 
@@ -94,8 +93,9 @@ func (e *EsdsBox) EncodeSW(sw bits.SliceWriter) error {
 // Info - write box-specific information
 func (e *EsdsBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string) error {
 	bd := newInfoDumper(w, indent, e, int(e.Version), e.Flags)
-	bd.write(" - maxBitrate: %d", e.DecConfigDescriptor.MaxBitrate)
-	bd.write(" - avgBitrate: %d", e.DecConfigDescriptor.AvgBitrate)
-	bd.write(" - decConfig: %s", hex.EncodeToString(e.DecConfigDescriptor.DecSpecificInfo.DecConfig))
+	err := e.ESDescriptor.Info(bd.w, specificBoxLevels, indent+indentStep, indentStep)
+	if err != nil {
+		return err
+	}
 	return bd.err
 }
diff --git a/mp4/infodumper.go b/mp4/infodumper.go
index ca44c99..e93bd46 100644
--- a/mp4/infodumper.go
+++ b/mp4/infodumper.go
@@ -7,6 +7,12 @@ import (
 	"strings"
 )
 
+const (
+	infoVersionNone         = -1
+	infoVersionGroupingType = -2
+	infoVersionDescriptor   = -3
+)
+
 type boxLike interface {
 	Type() string
 	Size() uint64
@@ -33,17 +39,24 @@ func fixStartingCopyrightChar(boxType string) string {
 }
 
 // newInfoDumper - make an infoDumper with indent
+// write version if >= 0
 // set Version to -1 if not present for box
 // set Version to -2 for sample group entries
+// set Version to -3 for descriptors
 func newInfoDumper(w io.Writer, indent string, b boxLike, version int, flags uint32) *infoDumper {
 	bd := infoDumper{w, indent, b, nil}
 	utf8BoxType := fixStartingCopyrightChar(b.Type())
-	if version == -1 {
-		bd.write("[%s] size=%d", utf8BoxType, b.Size())
-	} else if version >= 0 {
+	switch {
+	case version >= 0:
 		bd.write("[%s] size=%d version=%d flags=%06x", utf8BoxType, b.Size(), version, flags)
-	} else { // version = -2
+	case version == infoVersionNone:
+		bd.write("[%s] size=%d", utf8BoxType, b.Size())
+	case version == infoVersionGroupingType:
 		bd.write("GroupingType %q size=%d", utf8BoxType, b.Size())
+	case version == infoVersionDescriptor:
+		bd.write("Descriptor %q size=2+%d", b.Type(), b.Size())
+	default:
+		bd.write("Unknown version %d", version)
 	}
 	return &bd
 }
@@ -68,6 +81,9 @@ func getInfoLevel(b boxLike, specificBoxLevels string) (level int) {
 	}
 	boxesLevels := strings.Split(specificBoxLevels, ",")
 	boxType := b.Type()
+	if _, ok := b.(Descriptor); ok {
+		boxType = "esds"
+	}
 	var err error
 	for _, bl := range boxesLevels {
 		splitPos := strings.Index(bl, ":")


Test patch:
diff --git a/mp4/descriptors_test.go b/mp4/descriptors_test.go
index 5d51689..8fb3f90 100644
--- a/mp4/descriptors_test.go
+++ b/mp4/descriptors_test.go
@@ -3,32 +3,124 @@ package mp4
 import (
 	"bytes"
 	"encoding/hex"
+	"strings"
 	"testing"
 
 	"github.com/Eyevinn/mp4ff/bits"
 )
 
+const badSizeDescriptor = `031900010004134015000000000000000001f40005021190060102`
+const missingSLConfig = `031600010004114015000000000000000001d40005021190`
+const partOfEsdsProgIn = `03808080250002000480808017401500000000010d88000003f80580808005128856e500068080800102`
+
 func TestDecodeDescriptor(t *testing.T) {
-	esDesc, err := hex.DecodeString(esdsProgIn[24:])
-	if err != nil {
-		t.Error(err)
-	}
-	sr := bits.NewFixedSliceReader(esDesc)
-	desc, err := DecodeESDescriptor(sr, uint32(len(esDesc)))
-	if err != nil {
-		t.Error(err)
+	cases := []struct {
+		desc string
+		data string
+	}{
+		{"badSizeDescriptor", badSizeDescriptor},
+		{"missingSLConfig", missingSLConfig},
+		{"partOfEsdsProgIn", partOfEsdsProgIn},
 	}
-	if desc.Tag() != ES_DescrTag {
-		t.Error("tag is not 3")
+	for _, c := range cases {
+		t.Run(c.desc, func(t *testing.T) {
+			data, err := hex.DecodeString(c.data)
+			if err != nil {
+				t.Error(err)
+			}
+			sr := bits.NewFixedSliceReader(data)
+			desc, err := DecodeESDescriptor(sr, uint32(len(data)))
+			if err != nil {
+				t.Error(err)
+			}
+			if desc.Tag() != ES_DescrTag {
+				t.Error("tag is not 3")
+			}
+			out := make([]byte, len(data))
+			sw := bits.NewFixedSliceWriterFromSlice(out)
+			err = desc.EncodeSW(sw)
+			if err != nil {
+				t.Error(err)
+			}
+			if !bytes.Equal(sw.Bytes(), data) {
+				t.Errorf("written es descriptor differs from read\n%s\n%s",
+					hex.EncodeToString(sw.Bytes()), hex.EncodeToString(data))
+			}
+		})
 	}
-	out := make([]byte, len(esDesc))
-	sw := bits.NewFixedSliceWriterFromSlice(out)
-	err = desc.EncodeSW(sw)
-	if err != nil {
-		t.Error(err)
+}
+
+func TestDescriptorInfo(t *testing.T) {
+	cases := []struct {
+		desc       string
+		data       string
+		wantedInfo string
+	}{
+		{"badSizeDescriptor", badSizeDescriptor,
+			`Descriptor "tag=3 ES" size=2+25
+			- EsID: 1
+			- DependsOnEsID: 0
+			- OCResID: 0
+			- FlagsAndPriority: 0
+			- URLString:
+			 Descriptor "tag=4 DecoderConfig" size=2+19
+			  - ObjectType: 64
+			  - StreamType: 21
+			  - BufferSizeDB: 0
+			  - MaxBitrate: 0
+			  - AvgBitrate: 128000
+			   Descriptor "tag=5 DecoderSpecificInfo" size=2+2
+				- DecConfig (2B): 1190
+			  - UnknownData (2B): 0601
+			- Missing SLConfigDescriptor
+			- UnknownData (1B): 02
+			`},
+		{"missingSLConfig", missingSLConfig,
+			`Descriptor "tag=3 ES" size=2+22
+		- EsID: 1
+		- DependsOnEsID: 0
+		- OCResID: 0
+		- FlagsAndPriority: 0
+		- URLString:
+		 Descriptor "tag=4 DecoderConfig" size=2+17
+		  - ObjectType: 64
+		  - StreamType: 21
+		  - BufferSizeDB: 0
+		  - MaxBitrate: 0
+		  - AvgBitrate: 119808
+		   Descriptor "tag=5 DecoderSpecificInfo" size=2+2
+			- DecConfig (2B): 1190
+		- Missing SLConfigDescriptor
+		`},
 	}
-	if !bytes.Equal(sw.Bytes(), esDesc) {
-		t.Errorf("written es descriptor differs from read\n%s\n%s",
-			hex.EncodeToString(sw.Bytes()), hex.EncodeToString(esDesc))
+	for _, c := range cases {
+		t.Run(c.desc, func(t *testing.T) {
+			data, err := hex.DecodeString(c.data)
+			if err != nil {
+				t.Error(err)
+			}
+			sr := bits.NewFixedSliceReader(data)
+			desc, err := DecodeESDescriptor(sr, uint32(len(data)))
+			if err != nil {
+				t.Error(err)
+			}
+			buf := bytes.Buffer{}
+			err = desc.Info(&buf, "esds:1", "", "  ")
+			if err != nil {
+				t.Error(err)
+			}
+			gotLines := strings.Split(buf.String(), "\n")
+			wantedLines := strings.Split(c.wantedInfo, "\n")
+			if len(gotLines) != len(wantedLines) {
+				t.Errorf("got %d lines, wanted %d", len(gotLines), len(wantedLines))
+			}
+			for i, line := range gotLines {
+				gotTrimmed := strings.TrimSpace(line)
+				wantedTrimmed := strings.TrimSpace(wantedLines[i])
+				if gotTrimmed != wantedTrimmed {
+					t.Errorf("line %d differs\n%s\n%s", i, gotTrimmed, wantedTrimmed)
+				}
+			}
+		})
 	}
 }