Skip to content

Commit

Permalink
feat: add support for VP8 and VP9 video codecs
Browse files Browse the repository at this point in the history
Added support for VP8 and VP9 video codecs in MP4 containers:
- Added vp08 and vp09 boxes to decoder map
- Added VP Codec Configuration Box (vpcC)
- Updated StsdBox to support VP8/VP9 codec configurations
  • Loading branch information
tobbee committed Jan 28, 2025
1 parent 9019d6c commit bfcbba1
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 32 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- cmd/mp4ff-decrypt -key option instead of -k. Takes hex or base64 value
- cmd/mp4ff-encrypt -key and -kid options now take hex or bae64 values


### Added

- mp4.SetUUID() can take base64 string as well as hex-encoded.
- Support for weird dac3 box with initial 4 zero bytes (Issue #395)
- Lots of fuzzying tests and changes to avoid panic on bad input data.
- Lots of fuzzying tests and changes to avoid panic on bad input data
- Support for VP8 and VP9 video codecs (vp08 and vp09 boxes)
- Support for VP Codec Configuration Box (vpcC)

### Fixed

Expand Down
27 changes: 15 additions & 12 deletions mp4/box.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,24 @@ var decoders map[string]BoxDecoder

func init() {
decoders = map[string]BoxDecoder{
"\xa9ART": DecodeGenericContainerBox,
"\xa9nam": DecodeGenericContainerBox,
"\xa9too": DecodeGenericContainerBox,
"\xa9cpy": DecodeGenericContainerBox,
"ac-3": DecodeAudioSampleEntry,
"alou": DecodeAlou,
"av01": DecodeVisualSampleEntry,
"av1C": DecodeAv1C,
"avc1": DecodeVisualSampleEntry,
"avc3": DecodeVisualSampleEntry,
"avcC": DecodeAvcC,
"av1C": DecodeAv1C,
"btrt": DecodeBtrt,
"cdat": DecodeCdat,
"cdsc": DecodeTrefType,
"clap": DecodeClap,
"cslg": DecodeCslg,
"co64": DecodeCo64,
"colr": DecodeColr,
"cslg": DecodeCslg,
"ctim": DecodeCtim,
"ctts": DecodeCtts,
"dac3": DecodeDac3,
Expand All @@ -43,15 +47,15 @@ func init() {
"dpnd": DecodeTrefType,
"dref": DecodeDref,
"ec-3": DecodeAudioSampleEntry,
"elng": DecodeElng,
"esds": DecodeEsds,
"edts": DecodeEdts,
"elng": DecodeElng,
"elst": DecodeElst,
"emeb": DecodeEmeb,
"emib": DecodeEmib,
"emsg": DecodeEmsg,
"enca": DecodeAudioSampleEntry,
"encv": DecodeVisualSampleEntry,
"emsg": DecodeEmsg,
"esds": DecodeEsds,
"evte": DecodeEvte,
"font": DecodeTrefType,
"free": DecodeFree,
Expand All @@ -61,8 +65,8 @@ func init() {
"hev1": DecodeVisualSampleEntry,
"hind": DecodeTrefType,
"hint": DecodeTrefType,
"hvcC": DecodeHvcC,
"hvc1": DecodeVisualSampleEntry,
"hvcC": DecodeHvcC,
"iden": DecodeIden,
"ilst": DecodeIlst,
"iods": DecodeUnknown,
Expand All @@ -82,10 +86,10 @@ func init() {
"minf": DecodeMinf,
"moof": DecodeMoof,
"moov": DecodeMoov,
"mp4a": DecodeAudioSampleEntry,
"mpod": DecodeTrefType,
"mvex": DecodeMvex,
"mvhd": DecodeMvhd,
"mp4a": DecodeAudioSampleEntry,
"nmhd": DecodeNmhd,
"pasp": DecodePasp,
"payl": DecodePayl,
Expand All @@ -105,9 +109,9 @@ func init() {
"skip": DecodeFree,
"smhd": DecodeSmhd,
"ssix": DecodeSsix,
"sthd": DecodeSthd,
"stbl": DecodeStbl,
"stco": DecodeStco,
"sthd": DecodeSthd,
"stpp": DecodeStpp,
"stsc": DecodeStsc,
"stsd": DecodeStsd,
Expand Down Expand Up @@ -137,17 +141,16 @@ func init() {
"vdep": DecodeTrefType,
"vlab": DecodeVlab,
"vmhd": DecodeVmhd,
"vp08": DecodeVisualSampleEntry,
"vp09": DecodeVisualSampleEntry,
"vpcC": DecodeVppC,
"vplx": DecodeTrefType,
"vsid": DecodeVsid,
"vtta": DecodeVtta,
"vttc": DecodeVttc,
"vttC": DecodeVttC,
"vtte": DecodeVtte,
"wvtt": DecodeWvtt,
"\xa9cpy": DecodeGenericContainerBox,
"\xa9nam": DecodeGenericContainerBox,
"\xa9too": DecodeGenericContainerBox,
"\xa9ART": DecodeGenericContainerBox,
}
}

Expand Down
31 changes: 17 additions & 14 deletions mp4/boxsr.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@ var decodersSR map[string]BoxDecoderSR

func init() {
decodersSR = map[string]BoxDecoderSR{
"\xa9ART": DecodeGenericContainerBoxSR,
"\xa9cpy": DecodeGenericContainerBoxSR,
"\xa9nam": DecodeGenericContainerBoxSR,
"\xa9too": DecodeGenericContainerBoxSR,
"ac-3": DecodeAudioSampleEntrySR,
"alou": DecodeAlouBoxSR,
"av01": DecodeVisualSampleEntrySR,
"av1C": DecodeAv1CSR,
"avc1": DecodeVisualSampleEntrySR,
"avc3": DecodeVisualSampleEntrySR,
"alou": DecodeAlouBoxSR,
"avcC": DecodeAvcCSR,
"av1C": DecodeAv1CSR,
"btrt": DecodeBtrtSR,
"cdat": DecodeCdatSR,
"cdsc": DecodeTrefTypeSR,
"clap": DecodeClapSR,
"cslg": DecodeCslgSR,
"co64": DecodeCo64SR,
"colr": DecodeColrSR,
"cslg": DecodeCslgSR,
"ctim": DecodeCtimSR,
"ctts": DecodeCttsSR,
"dac3": DecodeDac3SR,
Expand All @@ -34,15 +38,15 @@ func init() {
"dpnd": DecodeTrefTypeSR,
"dref": DecodeDrefSR,
"ec-3": DecodeAudioSampleEntrySR,
"edts": DecodeEdtsSR,
"elng": DecodeElngSR,
"elst": DecodeElstSR,
"emeb": DecodeEmebSR,
"emib": DecodeEmibSR,
"esds": DecodeEsdsSR,
"edts": DecodeEdtsSR,
"elst": DecodeElstSR,
"emsg": DecodeEmsgSR,
"enca": DecodeAudioSampleEntrySR,
"encv": DecodeVisualSampleEntrySR,
"emsg": DecodeEmsgSR,
"esds": DecodeEsdsSR,
"evte": DecodeEvteSR,
"font": DecodeTrefTypeSR,
"free": DecodeFreeSR,
Expand All @@ -52,8 +56,8 @@ func init() {
"hev1": DecodeVisualSampleEntrySR,
"hind": DecodeTrefTypeSR,
"hint": DecodeTrefTypeSR,
"hvcC": DecodeHvcCSR,
"hvc1": DecodeVisualSampleEntrySR,
"hvcC": DecodeHvcCSR,
"iden": DecodeIdenSR,
"ilst": DecodeIlstSR,
"iods": DecodeUnknownSR,
Expand All @@ -73,10 +77,10 @@ func init() {
"minf": DecodeMinfSR,
"moof": DecodeMoofSR,
"moov": DecodeMoovSR,
"mp4a": DecodeAudioSampleEntrySR,
"mpod": DecodeTrefTypeSR,
"mvex": DecodeMvexSR,
"mvhd": DecodeMvhdSR,
"mp4a": DecodeAudioSampleEntrySR,
"nmhd": DecodeNmhdSR,
"pasp": DecodePaspSR,
"payl": DecodePaylSR,
Expand All @@ -96,9 +100,9 @@ func init() {
"skip": DecodeFreeSR,
"smhd": DecodeSmhdSR,
"ssix": DecodeSsixSR,
"sthd": DecodeSthdSR,
"stbl": DecodeStblSR,
"stco": DecodeStcoSR,
"sthd": DecodeSthdSR,
"stpp": DecodeStppSR,
"stsc": DecodeStscSR,
"stsd": DecodeStsdSR,
Expand Down Expand Up @@ -128,17 +132,16 @@ func init() {
"vdep": DecodeTrefTypeSR,
"vlab": DecodeVlabSR,
"vmhd": DecodeVmhdSR,
"vp08": DecodeVisualSampleEntrySR,
"vp09": DecodeVisualSampleEntrySR,
"vpcC": DecodeVppCSR,
"vplx": DecodeTrefTypeSR,
"vsid": DecodeVsidSR,
"vtta": DecodeVttaSR,
"vttc": DecodeVttcSR,
"vttC": DecodeVttCSR,
"vtte": DecodeVtteSR,
"wvtt": DecodeWvttSR,
"\xa9cpy": DecodeGenericContainerBoxSR,
"\xa9nam": DecodeGenericContainerBoxSR,
"\xa9too": DecodeGenericContainerBoxSR,
"\xa9ART": DecodeGenericContainerBoxSR,
}
}

Expand Down
21 changes: 21 additions & 0 deletions mp4/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mp4

import (
"bytes"
"encoding/binary"
"flag"
"os"
"testing"
Expand Down Expand Up @@ -128,6 +129,26 @@ func assertError(t *testing.T, err error, msg string) {
}
}

func changeBoxSizeAndAssertError(t *testing.T, data []byte, pos uint64, newSize uint32, errMsg string) {
t.Helper()
raw := make([]byte, len(data))
copy(raw, data)
binary.BigEndian.PutUint32(raw[pos:pos+4], newSize)
assertBoxDecodeError(t, raw, pos, errMsg)
}

func assertBoxDecodeError(t *testing.T, data []byte, pos uint64, errMsg string) {
t.Helper()
_, err := DecodeBox(pos, bytes.NewBuffer(data))
if err == nil || err.Error() != errMsg {
t.Errorf("DecodeBox: Expected error msg: %q", errMsg)
}
_, err = DecodeBoxSR(pos, bits.NewFixedSliceReader(data))
if err == nil || err.Error() != errMsg {
t.Errorf("DecodeBoxSR: Expected error msg: %q", errMsg)
}
}

// writeGolden - write golden file that to be used for later tests
func writeGolden(t *testing.T, goldenAssetPath string, data []byte) error {
t.Helper()
Expand Down
4 changes: 4 additions & 0 deletions mp4/stsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type StsdBox struct {
Av01 *VisualSampleEntryBox
// Encv is a pointer to a box with name encv
Encv *VisualSampleEntryBox
// VpXX is a pointer to a box with name vp08 or vp09 (VP8 or VP9 video)
VpXX *VisualSampleEntryBox
// Mp4a is a pointer to a box with name mp4a
Mp4a *AudioSampleEntryBox
// AC3 is a pointer to a box with name ac-3
Expand Down Expand Up @@ -57,6 +59,8 @@ func (s *StsdBox) AddChild(box Box) {
s.Encv = box.(*VisualSampleEntryBox)
case "av01":
s.Av01 = box.(*VisualSampleEntryBox)
case "vp08", "vp09":
s.VpXX = box.(*VisualSampleEntryBox)
case "mp4a":
s.Mp4a = box.(*AudioSampleEntryBox)
case "ac-3":
Expand Down
18 changes: 18 additions & 0 deletions mp4/stsd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mp4

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

"github.com/Eyevinn/mp4ff/aac"
Expand Down Expand Up @@ -72,3 +73,20 @@ func TestStsdEncodeDecode(t *testing.T) {
t.Errorf("Expected nil, got %v", btrt)
}
}

func TestStsdVP9(t *testing.T) {
hexData := "" +
"000000a87374736400000000000000010000009876703039000000000000" +
"000100000000000000000000000000000000050002d00048000000480000" +
"000000000001184c61766336312e31392e313030206c69627670782d7670" +
"39000000000000000018ffff000000147670634301000000001f80020202" +
"00000000000a6669656c0100000000107061737000000001000000010000" +
"001462747274000000000010152200101522"

binData, err := hex.DecodeString(hexData)
if err != nil {
t.Error(err)
}

cmpAfterDecodeEncodeBox(t, binData)
}
10 changes: 6 additions & 4 deletions mp4/visualsampleentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type VisualSampleEntryBox struct {
AvcC *AvcCBox
HvcC *HvcCBox
Av1C *Av1CBox
VppC *VppCBox
Btrt *BtrtBox
Clap *ClapBox
Pasp *PaspBox
Expand Down Expand Up @@ -63,6 +64,8 @@ func (b *VisualSampleEntryBox) AddChild(child Box) {
b.HvcC = box
case *Av1CBox:
b.Av1C = box
case *VppCBox:
b.VppC = box
case *BtrtBox:
b.Btrt = box
case *ClapBox:
Expand All @@ -72,7 +75,6 @@ func (b *VisualSampleEntryBox) AddChild(child Box) {
case *SinfBox:
b.Sinf = box
}

b.Children = append(b.Children, child)
}

Expand Down Expand Up @@ -112,7 +114,7 @@ func DecodeVisualSampleEntrySR(hdr BoxHeader, startPos uint64, sr bits.SliceRead
}
b.CompressorName = sr.ReadFixedLengthString(int(compressorNameLength))
sr.SkipBytes(int(31 - compressorNameLength))
sr.ReadUint16() // depth == 0x0018
sr.SkipBytes(2) // Skip depth
sr.ReadUint16() // pre_defined == -1

// Now there may be clap and pasp boxes
Expand Down Expand Up @@ -179,7 +181,7 @@ func (b *VisualSampleEntryBox) Encode(w io.Writer) error {
sw.WriteUint8(compressorNameLength)
sw.WriteString(b.CompressorName, false)
sw.WriteZeroBytes(int(31 - compressorNameLength))
sw.WriteUint16(0x0018) // depth == 0x0018
sw.WriteUint16(0x0018) // depth
sw.WriteUint16(0xffff) // pre_defined == -1 //86 bytes

_, err = w.Write(buf[:sw.Offset()]) // Only write written bytes
Expand Down Expand Up @@ -218,7 +220,7 @@ func (b *VisualSampleEntryBox) EncodeSW(sw bits.SliceWriter) error {
sw.WriteUint8(compressorNameLength)
sw.WriteString(b.CompressorName, false)
sw.WriteZeroBytes(int(31 - compressorNameLength))
sw.WriteUint16(0x0018) // depth == 0x0018
sw.WriteUint16(0x0018) // depth
sw.WriteUint16(0xffff) // pre_defined == -1 //86 bytes

// Next output child boxes in order
Expand Down
Loading

0 comments on commit bfcbba1

Please sign in to comment.