diff --git a/CHANGELOG.md b/CHANGELOG.md index c4010c5b..c911c7a2 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 f9723f5c..f5fa023c 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/aac/aac_test.go b/aac/aac_test.go index 8b78c5ba..b56bd63c 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 07b5ca38..d7146c4f 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 fc6d41df..728cc8dd 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.go b/hevc/hevc.go index 4e007c29..7d4ec0ea 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/hevc/hevc_test.go b/hevc/hevc_test.go index 3e7247ef..09941f05 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 df7b6b16..b1cd19dc 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 7f035d50..0bfe7612 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/audiosamplentry.go b/mp4/audiosamplentry.go index 6554b415..1f82a8c5 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 33764b19..76025614 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/box_test.go b/mp4/box_test.go index d41da373..9b861bf0 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 e6ae9e73..9c38049c 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 00000000..184369ef --- /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 b74433f6..a6a5e5b2 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 a6ec46fe..85884396 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 9c8c6cf6..57f276c2 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 00000000..fb49e7a4 --- /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.go b/mp4/hdlr.go index 54110a87..1e6047bb 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/hdlr_test.go b/mp4/hdlr_test.go index af06d1e6..2b514381 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 508c6d59..a86824a7 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.go b/mp4/stsd.go index 7b588348..573991ad 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) { diff --git a/mp4/stsd_test.go b/mp4/stsd_test.go index db8fad53..fc408a84 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 5333ff39..c34a602f 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 00000000..eb70c28a --- /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 00000000..f2fa777f --- /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) + } +}