Repository: Eyevinn/mp4ff
Pull request #386 (2024-11-09 07:49:48)

More tests in most packages


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4010c5..c911c7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - CreatePrftBox now takes flags parameter
 - PrftBox Info output
+- Removed ReplaceChild method of StsdBox
+- CreateHdlr name for timed metadata
 
 ### Added
 
@@ -18,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Constants for PrftBox flags
 - Unittest to all commands and examples
 
+
 ### Fixed
 
 - Allow missing optional DecoderSpecificInfo
diff --git a/aac/aac.go b/aac/aac.go
index f9723f5..f5fa023 100644
--- a/aac/aac.go
+++ b/aac/aac.go
@@ -1,7 +1,6 @@
 package aac
 
 import (
-	"errors"
 	"fmt"
 	"io"
 
@@ -90,28 +89,25 @@ func DecodeAudioSpecificConfig(r io.Reader) (*AudioSpecificConfig, error) {
 		asc.SBRPresentFlag = true
 		asc.PSPresentFlag = true
 	default:
-		return asc, errors.New("Only LC, HE-AACv1, and HE-AACv2 supported")
+		return asc, fmt.Errorf("unsupported object type: %d", audioObjectType)
 	}
 	frequency, ok := getFrequency(br)
 	if !ok {
-		return asc, fmt.Errorf("Strange frequency index")
+		return asc, fmt.Errorf("strange frequency index")
 	}
 	asc.SamplingFrequency = frequency
 	asc.ChannelConfiguration = byte(br.Read(4))
 	switch audioObjectType {
 	case HEAACv1, HEAACv2:
-		frequency, ok := getFrequency(br)
+		extFrequency, ok := getFrequency(br)
 		if !ok {
-			return asc, errors.New("Strange frequency index")
+			return asc, fmt.Errorf("strange frequency index")
 		}
-		asc.ExtensionFrequency = frequency
+		asc.ExtensionFrequency = extFrequency
 		audioObjectType = byte(br.Read(5)) // Shall be set to AAC-LC here again
-		if audioObjectType == 22 {
-			return asc, errors.New("ExtensionChannelConfiguration not supported")
-		}
 	}
 	if audioObjectType != AAClc {
-		return nil, fmt.Errorf("Base audioObjectType is %d instead of AAC-LC (2)", audioObjectType)
+		return nil, fmt.Errorf("base audioObjectType is %d instead of AAC-LC (2)", audioObjectType)
 	}
 	//GASpecificConfig()
 	_ = br.Read(3) //GASpecificConfig
diff --git a/hevc/hevc.go b/hevc/hevc.go
index 4e007c2..7d4ec0e 100644
--- a/hevc/hevc.go
+++ b/hevc/hevc.go
@@ -133,6 +133,9 @@ func FindNaluTypesUpToFirstVideoNalu(sample []byte) []NaluType {
 func ContainsNaluType(sample []byte, specificNaluType NaluType) bool {
 	var pos uint32 = 0
 	length := len(sample)
+	if length < 4 {
+		return false
+	}
 	for pos < uint32(length-4) {
 		naluLength := binary.BigEndian.Uint32(sample[pos : pos+4])
 		pos += 4
diff --git a/mp4/audiosamplentry.go b/mp4/audiosamplentry.go
index 6554b41..1f82a8c 100644
--- a/mp4/audiosamplentry.go
+++ b/mp4/audiosamplentry.go
@@ -44,7 +44,7 @@ func CreateAudioSampleEntryBox(name string, nrChannels, sampleSize, sampleRate u
 		ChannelCount:       nrChannels,
 		SampleSize:         sampleSize,
 		SampleRate:         sampleRate,
-		Children:           []Box{},
+		Children:           nil,
 	}
 	if child != nil {
 		a.AddChild(child)
diff --git a/mp4/box.go b/mp4/box.go
index 33764b1..7602561 100644
--- a/mp4/box.go
+++ b/mp4/box.go
@@ -356,14 +356,14 @@ func DecodeBoxLazyMdat(startPos uint64, r io.ReadSeeker) (Box, error) {
 type Fixed16 uint16
 
 func (f Fixed16) String() string {
-	return fmt.Sprintf("%d.%d", uint16(f)>>8, uint16(f)&7)
+	return fmt.Sprintf("%d.%d", uint16(f)>>8, uint16(f)&0xff)
 }
 
 // Fixed32 -  A 16.16 fixed point number
 type Fixed32 uint32
 
 func (f Fixed32) String() string {
-	return fmt.Sprintf("%d.%d", uint32(f)>>16, uint32(f)&15)
+	return fmt.Sprintf("%d.%d", uint32(f)>>16, uint32(f)&0xffff)
 }
 
 func strtobuf(out []byte, in string, l int) {
diff --git a/mp4/hdlr.go b/mp4/hdlr.go
index 54110a8..1e6047b 100644
--- a/mp4/hdlr.go
+++ b/mp4/hdlr.go
@@ -12,9 +12,8 @@ import (
 // Contained in: Media Box (mdia) or Meta Box (meta)
 //
 // This box describes the type of data contained in the trak.
-// HandlerType can be : "vide" (video track), "soun" (audio track), "subt" (subtitle track)
-// Other types are: "hint" (hint track), "meta" (timed Metadata track), "auxv" (auxiliary video track).
-// clcp (Closed Captions (QuickTime))
+// Most common hnadler types are: "vide" (video track), "soun" (audio track), "subt" (subtitle track),
+// "text" (text track). "meta" (timed Metadata track), clcp (Closed Captions (QuickTime))
 type HdlrBox struct {
 	Version              byte
 	Flags                uint32
@@ -40,16 +39,18 @@ func CreateHdlr(mediaOrHdlrType string) (*HdlrBox, error) {
 	case "text", "wvtt":
 		hdlr.HandlerType = "text"
 		hdlr.Name = "mp4ff text handler"
+	case "meta":
+		hdlr.HandlerType = "meta"
+		hdlr.Name = "mp4ff timed metadata handler"
 	case "clcp":
 		hdlr.HandlerType = "subt"
 		hdlr.Name = "mp4ff closed captions handler"
 	default:
 		if len(mediaOrHdlrType) != 4 {
-			return nil, fmt.Errorf("unknown media or hdlr type %s", mediaOrHdlrType)
+			return nil, fmt.Errorf("handler type is not four characters: %s", mediaOrHdlrType)
 		}
 		hdlr.HandlerType = mediaOrHdlrType
 		hdlr.Name = fmt.Sprintf("mp4ff %s handler", mediaOrHdlrType)
-
 	}
 	return hdlr, nil
 }
diff --git a/mp4/stsd.go b/mp4/stsd.go
index 7b58834..573991a 100644
--- a/mp4/stsd.go
+++ b/mp4/stsd.go
@@ -76,30 +76,6 @@ func (s *StsdBox) AddChild(box Box) {
 	s.SampleCount++
 }
 
-// ReplaceChild - Replace a child box with one of the same type
-func (s *StsdBox) ReplaceChild(box Box) {
-	switch box.(type) {
-	case *VisualSampleEntryBox:
-		for i, b := range s.Children {
-			switch b.(type) {
-			case *VisualSampleEntryBox:
-				s.Children[i] = box.(*VisualSampleEntryBox)
-				s.AvcX = box.(*VisualSampleEntryBox)
-			}
-		}
-	case *AudioSampleEntryBox:
-		for i, b := range s.Children {
-			switch b.(type) {
-			case *AudioSampleEntryBox:
-				s.Children[i] = box.(*AudioSampleEntryBox)
-				s.Mp4a = box.(*AudioSampleEntryBox)
-			}
-		}
-	default:
-		panic("Cannot handle box type")
-	}
-}
-
 // GetSampleDescription - get one of multiple descriptions
 func (s *StsdBox) GetSampleDescription(index int) (Box, error) {
 	if index >= len(s.Children) {


Test patch:
diff --git a/aac/aac_test.go b/aac/aac_test.go
index 8b78c5b..b56bd63 100644
--- a/aac/aac_test.go
+++ b/aac/aac_test.go
@@ -57,5 +57,51 @@ func TestAudioSpecificConfigEncodeDecode(t *testing.T) {
 			t.Errorf("Diff %v for %+v", diff, asc)
 		}
 	}
+}
+
+func TestVariousInputs(t *testing.T) {
 
+	testCases := []struct {
+		desc          string
+		data          []byte
+		expectedError string
+	}{
+		{
+			desc:          "unsupported object type",
+			data:          []byte{0x0f, 0x00},
+			expectedError: "unsupported object type: 1",
+		},
+		{
+			desc:          "bad frequency index",
+			data:          []byte{0x17, 0x30},
+			expectedError: "strange frequency index",
+		},
+		{
+			desc:          "too short extended frequency",
+			data:          []byte{0x17, 0x80, 0x40},
+			expectedError: "strange frequency index",
+		},
+	}
+
+	for _, c := range testCases {
+		t.Run(c.desc, func(t *testing.T) {
+			readBuf := bytes.NewBuffer(c.data)
+			_, err := DecodeAudioSpecificConfig(readBuf)
+			if err == nil || err.Error() != c.expectedError {
+				t.Errorf("Expected error: %s", c.expectedError)
+			}
+		})
+	}
+
+	t.Run("32768Hz", func(t *testing.T) {
+		data := []byte{0x17, 0x80, 0x40, 0x00, 0x00}
+		readBuf := bytes.NewBuffer(data)
+		gotAsc, err := DecodeAudioSpecificConfig(readBuf)
+		if err != nil {
+			t.Error(err)
+		}
+		if gotAsc.SamplingFrequency != 32768 {
+			t.Errorf("Expected 32768Hz, got %d", gotAsc.SamplingFrequency)
+		}
+	})
 }
diff --git a/aac/adts_test.go b/aac/adts_test.go
index 07b5ca3..d7146c4 100644
--- a/aac/adts_test.go
+++ b/aac/adts_test.go
@@ -18,22 +18,23 @@ func TestADTS(t *testing.T) {
 	adtsBytes := adtsHdrStart.Encode()
 
 	testCases := []struct {
-		adtsBytes    []byte
-		wantedHdr    *ADTSHeader
-		wantedOffset int
-		wantedError  error
+		adtsBytes       []byte
+		wantedHdr       *ADTSHeader
+		wantedOffset    int
+		wantedFrequency uint16
+		wantedError     error
 	}{
 		{
-			adtsBytes:    adtsBytes,
-			wantedHdr:    adtsHdrStart,
-			wantedOffset: 0,
-			wantedError:  nil,
+			adtsBytes:       adtsBytes,
+			wantedHdr:       adtsHdrStart,
+			wantedOffset:    0,
+			wantedFrequency: 48000,
 		},
 		{
-			adtsBytes:    append([]byte{0xfe}, adtsBytes...),
-			wantedHdr:    adtsHdrStart,
-			wantedOffset: 1,
-			wantedError:  nil,
+			adtsBytes:       append([]byte{0xfe}, adtsBytes...),
+			wantedHdr:       adtsHdrStart,
+			wantedOffset:    1,
+			wantedFrequency: 48000,
 		},
 	}
 
@@ -45,6 +46,9 @@ func TestADTS(t *testing.T) {
 		if gotOffset != tc.wantedOffset {
 			t.Errorf("Got offset %d instead of %d", gotOffset, tc.wantedOffset)
 		}
+		if tc.wantedFrequency != gotHdr.Frequency() {
+			t.Errorf("Got frequency %d instead of %d", gotHdr.Frequency(), tc.wantedFrequency)
+		}
 		if diff := deep.Equal(gotHdr, tc.wantedHdr); diff != nil {
 			t.Error(diff)
 		}
diff --git a/avc/slice_test.go b/avc/slice_test.go
index fc6d41d..728cc8d 100644
--- a/avc/slice_test.go
+++ b/avc/slice_test.go
@@ -22,6 +22,27 @@ func TestSliceTypeParser(t *testing.T) {
 	}
 }
 
+func TestSliceTypeStrings(t *testing.T) {
+	cases := []struct {
+		sliceType SliceType
+		want      string
+	}{
+		{SLICE_P, "P"},
+		{SLICE_B, "B"},
+		{SLICE_I, "I"},
+		{SLICE_SP, "SP"},
+		{SLICE_SI, "SI"},
+		{SliceType(12), ""},
+	}
+	for _, c := range cases {
+		got := c.sliceType.String()
+		if got != c.want {
+			t.Errorf("got %s want %s", got, c.want)
+		}
+	}
+
+}
+
 func TestParseSliceHeader_BlackFrame(t *testing.T) {
 	wantedHdr := SliceHeader{
 		SliceType:              7,
diff --git a/hevc/hevc_test.go b/hevc/hevc_test.go
index 3e7247e..09941f0 100644
--- a/hevc/hevc_test.go
+++ b/hevc/hevc_test.go
@@ -1,6 +1,7 @@
 package hevc
 
 import (
+	"strings"
 	"testing"
 
 	"github.com/go-test/deep"
@@ -8,14 +9,22 @@ import (
 
 func TestGetNaluTypes(t *testing.T) {
 	testCases := []struct {
-		name   string
-		input  []byte
-		wanted []NaluType
+		name                string
+		input               []byte
+		wanted              []NaluType
+		nalusUpToFirstVideo []NaluType
+		containsVPS         bool
+		isRapSample         bool
+		isIDRSample         bool
 	}{
 		{
 			"AUD",
 			[]byte{0, 0, 0, 2, 70, 0},
 			[]NaluType{NALU_AUD},
+			[]NaluType{NALU_AUD},
+			false,
+			false,
+			false,
 		},
 		{
 			"AUD, VPS, SPS, PPS, and IDR ",
@@ -26,14 +35,45 @@ func TestGetNaluTypes(t *testing.T) {
 				0, 0, 0, 3, 68, 3, 3,
 				0, 0, 0, 3, 40, 4, 4},
 			[]NaluType{NALU_AUD, NALU_VPS, NALU_SPS, NALU_PPS, NALU_IDR_N_LP},
+			[]NaluType{NALU_AUD, NALU_VPS, NALU_SPS, NALU_PPS, NALU_IDR_N_LP},
+			true,
+			true,
+			true,
+		},
+		{
+			"too short",
+			[]byte{0, 0, 0},
+			[]NaluType{},
+			[]NaluType{},
+			false,
+			false,
+			false,
 		},
 	}
 
 	for _, tc := range testCases {
-		got := FindNaluTypes(tc.input)
-		if diff := deep.Equal(got, tc.wanted); diff != nil {
-			t.Errorf("%s: %v", tc.name, diff)
-		}
+		t.Run(tc.name, func(t *testing.T) {
+			got := FindNaluTypes(tc.input)
+			if diff := deep.Equal(got, tc.wanted); diff != nil {
+				t.Errorf("nalulist diff: %v", diff)
+			}
+			got = FindNaluTypesUpToFirstVideoNalu(tc.input)
+			if diff := deep.Equal(got, tc.nalusUpToFirstVideo); diff != nil {
+				t.Errorf("nalus before first video diff: %v", diff)
+			}
+			hasVPS := ContainsNaluType(tc.input, NALU_VPS)
+			if hasVPS != tc.containsVPS {
+				t.Errorf("got %t instead of %t", hasVPS, tc.containsVPS)
+			}
+			isRAP := IsRAPSample(tc.input)
+			if isRAP != tc.isRapSample {
+				t.Errorf("got %t instead of %t", isRAP, tc.isRapSample)
+			}
+			isIDR := IsIDRSample(tc.input)
+			if isIDR != tc.isIDRSample {
+				t.Errorf("got %t instead of %t", isIDR, tc.isIDRSample)
+			}
+		})
 	}
 }
 
@@ -61,10 +101,12 @@ func TestHasParameterSets(t *testing.T) {
 	}
 
 	for _, tc := range testCases {
-		got := HasParameterSets(tc.input)
-		if got != tc.wanted {
-			t.Errorf("%s: got %t instead of %t", tc.name, got, tc.wanted)
-		}
+		t.Run(tc.name, func(t *testing.T) {
+			got := HasParameterSets(tc.input)
+			if got != tc.wanted {
+				t.Errorf("got %t instead of %t", got, tc.wanted)
+			}
+		})
 	}
 }
 
@@ -96,15 +138,30 @@ func TestGetParameterSets(t *testing.T) {
 	}
 
 	for _, tc := range testCases {
-		gotVPS, gotSPS, gotPPS := GetParameterSets(tc.input)
-		if diff := deep.Equal(gotVPS, tc.wantedVPS); diff != nil {
-			t.Errorf("%s VPS: %v", tc.name, diff)
-		}
-		if diff := deep.Equal(gotSPS, tc.wantedSPS); diff != nil {
-			t.Errorf("%s SPS: %v", tc.name, diff)
-		}
-		if diff := deep.Equal(gotPPS, tc.wantedPPS); diff != nil {
-			t.Errorf("%s PPS: %v", tc.name, diff)
+		t.Run(tc.name, func(t *testing.T) {
+			gotVPS, gotSPS, gotPPS := GetParameterSets(tc.input)
+			if diff := deep.Equal(gotVPS, tc.wantedVPS); diff != nil {
+				t.Errorf("VPS diff: %v", diff)
+			}
+			if diff := deep.Equal(gotSPS, tc.wantedSPS); diff != nil {
+				t.Errorf("SPS diff: %v", diff)
+			}
+			if diff := deep.Equal(gotPPS, tc.wantedPPS); diff != nil {
+				t.Errorf("PPS diff: %v", diff)
+			}
+		})
+	}
+}
+
+func TestNaluTypeStrings(t *testing.T) {
+	named := 0
+	for n := NaluType(0); n < NaluType(64); n++ {
+		desc := n.String()
+		if !strings.HasPrefix(desc, "Other") {
+			named++
 		}
 	}
+	if named != 22 {
+		t.Errorf("got %d named instead of 22", named)
+	}
 }
diff --git a/hevc/hevcdecoderconfigurationrecord_test.go b/hevc/hevcdecoderconfigurationrecord_test.go
index df7b6b1..b1cd19d 100644
--- a/hevc/hevcdecoderconfigurationrecord_test.go
+++ b/hevc/hevcdecoderconfigurationrecord_test.go
@@ -1,6 +1,7 @@
 package hevc
 
 import (
+	"bytes"
 	"encoding/hex"
 	"strings"
 	"testing"
@@ -96,7 +97,6 @@ func TestDecodeConfRec(t *testing.T) {
 		t.Error(err)
 	}
 	for _, na := range hdcr.NaluArrays {
-
 		switch na.NaluType() {
 		case NALU_VPS, NALU_PPS:
 		case NALU_SPS:
@@ -127,4 +127,10 @@ func TestDecodeConfRec(t *testing.T) {
 			t.Errorf("strange nalu type %s", na.NaluType())
 		}
 	}
+
+	out := bytes.Buffer{}
+	err = hdcr.Encode(&out)
+	if err != nil {
+		t.Error(err)
+	}
 }
diff --git a/mp4/audiosampleentry_test.go b/mp4/audiosampleentry_test.go
index 7f035d5..0bfe761 100644
--- a/mp4/audiosampleentry_test.go
+++ b/mp4/audiosampleentry_test.go
@@ -1,42 +1,17 @@
 package mp4
 
 import (
-	"bytes"
 	"testing"
-
-	"github.com/Eyevinn/mp4ff/bits"
 )
 
 func TestWriteReadOfAudioSampleEntry(t *testing.T) {
-	ase := CreateAudioSampleEntryBox("mp4a", 2, 16, 48000, nil)
-
-	// Write to a buffer so that we can read and check
-	var buf bytes.Buffer
-	err := ase.Encode(&buf)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Read back from buffer
-	encData := buf.Bytes()
-	encBuf := bytes.NewBuffer(encData)
-	decodedBox, err := DecodeBox(0, encBuf)
-	if err != nil {
-		t.Error("Did not get a box back")
-	}
-	outAse := decodedBox.(*AudioSampleEntryBox)
-	if outAse.SampleRate != ase.SampleRate {
-		t.Errorf("Out sampled rate %d differs from in %d", outAse.SampleRate, ase.SampleRate)
-	}
-
-	// Read back from buffer
-	sr := bits.NewFixedSliceReader(encData)
-	decodedBoxSR, err := DecodeBoxSR(0, sr)
-	if err != nil {
-		t.Error("Did not get a box back")
-	}
-	outAse = decodedBoxSR.(*AudioSampleEntryBox)
-	if outAse.SampleRate != ase.SampleRate {
-		t.Errorf("Out sampled rate %d differs from in %d for SliceReader", outAse.SampleRate, ase.SampleRate)
+	ascBytes := []byte{0x11, 0x90}
+	esds := CreateEsdsBox(ascBytes)
+	ase := CreateAudioSampleEntryBox("mp4a", 2, 16, 48000, esds)
+	boxDiffAfterEncodeAndDecode(t, ase)
+	_, err := ase.RemoveEncryption()
+	expectedErrMsg := "is not encrypted: mp4a"
+	if err == nil || err.Error() != expectedErrMsg {
+		t.Errorf("expected error with message: %q", err.Error())
 	}
 }
diff --git a/mp4/box_test.go b/mp4/box_test.go
index d41da37..9b861bf 100644
--- a/mp4/box_test.go
+++ b/mp4/box_test.go
@@ -50,3 +50,14 @@ func TestBadBoxAndRemoveBoxDecoder(t *testing.T) {
 		t.Errorf("written unknown differs")
 	}
 }
+
+func TestFixed16and32(t *testing.T) {
+	f16 := Fixed16(256)
+	if f16.String() != "1.0" {
+		t.Errorf("Fixed16(256) should be 1.0, not %s", f16.String())
+	}
+	f32 := Fixed32(65536)
+	if f16.String() != "1.0" {
+		t.Errorf("Fixed32(65536) should be 1.0, not %s", f32.String())
+	}
+}
diff --git a/mp4/co64_test.go b/mp4/co64_test.go
index e6ae9e7..9c38049 100644
--- a/mp4/co64_test.go
+++ b/mp4/co64_test.go
@@ -12,4 +12,17 @@ func TestEncDecCo64(t *testing.T) {
 		ChunkOffset: []uint64{1234, 8908080},
 	}
 	boxDiffAfterEncodeAndDecode(t, b)
+
+	_, err := b.GetOffset(0)
+	if err == nil {
+		t.Error("should not be able to get offset for nr 0")
+	}
+
+	offset, err := b.GetOffset(1)
+	if err != nil {
+		t.Error(err)
+	}
+	if offset != 1234 {
+		t.Errorf("offset = %d instead of 1234", offset)
+	}
 }
diff --git a/mp4/container_test.go b/mp4/container_test.go
new file mode 100644
index 0000000..184369e
--- /dev/null
+++ b/mp4/container_test.go
@@ -0,0 +1,21 @@
+package mp4
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestGenericContainer(t *testing.T) {
+	// Just check that it doesn't crash
+	c := NewGenericContainerBox("test")
+	c.AddChild(&VsidBox{SourceID: 42})
+	w := bytes.Buffer{}
+	err := c.Encode(&w)
+	if err != nil {
+		t.Error(err)
+	}
+	err = c.Info(&w, "", "", "  ")
+	if err != nil {
+		t.Error(err)
+	}
+}
diff --git a/mp4/crypto_test.go b/mp4/crypto_test.go
index b74433f..a6a5e5b 100644
--- a/mp4/crypto_test.go
+++ b/mp4/crypto_test.go
@@ -106,18 +106,29 @@ func TestEncryptDecrypt(t *testing.T) {
 	kidHex := "11112222333344445555666677778888"
 	key, _ := hex.DecodeString(keyHex)
 	kidUUID, _ := NewUUIDFromHex(kidHex)
+	psshFile := "testdata/pssh.bin"
+	psh, err := os.Open(psshFile)
+	if err != nil {
+		t.Fatal(err)
+	}
+	box, err := DecodeBox(0, psh)
+	if err != nil {
+		t.Fatal(err)
+	}
+	pssh := box.(*PsshBox)
 
 	testCases := []struct {
-		desc   string
-		init   string
-		seg    string
-		scheme string
-		iv     string
+		desc    string
+		init    string
+		seg     string
+		scheme  string
+		iv      string
+		hasPssh bool
 	}{
 		{desc: "video, cenc, iv8", init: videoInit, seg: videoSeg, scheme: "cenc", iv: ivHex8},
 		{desc: "video, cbcs, iv8", init: videoInit, seg: videoSeg, scheme: "cbcs", iv: ivHex8},
 		{desc: "video, cbcs, iv16", init: videoInit, seg: videoSeg, scheme: "cbcs", iv: ivHex16},
-		{desc: "audio, cbcs, iv16", init: audioInit, seg: audioSeg, scheme: "cbcs", iv: ivHex16},
+		{desc: "audio, cbcs, iv16", init: audioInit, seg: audioSeg, scheme: "cbcs", iv: ivHex16, hasPssh: true},
 	}
 	for _, c := range testCases {
 		t.Run(c.desc, func(t *testing.T) {
@@ -134,7 +145,13 @@ func TestEncryptDecrypt(t *testing.T) {
 			if err != nil {
 				t.Fatal(err)
 			}
-			ipf, err := InitProtect(init.Init, key, iv, c.scheme, kidUUID, nil)
+
+			var psshs []*PsshBox
+			if c.hasPssh {
+				psshs = []*PsshBox{pssh}
+			}
+
+			ipf, err := InitProtect(init.Init, key, iv, c.scheme, kidUUID, psshs)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -254,7 +271,3 @@ func TestDecryptInit(t *testing.T) {
 		}
 	}
 }
-
-func TestDecryptEncrypt(t *testing.T) {
-
-}
diff --git a/mp4/descriptors_test.go b/mp4/descriptors_test.go
index a6ec46f..8588439 100644
--- a/mp4/descriptors_test.go
+++ b/mp4/descriptors_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/Eyevinn/mp4ff/bits"
+	"github.com/go-test/deep"
 )
 
 const badSizeDescriptor = `031900010004134015000000000000000001f40005021190060102`
@@ -142,3 +143,42 @@ func TestDescriptorInfo(t *testing.T) {
 		})
 	}
 }
+
+func TestRawDescriptor(t *testing.T) {
+	sizeSizeMinus1 := 0
+	data := []byte{0x02, 0x03}
+	rd, err := CreateRawDescriptor(15, byte(sizeSizeMinus1), data)
+	if err != nil {
+		t.Error(err)
+	}
+	if rd.Tag() != 15 {
+		t.Error("tag is not 1")
+	}
+	if int(rd.Size()) != len(data) {
+		t.Errorf("size is %d instead of %d", rd.Size(), len(data))
+	}
+	expectedType := "tag=15 Unknown"
+	if rd.Type() != expectedType {
+		t.Errorf(`type is not %q but %q`, expectedType, rd.Type())
+	}
+	sw := bits.NewFixedSliceWriter(int(rd.SizeSize()))
+	err = rd.EncodeSW(sw)
+	if err != nil {
+		t.Error(err)
+	}
+	info := bytes.Buffer{}
+	err = rd.Info(&info, "all:1", "", "  ")
+	if err != nil {
+		t.Error(err)
+	}
+	totNrBytes := rd.SizeSize()
+	rw := bits.NewFixedSliceReader(sw.Bytes())
+	rdDec, err := DecodeDescriptor(rw, int(totNrBytes))
+	if err != nil {
+		t.Error(err)
+	}
+	rawDec := rdDec.(*RawDescriptor)
+	if diff := deep.Equal(rawDec, &rd); diff != nil {
+		t.Error(diff)
+	}
+}
diff --git a/mp4/eventmessage_test.go b/mp4/eventmessage_test.go
index 9c8c6cf..57f276c 100644
--- a/mp4/eventmessage_test.go
+++ b/mp4/eventmessage_test.go
@@ -42,6 +42,7 @@ func TestEvteInclSilb(t *testing.T) {
 			},
 		},
 	}
+	boxDiffAfterEncodeAndDecode(t, &silb)
 	evte := EvteBox{
 		DataReferenceIndex: 1,
 	}
diff --git a/mp4/frma_test.go b/mp4/frma_test.go
new file mode 100644
index 0000000..fb49e7a
--- /dev/null
+++ b/mp4/frma_test.go
@@ -0,0 +1,8 @@
+package mp4
+
+import "testing"
+
+func TestFrma(t *testing.T) {
+	frma := &FrmaBox{DataFormat: "avc1"}
+	boxDiffAfterEncodeAndDecode(t, frma)
+}
diff --git a/mp4/hdlr_test.go b/mp4/hdlr_test.go
index af06d1e..2b51438 100644
--- a/mp4/hdlr_test.go
+++ b/mp4/hdlr_test.go
@@ -7,19 +7,47 @@ import (
 )
 
 func TestHdlr(t *testing.T) {
-	mediaTypes := []string{"video", "audio", "subtitle"}
 
-	for _, m := range mediaTypes {
-		hdlr, err := CreateHdlr(m)
-		assertNoError(t, err)
-		boxDiffAfterEncodeAndDecode(t, hdlr)
+	cases := []struct {
+		mediaType     string
+		handlerType   string
+		handlerName   string
+		expectedError string
+	}{
+		{"video", "vide", "mp4ff video handler", ""},
+		{"vide", "vide", "mp4ff video handler", ""},
+		{"audio", "soun", "mp4ff audio handler", ""},
+		{"soun", "soun", "mp4ff audio handler", ""},
+		{"subtitle", "subt", "mp4ff subtitle handler", ""},
+		{"text", "text", "mp4ff text handler", ""},
+		{"wvtt", "text", "mp4ff text handler", ""},
+		{"meta", "meta", "mp4ff timed metadata handler", ""},
+		{"clcp", "subt", "mp4ff closed captions handler", ""},
+		{"roses", "", "", "handler type is not four characters: roses"},
+		{"auxv", "auxv", "mp4ff auxv handler", ""},
 	}
 
-	for _, m := range mediaTypes {
-		hdlr, err := CreateHdlr(m)
-		hdlr.LacksNullTermination = true
-		assertNoError(t, err)
-		boxDiffAfterEncodeAndDecode(t, hdlr)
+	for _, c := range cases {
+		t.Run(c.mediaType, func(t *testing.T) {
+			hdlr, err := CreateHdlr(c.mediaType)
+			if c.expectedError != "" {
+				if err == nil {
+					t.Errorf("Expected error %s, but got nil", c.expectedError)
+				} else if err.Error() != c.expectedError {
+					t.Errorf("Expected error %s, but got %s", c.expectedError, err.Error())
+				}
+				return
+			}
+			if hdlr.HandlerType != c.handlerType {
+				t.Errorf("Expected handler type %s, but got %s", c.handlerType, hdlr.HandlerType)
+			}
+			if hdlr.Name != c.handlerName {
+				t.Errorf("Expected handler name %s, but got %s", c.handlerName, hdlr.Name)
+			}
+			boxDiffAfterEncodeAndDecode(t, hdlr)
+			hdlr.LacksNullTermination = true
+			boxDiffAfterEncodeAndDecode(t, hdlr)
+		})
 	}
 }
 
diff --git a/mp4/nmhd_test.go b/mp4/nmhd_test.go
index 508c6d5..a86824a 100644
--- a/mp4/nmhd_test.go
+++ b/mp4/nmhd_test.go
@@ -1,28 +1,11 @@
 package mp4
 
 import (
-	"bytes"
 	"testing"
-
-	"github.com/go-test/deep"
 )
 
 func TestNmhd(t *testing.T) {
 
 	encBox := &NmhdBox{}
-
-	buf := bytes.Buffer{}
-	err := encBox.Encode(&buf)
-	if err != nil {
-		t.Error(err)
-	}
-
-	decBox, err := DecodeBox(0, &buf)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if diff := deep.Equal(encBox, decBox); diff != nil {
-		t.Error(diff)
-	}
+	boxDiffAfterEncodeAndDecode(t, encBox)
 }
diff --git a/mp4/stsd_test.go b/mp4/stsd_test.go
index db8fad5..fc408a8 100644
--- a/mp4/stsd_test.go
+++ b/mp4/stsd_test.go
@@ -50,3 +50,25 @@ func TestStsd(t *testing.T) {
 		}
 	}
 }
+
+func TestStsdEncodeDecode(t *testing.T) {
+	stsd := &StsdBox{}
+	evte := &EvteBox{}
+	stsd.AddChild(evte)
+	boxDiffAfterEncodeAndDecode(t, stsd)
+	b, err := stsd.GetSampleDescription(0)
+	if err != nil {
+		t.Error(err)
+	}
+	if b != evte {
+		t.Errorf("Expected %v, got %v", evte, b)
+	}
+	b, err = stsd.GetSampleDescription(1)
+	if err == nil {
+		t.Errorf("Expected error, got %v", b)
+	}
+	btrt := stsd.GetBtrt()
+	if btrt != nil {
+		t.Errorf("Expected nil, got %v", btrt)
+	}
+}
diff --git a/mp4/wvtt_test.go b/mp4/wvtt_test.go
index 5333ff3..c34a602 100644
--- a/mp4/wvtt_test.go
+++ b/mp4/wvtt_test.go
@@ -41,3 +41,13 @@ func TestVtta(t *testing.T) {
 	vtta := &VttaBox{CueAdditionalText: "This is a comment"}
 	boxDiffAfterEncodeAndDecode(t, vtta)
 }
+
+func TestVlab(t *testing.T) {
+	vlab := &VlabBox{SourceLabel: "Swedish news"}
+	boxDiffAfterEncodeAndDecode(t, vlab)
+}
+
+func TestVttC(t *testing.T) {
+	vttC := &VttCBox{Config: "..."}
+	boxDiffAfterEncodeAndDecode(t, vttC)
+}
diff --git a/sei/sei136_test.go b/sei/sei136_test.go
new file mode 100644
index 0000000..eb70c28
--- /dev/null
+++ b/sei/sei136_test.go
@@ -0,0 +1,53 @@
+package sei
+
+import (
+	"testing"
+
+	"github.com/go-test/deep"
+)
+
+func TestSei136Clock(t *testing.T) {
+	cl := CreateClockTS()
+	cl.TimeOffsetValue = 1300
+	cl.NFrames = 5
+	cl.Hours = 12
+	cl.Minutes = 30
+	cl.Seconds = 10
+	cl.ClockTimeStampFlag = true
+	cl.UnitsFieldBasedFlag = true
+	cl.FullTimeStampFlag = false
+	cl.SecondsFlag = true
+	cl.MinutesFlag = true
+	cl.HoursFlag = true
+	cl.DiscontinuityFlag = false
+	cl.CntDroppedFlag = false
+	cl.CountingType = 1
+	cl.TimeOffsetLength = 11
+
+	tc := TimeCodeSEI{}
+	tc.Clocks = append(tc.Clocks, cl)
+	tc.Clocks = append(tc.Clocks, cl)
+	pl := tc.Payload()
+	str := tc.String()
+	if str == "" {
+		t.Error("String() failed")
+	}
+	if len(pl) == 0 {
+		t.Error("Payload() failed")
+	}
+
+	seiData := SEIData{
+		payloadType: SEITimeCodeType,
+		payload:     pl,
+	}
+	sei, err := DecodeTimeCodeSEI(&seiData)
+	if err != nil {
+		t.Error(err)
+	}
+	tcDec := sei.(*TimeCodeSEI)
+	decCl := tcDec.Clocks[0]
+	diff := deep.Equal(cl, decCl)
+	if diff != nil {
+		t.Error(diff)
+	}
+}
diff --git a/sei/sei1_avc_test.go b/sei/sei1_avc_test.go
new file mode 100644
index 0000000..f2fa777
--- /dev/null
+++ b/sei/sei1_avc_test.go
@@ -0,0 +1,52 @@
+package sei
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/Eyevinn/mp4ff/bits"
+	"github.com/go-test/deep"
+)
+
+func TestSE1AVCCLock(t *testing.T) {
+	cl := ClockTSAvc{
+		CtType:             0,
+		NuitFieldBasedFlag: false,
+		CountingType:       0,
+		NFrames:            5,
+		Hours:              12,
+		Minutes:            30,
+		Seconds:            10,
+		ClockTimeStampFlag: true,
+		FullTimeStampFlag:  false,
+		SecondsFlag:        true,
+		MinutesFlag:        true,
+		HoursFlag:          true,
+		DiscontinuityFlag:  false,
+		CntDroppedFlag:     false,
+		TimeOffsetLength:   5,
+		TimeOffsetValue:    -15,
+	}
+	jsonBytes, err := cl.MarshalJSON()
+	if err != nil {
+		t.Error(err)
+	}
+	wantedJSON := `{"time":"12:30:10:05","offset":-15}`
+	if string(jsonBytes) != wantedJSON {
+		t.Errorf("Got %s but wanted %s", jsonBytes, wantedJSON)
+	}
+	size := cl.NrBits()
+	nrBytes := (size + 7) / 8
+	sw := bits.NewFixedSliceWriter(nrBytes)
+	cl.WriteToSliceWriter(sw)
+	if sw.AccError() != nil {
+		t.Error(sw.AccError())
+	}
+	sw.FlushBits()
+	r := bytes.NewReader(sw.Bytes())
+	rd := bits.NewReader(r)
+	decClock := DecodeClockTSAvc(rd, cl.TimeOffsetLength)
+	if diff := deep.Equal(cl, decClock); diff != nil {
+		t.Error(diff)
+	}
+}