1
1
mirror of https://github.com/wader/fq.git synced 2024-11-26 21:55:57 +03:00
fq/format/matroska/matroska.go
Mattias Wadman 8e0dde03d0 decode: Support multiple format args and some rename and refactor
This will allow passing both cli options and format options to sub decoder.
Ex: pass keylog option to a tls decoder when decoding a pcap.
Ex: pass decode options to a format inside a http body inside a pcap.

Add ArgAs method to lookup argument based on type. This also makes the format
decode function have same signature as sub decoders in the decode API.

This change decode.Format a bit:
DecodeFn is now just func(d *D) any
DecodeInArg renamed to DefaultInArg
2023-02-18 21:38:51 +01:00

567 lines
18 KiB
Go

package matroska
// https://tools.ietf.org/html/draft-ietf-cellar-ebml-00
// https://matroska.org/technical/specs/index.html
// https://www.matroska.org/technical/basics.html
// https://www.matroska.org/technical/codec_specs.html
// https://wiki.xiph.org/MatroskaOpus
// TODO: refactor simepleblock/block to just defer decode etc?
// TODO: CRC
// TODO: handle garbage (see tcl and example files)
// TODO: could use md5 here somehow, see flac.go
import (
"embed"
"fmt"
"github.com/wader/fq/format"
"github.com/wader/fq/format/matroska/ebml"
"github.com/wader/fq/format/matroska/ebml_matroska"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/ranges"
"github.com/wader/fq/pkg/scalar"
)
//go:embed matroska.jq
//go:embed matroska.md
var matroskaFS embed.FS
var aacFrameFormat decode.Group
var av1CCRFormat decode.Group
var av1FrameFormat decode.Group
var flacFrameFormat decode.Group
var flacMetadatablocksFormat decode.Group
var imageFormat decode.Group
var mp3FrameFormat decode.Group
var mpegASCFrameFormat decode.Group
var mpegAVCAUFormat decode.Group
var mpegAVCDCRFormat decode.Group
var mpegHEVCDCRFormat decode.Group
var mpegHEVCSampleFormat decode.Group
var mpegPESPacketSampleFormat decode.Group
var mpegSPUFrameFormat decode.Group
var opusPacketFrameFormat decode.Group
var vorbisPacketFormat decode.Group
var vp8FrameFormat decode.Group
var vp9CFMFormat decode.Group
var vp9FrameFormat decode.Group
var codecToFormat map[string]*decode.Group
func init() {
interp.RegisterFormat(decode.Format{
Name: format.MATROSKA,
Description: "Matroska file",
Groups: []string{format.PROBE},
DecodeFn: matroskaDecode,
DefaultInArg: format.MatroskaIn{
DecodeSamples: true,
},
Dependencies: []decode.Dependency{
{Names: []string{format.AAC_FRAME}, Group: &aacFrameFormat},
{Names: []string{format.AV1_CCR}, Group: &av1CCRFormat},
{Names: []string{format.AV1_FRAME}, Group: &av1FrameFormat},
{Names: []string{format.AVC_AU}, Group: &mpegAVCAUFormat},
{Names: []string{format.AVC_DCR}, Group: &mpegAVCDCRFormat},
{Names: []string{format.FLAC_FRAME}, Group: &flacFrameFormat},
{Names: []string{format.FLAC_METADATABLOCKS}, Group: &flacMetadatablocksFormat},
{Names: []string{format.HEVC_AU}, Group: &mpegHEVCSampleFormat},
{Names: []string{format.HEVC_DCR}, Group: &mpegHEVCDCRFormat},
{Names: []string{format.IMAGE}, Group: &imageFormat},
{Names: []string{format.MP3_FRAME}, Group: &mp3FrameFormat},
{Names: []string{format.MPEG_ASC}, Group: &mpegASCFrameFormat},
{Names: []string{format.MPEG_PES_PACKET}, Group: &mpegPESPacketSampleFormat},
{Names: []string{format.MPEG_SPU}, Group: &mpegSPUFrameFormat},
{Names: []string{format.OPUS_PACKET}, Group: &opusPacketFrameFormat},
{Names: []string{format.VORBIS_PACKET}, Group: &vorbisPacketFormat},
{Names: []string{format.VP8_FRAME}, Group: &vp8FrameFormat},
{Names: []string{format.VP9_CFM}, Group: &vp9CFMFormat},
{Names: []string{format.VP9_FRAME}, Group: &vp9FrameFormat},
},
})
interp.RegisterFS(matroskaFS)
codecToFormat = map[string]*decode.Group{
"A_VORBIS": &vorbisPacketFormat,
"A_MPEG/L3": &mp3FrameFormat,
"A_FLAC": &flacFrameFormat,
"A_AAC": &aacFrameFormat,
"A_OPUS": &opusPacketFrameFormat,
"V_VP8": &vp8FrameFormat,
"V_VP9": &vp9FrameFormat,
"V_AV1": &av1FrameFormat,
"V_VOBSUB": &mpegSPUFrameFormat,
"V_MPEG4/ISO/AVC": &mpegAVCAUFormat,
"V_MPEGH/ISO/HEVC": &mpegHEVCSampleFormat,
"V_MPEG2": &mpegPESPacketSampleFormat,
"S_VOBSUB": &mpegSPUFrameFormat,
}
}
const (
lacingTypeNone = 0b00
lacingTypeXiph = 0b01
lacingTypeFixed = 0b10
lacingTypeEBML = 0b11
)
var lacingTypeNames = scalar.UintMapSymStr{
lacingTypeNone: "none",
lacingTypeXiph: "xiph",
lacingTypeFixed: "fixed",
lacingTypeEBML: "ebml",
}
const tagSizeUnknown = 0xffffffffffffff
func decodeLacingFn(d *decode.D, lacingType int, fn func(d *decode.D)) {
if lacingType == lacingTypeNone {
fn(d)
return
}
// -1 size means rest of buffer, used last sometimes
var laceSizes []int64
switch lacingType {
case lacingTypeXiph:
numLaces := int(d.FieldU8("num_laces"))
d.FieldArray("lace_sizes", func(d *decode.D) {
for i := 0; i < numLaces; i++ {
s := int64(d.FieldUintFn("lace_size", decodeXiphLaceSize))
laceSizes = append(laceSizes, s)
}
laceSizes = append(laceSizes, -1)
})
case lacingTypeEBML:
numLaces := int(d.FieldU8("num_laces"))
d.FieldArray("lace_sizes", func(d *decode.D) {
s := int64(d.FieldUintFn("lace_size", decodeVint)) // first is unsigned, not ranged shifted
laceSizes = append(laceSizes, s)
for i := 0; i < numLaces-1; i++ {
d := int64(d.FieldUintFn("lace_size_delta", decodeRawVint))
// range shifting
switch {
case d&0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1000_0000 == 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_1000_0000:
// value -(2^6^-1) to 2^6^-1 (ie 0_to 2^7^-2 minus 2^6^-1, half of the range)
d -= 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_1011_1111
case d&0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1100_0000_0000_0000 == 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0100_0000_0000_0000:
// value -(2^13^-1) to 2^13^-1
d -= 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0101_1111_1111_1111
case d&0b1111_1111_1111_1111_1111_1111_1111_1111_1110_0000_0000_0000_0000_0000 == 0b0000_0000_0000_0000_0000_0000_0000_0000_0010_0000_0000_0000_0000_0000:
// value -(2^20^-1) to 2^20^-1
d -= 0b0000_0000_0000_0000_0000_0000_0000_0000_0010_1111_1111_1111_1111_1111
case d&0b1111_1111_1111_1111_1111_1111_1111_0000_0000_0000_0000_0000_0000_0000 == 0b0000_0000_0000_0000_0000_0000_0001_0000_0000_0000_0000_0000_0000_0000:
// value -(2^27^-1) to 2^27^-1
d -= 0b0000_0000_0000_0000_0000_0000_0001_0111_1111_1111_1111_1111_1111_1111
case d&0b1111_1111_1111_1111_1111_1000_0000_0000_0000_0000_0000_0000_0000_0000 == 0b0000_0000_0000_0000_0000_1000_0000_0000_0000_0000_0000_0000_0000_0000:
// value -(2^34^-1) to 2^34^-1
d -= 0b0000_0000_0000_0000_0000_1011_1111_1111_1111_1111_1111_1111_1111_1111
case d&0b1111_1111_1111_1100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000 == 0b0000_0000_0000_0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000:
// value -(2^41^-1) to 2^41^-1
d -= 0b0000_0000_0000_0101_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111
case d&0b1111_1110_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000 == 0b0000_0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000:
// value -(2^48^-1) to 2^48^-1
d -= 0b0000_0010_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111
}
s += d
laceSizes = append(laceSizes, s)
}
laceSizes = append(laceSizes, -1)
})
case lacingTypeFixed:
numLaces := int(d.FieldU8("num_laces"))
fixedSize := (d.BitsLeft() / 8) / int64(numLaces+1)
for i := 0; i < numLaces+1; i++ {
laceSizes = append(laceSizes, fixedSize)
}
default:
panic("unreachable")
}
d.FieldArray("laces", func(d *decode.D) {
for _, laceSize := range laceSizes {
s := laceSize * 8
if laceSize == -1 {
s = d.BitsLeft()
}
d.FramedFn(s, fn)
}
})
}
// TODO: smarter?
func decodeRawVintWidth(d *decode.D) (uint64, int) {
n := d.U8()
w := 1
for i := 0; i <= 7 && (n&(1<<(7-i))) == 0; i++ {
w++
}
for i := 1; i < w; i++ {
n = n<<8 | d.U8()
}
return n, w
}
func decodeRawVint(d *decode.D) uint64 {
n, _ := decodeRawVintWidth(d)
return n
}
func peekRawVint(d *decode.D) uint64 {
n, w := decodeRawVintWidth(d)
d.SeekRel(int64(-w) * 8)
return n
}
func decodeVint(d *decode.D) uint64 {
n, w := decodeRawVintWidth(d)
m := (uint64(1<<((w-1)*8+(8-w))) - 1)
return n & m
}
func decodeXiphLaceSize(d *decode.D) uint64 {
var s uint64
for {
n := d.U8()
s += n
if n < 255 {
return s
}
}
}
type track struct {
parentD *decode.D
number int
codec string
codecPrivatePos int64
codecPrivateTagSize int64
formatInArg any
}
type block struct {
d *decode.D
r ranges.Range
simple bool
}
type decodeContext struct {
currentTrack *track
tracks []*track
blocks []block
}
func decodeMaster(d *decode.D, bitsLimit int64, elm *ebml.Master, unknownSize bool, dc *decodeContext) {
tagEndBit := d.Pos() + bitsLimit
d.FieldArray("elements", func(d *decode.D) {
for d.Pos() < tagEndBit && !d.End() {
// current we assume master with unknown size has ended if a valid parent is found
// TODO:
// https://github.com/ietf-wg-cellar/ebml-specification/blob/master/specification.markdown#unknown-data-size
// > Any valid EBML Element according to the EBML Schema, Global Elements excluded, that
// > is not a Descendant Element of the Unknown-Sized Element but shares a common direct
// > parent, such as a Top-Level Element.
// TODO: What to do if peeked is unknown?
// TODO: Handle garbage between element
if unknownSize {
peekTagID := peekRawVint(d)
_, validParent := ebml.FindParentID(ebml_matroska.IDToElement, elm.GetID(), ebml.ID(peekTagID))
if validParent {
break
}
}
d.FieldStruct("element", func(d *decode.D) {
var childElm ebml.Element
childElm = &ebml.Unknown{}
tagID := d.FieldUintFn("id", decodeRawVint, scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
n := s.Actual
var ok bool
childElm, ok = elm.Master[ebml.ID(n)]
if !ok {
childElm, ok = ebml.Global.Master[ebml.ID(n)]
if !ok {
childElm = &ebml.Unknown{}
return scalar.Uint{Actual: n, DisplayFormat: scalar.NumberHex, Description: "Unknown"}, nil
}
}
return scalar.Uint{
Actual: n,
DisplayFormat: scalar.NumberHex,
Sym: childElm.GetName(),
Description: childElm.GetDefinition(),
}, nil
}))
d.FieldValueStr("type", childElm.GetType())
if tagID == ebml_matroska.TrackEntryID {
dc.currentTrack = &track{}
dc.tracks = append(dc.tracks, dc.currentTrack)
}
const maxStringTagSize = 100 * 1024 * 1024
tagSize := d.FieldUintFn("size", decodeVint, scalar.UintMapDescription{
0xffffffffffffff: "Unknown size",
})
unknownSize := tagSize == tagSizeUnknown
if unknownSize {
tagSize = uint64(d.BitsLeft() / 8)
}
// assert sane tag size
// TODO: strings are limited for now because they are read into memory
switch childElm.(type) {
case *ebml.Integer,
*ebml.Uinteger,
*ebml.Float:
if tagSize > 8 {
d.Fatalf("invalid tagSize %d for number type", tagSize)
}
case *ebml.String,
*ebml.UTF8:
if tagSize > maxStringTagSize {
d.Errorf("tagSize %d > maxStringTagSize %d", tagSize, maxStringTagSize)
}
case *ebml.Unknown,
*ebml.Binary,
*ebml.Date,
*ebml.Master:
// nop
}
switch childElm := childElm.(type) {
case *ebml.Unknown:
d.FieldRawLen("data", int64(tagSize)*8)
case *ebml.Integer:
var sm []scalar.SintMapper
if childElm.Enums != nil {
sm = append(sm, scalar.SintFn(func(s scalar.Sint) (scalar.Sint, error) {
if e, ok := childElm.Enums[s.Actual]; ok {
s.Sym = e.Name
s.Description = e.Description
}
return s, nil
}))
}
d.FieldS("value", int(tagSize)*8, sm...)
case *ebml.Uinteger:
var sm []scalar.UintMapper
if childElm.Enums != nil {
sm = append(sm, scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
if e, ok := childElm.Enums[s.Actual]; ok {
s.Sym = e.Name
s.Description = e.Description
}
return s, nil
}))
}
v := d.FieldU("value", int(tagSize)*8, sm...)
if dc.currentTrack != nil && tagID == ebml_matroska.TrackNumberID {
dc.currentTrack.number = int(v)
}
case *ebml.Float:
d.FieldF("value", int(tagSize)*8)
case *ebml.String:
var sm []scalar.StrMapper
sm = append(sm, scalar.StrFn(func(s scalar.Str) (scalar.Str, error) {
if e, ok := childElm.Enums[s.Actual]; ok {
s.Sym = e.Name
s.Description = e.Description
}
return s, nil
}))
v := d.FieldUTF8("value", int(tagSize), sm...)
if dc.currentTrack != nil && tagID == ebml_matroska.CodecIDID {
dc.currentTrack.codec = v
}
case *ebml.UTF8:
d.FieldUTF8NullFixedLen("value", int(tagSize))
case *ebml.Date:
// TODO:
/*
proc type_date {size label _extra} {
set s [clock scan {2001-01-01 00:00:00}]
set frac 0
switch $size {
0 {}
8 {
set nano [int64]
set s [clock add $s [expr $nano/1000000000] seconds]
set frac [expr ($nano%1000000000)/1000000000.0]
}
default {
bytes $size $label
return
}
}
entry $label "[clock format $s] ${frac}s" $size [expr [pos]-$size]
}
*/
d.FieldRawLen("value", int64(tagSize)*8)
case *ebml.Binary:
switch tagID {
case ebml_matroska.SimpleBlockID:
dc.blocks = append(dc.blocks, block{
d: d,
r: ranges.Range{Start: d.Pos(), Len: int64(tagSize) * 8},
simple: true,
})
d.SeekRel(int64(tagSize) * 8)
case ebml_matroska.BlockID:
dc.blocks = append(dc.blocks, block{
d: d,
r: ranges.Range{Start: d.Pos(), Len: int64(tagSize) * 8},
})
d.SeekRel(int64(tagSize) * 8)
case ebml_matroska.CodecPrivateID:
if dc.currentTrack != nil {
dc.currentTrack.parentD = d
dc.currentTrack.codecPrivatePos = d.Pos()
dc.currentTrack.codecPrivateTagSize = int64(tagSize) * 8
}
d.SeekRel(int64(tagSize) * 8)
case ebml_matroska.FileDataID:
d.FieldFormatLen("value", int64(tagSize)*8, imageFormat, nil)
default:
d.FieldRawLen("value", int64(tagSize)*8)
}
case *ebml.Master:
decodeMaster(d, int64(tagSize)*8, childElm, unknownSize, dc)
}
})
}
})
}
func matroskaDecode(d *decode.D) any {
var mi format.MatroskaIn
d.ArgAs(&mi)
ebmlHeaderID := uint64(0x1a45dfa3)
if d.PeekBits(32) != ebmlHeaderID {
d.Errorf("no EBML header found")
}
dc := &decodeContext{tracks: []*track{}}
decodeMaster(d, d.BitsLeft(), ebml_matroska.RootElement, false, dc)
trackNumberToTrack := map[int]*track{}
for _, t := range dc.tracks {
trackNumberToTrack[t.number] = t
}
for _, t := range dc.tracks {
// no CodecPrivate found
if t.parentD == nil {
continue
}
// TODO: refactor, one DecodeRangeFn or refactor to add FieldFormatRangeFn?
switch t.codec {
case "A_VORBIS":
t.parentD.RangeFn(t.codecPrivatePos, t.codecPrivateTagSize, func(d *decode.D) {
decodeLacingFn(d, lacingTypeXiph, func(d *decode.D) {
if mi.DecodeSamples {
d.FieldFormat("packet", vorbisPacketFormat, nil)
} else {
d.FieldRawLen("packet", d.BitsLeft())
}
})
})
case "A_AAC":
_, v := t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, mpegASCFrameFormat, nil)
mpegASCOut, ok := v.(format.MPEGASCOut)
if !ok {
panic(fmt.Sprintf("expected mpegASCOut got %#+v", v))
}
//nolint:gosimple
t.formatInArg = format.AACFrameIn{ObjectType: mpegASCOut.ObjectType}
case "A_OPUS":
t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, opusPacketFrameFormat, nil)
case "A_FLAC":
t.parentD.RangeFn(t.codecPrivatePos, t.codecPrivateTagSize, func(d *decode.D) {
d.FieldStruct("value", func(d *decode.D) {
d.FieldUTF8("magic", 4, d.StrAssert("fLaC"))
_, v := d.FieldFormat("metadatablocks", flacMetadatablocksFormat, nil)
flacMetadatablockOut, ok := v.(format.FlacMetadatablocksOut)
if !ok {
panic(fmt.Sprintf("expected FlacMetadatablockOut got %#+v", v))
}
if flacMetadatablockOut.HasStreamInfo {
t.formatInArg = format.FlacFrameIn{BitsPerSample: int(flacMetadatablockOut.StreamInfo.BitsPerSample)}
}
})
})
case "V_MPEG4/ISO/AVC":
_, v := t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, mpegAVCDCRFormat, nil)
avcDcrOut, ok := v.(format.AvcDcrOut)
if !ok {
panic(fmt.Sprintf("expected AvcDcrOut got %#+v", v))
}
t.formatInArg = format.AvcAuIn{LengthSize: avcDcrOut.LengthSize} //nolint:gosimple
case "V_MPEGH/ISO/HEVC":
_, v := t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, mpegHEVCDCRFormat, nil)
hevcDcrOut, ok := v.(format.HevcDcrOut)
if !ok {
panic(fmt.Sprintf("expected HevcDcrOut got %#+v", v))
}
t.formatInArg = format.HevcAuIn{LengthSize: hevcDcrOut.LengthSize} //nolint:gosimple
case "V_AV1":
t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, av1CCRFormat, nil)
case "V_VP9":
t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, vp9CFMFormat, nil)
default:
t.parentD.RangeFn(t.codecPrivatePos, t.codecPrivateTagSize, func(d *decode.D) {
d.FieldRawLen("value", d.BitsLeft())
})
}
}
for _, b := range dc.blocks {
b.d.RangeFn(b.r.Start, b.r.Len, func(d *decode.D) {
var lacing uint64
trackNumber := d.FieldUintFn("track_number", decodeVint)
d.FieldU16("timestamp")
if b.simple {
d.FieldStruct("flags", func(d *decode.D) {
d.FieldBool("key_frame")
d.FieldU3("reserved")
d.FieldBool("invisible")
lacing = d.FieldU2("lacing", lacingTypeNames)
d.FieldBool("discardable")
})
} else {
d.FieldStruct("flags", func(d *decode.D) {
d.FieldU4("reserved")
d.FieldBool("invisible")
lacing = d.FieldU2("lacing", lacingTypeNames)
d.FieldBool("not_used")
})
}
var f *decode.Group
var track *track
track, trackOk := trackNumberToTrack[int(trackNumber)]
if trackOk {
f = codecToFormat[track.codec]
}
decodeLacingFn(d, int(lacing), func(d *decode.D) {
if mi.DecodeSamples && f != nil {
d.FieldFormat("packet", *f, track.formatInArg)
} else {
d.FieldRawLen("packet", d.BitsLeft())
}
})
})
}
return nil
}