diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fae3efa..aed311fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,10 +21,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for SMPTE-2086 Mastering Display Metadata Box (SmDm) - Support for Content Light Level Box (CoLL) - Better test coverage for VisualSampleEntryBox +- IsVideoNaluType functions in both avc and hevc packages ### Fixed - support short ESDS without SLConfig descriptor (issue #393) +- HEVC Slice Header CollocatedFromL0Flag should be true by default ## [0.47.0] - 2024-11-12 diff --git a/avc/avc_test.go b/avc/avc_test.go index 277b630e..948e6621 100644 --- a/avc/avc_test.go +++ b/avc/avc_test.go @@ -118,3 +118,46 @@ func TestGetParameterSets(t *testing.T) { } } } + +func TestIsVideoNaluType(t *testing.T) { + testCases := []struct { + name string + naluType NaluType + want bool + }{ + { + name: "video type - NALU_NON_IDR (1)", + naluType: NALU_NON_IDR, + want: true, + }, + { + name: "video type - NALU_IDR (5)", + naluType: NALU_IDR, + want: true, + }, + { + name: "non-video type - NALU_SEI (6)", + naluType: NALU_SEI, + want: false, + }, + { + name: "non-video type - NALU_SPS (7)", + naluType: NALU_SPS, + want: false, + }, + { + name: "non-video type - NALU_AUD (9)", + naluType: NALU_AUD, + want: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := IsVideoNaluType(tc.naluType) + if got != tc.want { + t.Errorf("IsVideoNaluType(%d) = %v; want %v", tc.naluType, got, tc.want) + } + }) + } +} diff --git a/cmd/mp4ff-decrypt/doc.go b/cmd/mp4ff-decrypt/doc.go index 3eb5035a..74f3e0f8 100644 --- a/cmd/mp4ff-decrypt/doc.go +++ b/cmd/mp4ff-decrypt/doc.go @@ -8,10 +8,10 @@ mp4ff-decrypt [options] infile outfile options: -init string - Path to init file with encryption info (scheme, kid, pssh) + Path to init file with encryption info (scheme, kid, pssh) -key string - Required: key (32 hex or 24 base64 chars) + Required: key (32 hex or 24 base64 chars) -version - Get mp4ff version + Get mp4ff version */ package main diff --git a/cmd/mp4ff-decrypt/main.go b/cmd/mp4ff-decrypt/main.go index 3c0ac901..61b1bb62 100644 --- a/cmd/mp4ff-decrypt/main.go +++ b/cmd/mp4ff-decrypt/main.go @@ -74,6 +74,7 @@ func run(args []string) error { var outFilePath = fs.Arg(1) if opts.keyStr == "" { + fs.Usage() return fmt.Errorf("no key specified") } diff --git a/cmd/mp4ff-encrypt/doc.go b/cmd/mp4ff-encrypt/doc.go index 0741729a..19b24c4f 100644 --- a/cmd/mp4ff-encrypt/doc.go +++ b/cmd/mp4ff-encrypt/doc.go @@ -2,6 +2,8 @@ mp4ff-encrypt encrypts a fragmented mp4 file using Common Encryption with cenc or cbcs scheme. A combined fragmented file with init segment and media segment(s) will be encrypted. For a pure media segment, an init segment with encryption information is needed. +For video, only AVC with avc1 and HEVC with hvc1 sample entries are currently supported. +For audio, all supported audio codecs should work. Usage of mp4ff-encrypt: @@ -10,18 +12,18 @@ mp4ff-encrypt [options] infile outfile options: -init string - Path to init file with encryption info (scheme, kid, pssh) + Path to init file with encryption info (scheme, kid, pssh) -iv string - Required: iv (16 or 32 hex chars) + Required: iv (16 or 32 hex chars) -key string - Required: key (32 hex or 24 base64 chars) + Required: key (32 hex or 24 base64 chars) -kid string - key id (32 hex or 24 base64 chars). Required if initFilePath empty + key id (32 hex or 24 base64 chars). Required if initFilePath empty -pssh string - file with one or more pssh box(es) in binary format. Will be added at end of moov box + file with one or more pssh box(es) in binary format. Will be added at end of moov box -scheme string - cenc or cbcs. Required if initFilePath empty (default "cenc") + cenc or cbcs. Required if initFilePath empty (default "cenc") -version - Get mp4ff version + Get mp4ff version */ package main diff --git a/cmd/mp4ff-encrypt/main.go b/cmd/mp4ff-encrypt/main.go index 68e55ea1..4fe8a731 100644 --- a/cmd/mp4ff-encrypt/main.go +++ b/cmd/mp4ff-encrypt/main.go @@ -19,6 +19,8 @@ const ( var usg = `%s encrypts a fragmented mp4 file using Common Encryption with cenc or cbcs scheme. A combined fragmented file with init segment and media segment(s) will be encrypted. For a pure media segment, an init segment with encryption information is needed. +For video, only AVC with avc1 and HEVC with hvc1 sample entries are currently supported. +For audio, all supported audio codecs should work. Usage of %s: ` diff --git a/hevc/hevc.go b/hevc/hevc.go index 7d4ec0ea..9406b73f 100644 --- a/hevc/hevc.go +++ b/hevc/hevc.go @@ -122,13 +122,18 @@ func FindNaluTypesUpToFirstVideoNalu(sample []byte) []NaluType { naluType := GetNaluType(sample[pos]) naluList = append(naluList, naluType) pos += naluLength - if naluType <= highestVideoNaluType { + if IsVideoNaluType(naluType) { break // Video has started } } return naluList } +// IsVideoNaluType returns true if NaluType is a video type (<= 31) +func IsVideoNaluType(naluType NaluType) bool { + return naluType <= highestVideoNaluType +} + // ContainsNaluType - is specific NaluType present in sample func ContainsNaluType(sample []byte, specificNaluType NaluType) bool { var pos uint32 = 0 diff --git a/hevc/hevc_test.go b/hevc/hevc_test.go index 09941f05..b010937f 100644 --- a/hevc/hevc_test.go +++ b/hevc/hevc_test.go @@ -165,3 +165,51 @@ func TestNaluTypeStrings(t *testing.T) { t.Errorf("got %d named instead of 22", named) } } + +func TestIsVideoNaluType(t *testing.T) { + testCases := []struct { + name string + naluType NaluType + want bool + }{ + { + name: "video type - NALU_TRAIL_N (0)", + naluType: NALU_TRAIL_N, + want: true, + }, + { + name: "video type - NALU_TRAIL_R (1)", + naluType: NALU_TRAIL_R, + want: true, + }, + { + name: "video type - NALU_IDR_W_RADL (19)", + naluType: NALU_IDR_W_RADL, + want: true, + }, + { + name: "video type - highest (31)", + naluType: 31, + want: true, + }, + { + name: "non-video type - VPS (32)", + naluType: NALU_VPS, + want: false, + }, + { + name: "non-video type - SPS (33)", + naluType: NALU_SPS, + want: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := IsVideoNaluType(tc.naluType) + if got != tc.want { + t.Errorf("IsVideoNaluType(%d) = %v; want %v", tc.naluType, got, tc.want) + } + }) + } +} diff --git a/hevc/slice.go b/hevc/slice.go index ed7ce88e..2d8c45d5 100644 --- a/hevc/slice.go +++ b/hevc/slice.go @@ -109,7 +109,9 @@ type WeightingFactors struct { } func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PPS) (*SliceHeader, error) { - sh := &SliceHeader{} + sh := &SliceHeader{ + CollocatedFromL0Flag: true, // Default according to Section 7.4.7.1 + } buf := bytes.NewBuffer(nalu) r := bits.NewEBSPReader(buf) diff --git a/hevc/slice_test.go b/hevc/slice_test.go index 3a0ed0d0..13477497 100644 --- a/hevc/slice_test.go +++ b/hevc/slice_test.go @@ -20,6 +20,7 @@ func TestParseSliceHeader(t *testing.T) { LoopFilterAcrossSlicesEnabledFlag: true, NumEntryPointOffsets: 1, OffsetLenMinus1: 3, + CollocatedFromL0Flag: true, EntryPointOffsetMinus1: []uint32{12}, Size: 6}, NALU_TRAIL_N: { @@ -46,6 +47,7 @@ func TestParseSliceHeader(t *testing.T) { LoopFilterAcrossSlicesEnabledFlag: true, NumEntryPointOffsets: 1, OffsetLenMinus1: 1, + CollocatedFromL0Flag: false, EntryPointOffsetMinus1: []uint32{1}, Size: 10, }, @@ -74,6 +76,7 @@ func TestParseSliceHeader(t *testing.T) { }, }, }, + CollocatedFromL0Flag: true, FiveMinusMaxNumMergeCand: 2, QpDelta: 7, NumEntryPointOffsets: 1, @@ -113,6 +116,6 @@ func TestParseSliceHeader(t *testing.T) { } } if diff := deep.Equal(wantedHdr, gotHdr); diff != nil { - t.Errorf("Got Slice Header: %+v\n Diff is %v", gotHdr, diff) + t.Errorf("Got Slice Headers: %+v\n Diff is %v", gotHdr, diff) } } diff --git a/mp4/crypto.go b/mp4/crypto.go index 2e74d4e9..ab110230 100644 --- a/mp4/crypto.go +++ b/mp4/crypto.go @@ -5,9 +5,9 @@ import ( "crypto/cipher" "encoding/binary" "fmt" - "log" "github.com/Eyevinn/mp4ff/avc" + "github.com/Eyevinn/mp4ff/hevc" ) type cryptoDir int @@ -19,7 +19,7 @@ const ( dirDec ) -// GetAVCProtectRanges for common encryption from a sample with 4-byt NALU lengths. +// GetAVCProtectRanges for common encryption from a sample with 4-byte NALU lengths. // THe spsMap and ppsMap are only needed for CBCS mode. // For scheme cenc, protection ranges must be a multiple of 16 bytes leaving header and some more in the clear // For scheme cbcs, protection range must start after the slice header. @@ -42,8 +42,7 @@ func GetAVCProtectRanges(spsMap map[uint32]*avc.SPS, ppsMap map[uint32]*avc.PPS, naluType := avc.GetNaluType(sample[pos]) var bytesToProtect uint32 = 0 clearEnd = pos + naluLength - if naluType < avc.NALU_SEI { - //VCL NALU + if avc.IsVideoNaluType(naluType) { nalu := sample[pos : pos+naluLength] switch scheme { case "cenc": @@ -80,6 +79,62 @@ func GetAVCProtectRanges(spsMap map[uint32]*avc.SPS, ppsMap map[uint32]*avc.PPS, return ssps, nil } +func GetHEVCProtectRanges(spsMap map[uint32]*hevc.SPS, ppsMap map[uint32]*hevc.PPS, + sample []byte, scheme string) ([]SubSamplePattern, error) { + var ssps []SubSamplePattern + length := len(sample) + if length < 4 { + return nil, fmt.Errorf("less than 4 bytes, No NALUs") + } + var pos uint32 = 0 + clearStart := uint32(0) + clearEnd := uint32(0) + for pos < uint32(length-4) { + naluLength := binary.BigEndian.Uint32(sample[pos : pos+4]) + pos += 4 + if int(pos+naluLength) > len(sample) { + return nil, fmt.Errorf("NALU length fields are bad") + } + naluType := hevc.GetNaluType(sample[pos]) + var bytesToProtect uint32 = 0 + clearEnd = pos + naluLength + if hevc.IsVideoNaluType(naluType) { + nalu := sample[pos : pos+naluLength] + switch scheme { + case "cenc": + if naluLength+naluHdrLen >= minClearSize+16 { + // Calculate a multiple of 16 bytes to protect + bytesToProtect = (naluLength + naluHdrLen - minClearSize) & 0xfffffff0 + if bytesToProtect > 0 { + clearEnd -= bytesToProtect + } + } + case "cbcs": + sh, err := hevc.ParseSliceHeader(nalu, spsMap, ppsMap) + if err != nil { + return nil, err + } + clearHeadSize := uint32(sh.Size) + clearEnd = pos + clearHeadSize + bytesToProtect = naluLength - clearHeadSize + default: + return nil, fmt.Errorf("unknown protect scheme %s", scheme) + } + } + if bytesToProtect > 0 { + ssps = appendProtectRange(ssps, clearEnd-clearStart, bytesToProtect) + clearStart = clearEnd + bytesToProtect + clearEnd = clearStart + } + + pos += naluLength + } + if clearEnd > clearStart { + ssps = appendProtectRange(ssps, clearEnd-clearStart, 0) + } + return ssps, nil +} + // appendProtectRange appends a SubSamplePattern to a slice of SubSamplePattern, splitting into multiple if needed. func appendProtectRange(ssps []SubSamplePattern, nrClear, nrProtected uint32) []SubSamplePattern { for { @@ -258,12 +313,13 @@ func InitProtect(init *InitSegment, key, iv []byte, scheme string, kid UUID, pss iv = make([]byte, 16) copy(iv, iv8) } - var err error ipd.Trex = moov.Mvex.Trex sinf := SinfBox{} + var mediaType string switch se := stsd.Children[0].(type) { case *VisualSampleEntryBox: + mediaType = "video" veType := se.Type() se.SetType("encv") frma := FrmaBox{DataFormat: veType} @@ -275,36 +331,21 @@ func InitProtect(init *InitSegment, key, iv []byte, scheme string, kid UUID, pss if err != nil { return nil, fmt.Errorf("get avc protect func: %w", err) } - switch scheme { - case "cenc": - ipd.Tenc = &TencBox{Version: 0, DefaultIsProtected: 1, DefaultPerSampleIVSize: 16, DefaultKID: kid} - case "cbcs": - ipd.Tenc = &TencBox{Version: 1, DefaultCryptByteBlock: 1, DefaultSkipByteBlock: 9, - DefaultIsProtected: 1, DefaultPerSampleIVSize: 0, DefaultKID: kid, - DefaultConstantIV: iv} - default: - return nil, fmt.Errorf("unknown protection mode %s", scheme) + case "hvc1", "hev1": + ipd.ProtFunc, err = getHEVCProtFunc(se.HvcC) + if err != nil { + return nil, fmt.Errorf("get hevc protect func: %w", err) } - default: return nil, fmt.Errorf("visual sample entry type %s not yet supported", veType) } case *AudioSampleEntryBox: + mediaType = "audio" aeType := se.Type() se.SetType("enca") frma := FrmaBox{DataFormat: aeType} sinf.AddChild(&frma) se.AddChild(&sinf) - switch scheme { - case "cenc": - ipd.Tenc = &TencBox{Version: 0, DefaultIsProtected: 1, DefaultPerSampleIVSize: 16, DefaultKID: kid} - case "cbcs": - ipd.Tenc = &TencBox{Version: 1, DefaultCryptByteBlock: 0, DefaultSkipByteBlock: 0, - DefaultIsProtected: 1, DefaultPerSampleIVSize: 0, DefaultKID: kid, - DefaultConstantIV: iv} - default: - return nil, fmt.Errorf("unknown protection scheme %s", scheme) - } ipd.ProtFunc = getAudioProtectRanges default: return nil, fmt.Errorf("sample entry type %s should not be encrypted", se.Type()) @@ -312,8 +353,19 @@ func InitProtect(init *InitSegment, key, iv []byte, scheme string, kid UUID, pss schi := SchiBox{} switch scheme { case "cenc": + ipd.Tenc = &TencBox{Version: 0, DefaultIsProtected: 1, DefaultPerSampleIVSize: 16, DefaultKID: kid} sinf.AddChild(&SchmBox{SchemeType: "cenc", SchemeVersion: 65536}) case "cbcs": + switch mediaType { + case "video": + ipd.Tenc = &TencBox{Version: 1, DefaultCryptByteBlock: 1, DefaultSkipByteBlock: 9, + DefaultIsProtected: 1, DefaultPerSampleIVSize: 0, DefaultKID: kid, + DefaultConstantIV: iv} + case "audio": + ipd.Tenc = &TencBox{Version: 1, DefaultCryptByteBlock: 0, DefaultSkipByteBlock: 0, + DefaultIsProtected: 1, DefaultPerSampleIVSize: 0, DefaultKID: kid, + DefaultConstantIV: iv} + } sinf.AddChild(&SchmBox{SchemeType: "cbcs", SchemeVersion: 65536}) default: return nil, fmt.Errorf("unknown protection scheme %s", scheme) @@ -326,38 +378,81 @@ func InitProtect(init *InitSegment, key, iv []byte, scheme string, kid UUID, pss return &ipd, nil } -func getSPSMap(spss [][]byte) map[uint32]*avc.SPS { +func getAVCPSMaps(spss [][]byte, ppss [][]byte) (map[uint32]*avc.SPS, map[uint32]*avc.PPS, error) { spsMap := make(map[uint32]*avc.SPS, 1) for _, spsNalu := range spss { sps, err := avc.ParseSPSNALUnit(spsNalu, false) if err != nil { - log.Fatal(err) + return nil, nil, err } spsMap[sps.ParameterID] = sps } - return spsMap -} - -func getPPSMap(ppss [][]byte, spsMap map[uint32]*avc.SPS) map[uint32]*avc.PPS { ppsMap := make(map[uint32]*avc.PPS, 1) for _, ppsNalu := range ppss { pps, err := avc.ParsePPSNALUnit(ppsNalu, spsMap) if err != nil { - log.Fatal(err) + return nil, nil, err } ppsMap[pps.PicParameterSetID] = pps } - return ppsMap + return spsMap, ppsMap, nil } func getAVCProtFunc(avcC *AvcCBox) (ProtectionRangeFunc, error) { - spsMap := getSPSMap(avcC.SPSnalus) - ppsMap := getPPSMap(avcC.PPSnalus, spsMap) + spsMap, ppsMap, err := getAVCPSMaps(avcC.SPSnalus, avcC.PPSnalus) + if err != nil { + return nil, fmt.Errorf("get avc ps maps: %w", err) + } return func(sample []byte, scheme string) ([]SubSamplePattern, error) { return GetAVCProtectRanges(spsMap, ppsMap, sample, scheme) }, nil } +func getHEVCPSMaps(arrays []hevc.NaluArray) (map[uint32]*hevc.SPS, map[uint32]*hevc.PPS, error) { + // First check SPS + spsMap := make(map[uint32]*hevc.SPS, 1) + for _, naluArray := range arrays { + naluType := naluArray.NaluType() + switch naluType { + case hevc.NALU_SPS: + for _, nalu := range naluArray.Nalus { + sps, err := hevc.ParseSPSNALUnit(nalu) + if err != nil { + return nil, nil, err + } + spsMap[uint32(sps.SpsID)] = sps + } + } + } + // Then check PPS + ppsMap := make(map[uint32]*hevc.PPS, 1) + for _, naluArray := range arrays { + naluType := naluArray.NaluType() + switch naluType { + case hevc.NALU_PPS: + for _, nalu := range naluArray.Nalus { + pps, err := hevc.ParsePPSNALUnit(nalu, spsMap) + if err != nil { + return nil, nil, err + } + ppsMap[pps.PicParameterSetID] = pps + } + } + } + return spsMap, ppsMap, nil +} + +func getHEVCProtFunc(hvcC *HvcCBox) (ProtectionRangeFunc, error) { + return func(sample []byte, scheme string) ([]SubSamplePattern, error) { + spsMap, ppsMap, err := getHEVCPSMaps(hvcC.NaluArrays) + if err != nil { + return nil, fmt.Errorf("get hevc ps maps: %w", err) + } + return GetHEVCProtectRanges(spsMap, ppsMap, sample, scheme) + }, nil + +} + func EncryptFragment(f *Fragment, key, iv []byte, ipd *InitProtectData) error { if ipd == nil { return fmt.Errorf("no protection data") @@ -402,7 +497,7 @@ func EncryptFragment(f *Fragment, key, iv []byte, ipd *InitProtectData) error { sample := fs.Data subsamplePatterns, err := ipd.ProtFunc(sample, ipd.Scheme) if err != nil { - return fmt.Errorf("get avc protect ranges: %w", err) + return fmt.Errorf("get protect ranges: %w", err) } switch ipd.Scheme { case "cenc": @@ -663,12 +758,18 @@ func ExtractInitProtectData(inSeg *InitSegment) (*InitProtectData, error) { case *VisualSampleEntryBox: sinf = box.Sinf frma := sinf.Frma - if frma.DataFormat == "avc1" { + switch frma.DataFormat { + case "avc1": ipd.ProtFunc, err = getAVCProtFunc(box.AvcC) if err != nil { return nil, fmt.Errorf("get AVC protect func: %w", err) } - } else { + case "hvc1": + ipd.ProtFunc, err = getHEVCProtFunc(box.HvcC) + if err != nil { + return nil, fmt.Errorf("get HEVC protect func: %w", err) + } + default: return nil, fmt.Errorf("unsupported video codec descriptor %s", frma.DataFormat) } case *AudioSampleEntryBox: diff --git a/mp4/crypto_test.go b/mp4/crypto_test.go index 20381aae..3dc076ea 100644 --- a/mp4/crypto_test.go +++ b/mp4/crypto_test.go @@ -96,8 +96,10 @@ func TestFindAVCSubsampleRanges(t *testing.T) { } func TestEncryptDecrypt(t *testing.T) { - videoInit := "testdata/init.mp4" - videoSeg := "testdata/1.m4s" + videoAVCInit := "testdata/init.mp4" + videoAVCSeg := "testdata/1.m4s" + videoHEVCInit := "testdata/hvc1_init.mp4" + videoHEVCSeg := "testdata/hvc1_seg_1.m4s" audioInit := "testdata/aac_init.mp4" audioSeg := "testdata/aac_1.m4s" keyHex := "00112233445566778899aabbccddeeff" @@ -125,10 +127,13 @@ func TestEncryptDecrypt(t *testing.T) { 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, hasPssh: true}, + {desc: "video AVC cenc iv8", init: videoAVCInit, seg: videoAVCSeg, scheme: "cenc", iv: ivHex8}, + {desc: "video AVC cbcs iv8", init: videoAVCInit, seg: videoAVCSeg, scheme: "cbcs", iv: ivHex8}, + {desc: "video AVC cbcs iv16", init: videoAVCInit, seg: videoAVCSeg, scheme: "cbcs", iv: ivHex16}, + {desc: "video HEVC cenc iv8", init: videoHEVCInit, seg: videoHEVCSeg, scheme: "cenc", iv: ivHex8}, + {desc: "video HEVC cbcs iv8", init: videoHEVCInit, seg: videoHEVCSeg, scheme: "cbcs", iv: ivHex8}, + {desc: "video HEVC cbcs iv16", init: videoHEVCInit, seg: videoHEVCSeg, scheme: "cbcs", iv: ivHex16}, + {desc: "audio AAC cbcs iv16", init: audioInit, seg: audioSeg, scheme: "cbcs", iv: ivHex16, hasPssh: true}, } for _, c := range testCases { t.Run(c.desc, func(t *testing.T) { diff --git a/mp4/sinf.go b/mp4/sinf.go index c9362fe5..b76a40b6 100644 --- a/mp4/sinf.go +++ b/mp4/sinf.go @@ -68,12 +68,12 @@ func (b *SinfBox) GetChildren() []Box { return b.Children } -// Encode - write minf container to w +// Encode - write sinf container to w func (b *SinfBox) Encode(w io.Writer) error { return EncodeContainer(b, w) } -// Encode - write minf container to sw +// Encode - write sinf container to sw func (b *SinfBox) EncodeSW(sw bits.SliceWriter) error { return EncodeContainerSW(b, sw) } diff --git a/mp4/testdata/hvc1_init.mp4 b/mp4/testdata/hvc1_init.mp4 new file mode 100644 index 00000000..c19877d0 Binary files /dev/null and b/mp4/testdata/hvc1_init.mp4 differ diff --git a/mp4/testdata/hvc1_seg_1.m4s b/mp4/testdata/hvc1_seg_1.m4s new file mode 100644 index 00000000..a96a0724 Binary files /dev/null and b/mp4/testdata/hvc1_seg_1.m4s differ