1
1
mirror of https://github.com/wader/fq.git synced 2024-10-27 12:19:52 +03:00
fq/format/ogg/ogg.go
2021-09-12 13:08:50 +02:00

151 lines
3.7 KiB
Go

package ogg
// https://xiph.org/ogg/doc/framing.html
// TODO: audio/ogg"
import (
"bytes"
"github.com/wader/fq/format"
"github.com/wader/fq/format/registry"
"github.com/wader/fq/pkg/bitio"
"github.com/wader/fq/pkg/decode"
)
var oggPage []*decode.Format
var vorbisPacket []*decode.Format
var opusPacket []*decode.Format
func init() {
registry.MustRegister(&decode.Format{
Name: format.OGG,
Description: "OGG file",
Groups: []string{format.PROBE},
DecodeFn: decodeOgg,
Dependencies: []decode.Dependency{
{Names: []string{format.OGG_PAGE}, Formats: &oggPage},
{Names: []string{format.VORBIS_PACKET}, Formats: &vorbisPacket},
{Names: []string{format.OPUS_PACKET}, Formats: &opusPacket},
},
})
}
var (
vorbisIdentification = []byte("\x01vorbis")
opusIdentification = []byte("OpusHead")
)
type streamCodec int
const (
codecUnknown streamCodec = iota
codecVorbis
codecOpus
)
type stream struct {
firstBit int64
sequenceNo uint32
packetBuf []byte
packetD *decode.D
codec streamCodec
}
func decodeOgg(d *decode.D, in interface{}) interface{} {
validPages := 0
streams := map[uint32]*stream{}
streamsD := d.FieldArray("streams")
d.FieldArrayFn("pages", func(d *decode.D) {
for !d.End() {
_, dv, _ := d.FieldTryFormat("page", oggPage)
if dv == nil {
break
}
oggPageOut, ok := dv.(format.OggPageOut)
if !ok {
panic("page decode is not a oggPageOut")
}
s, sFound := streams[oggPageOut.StreamSerialNumber]
if !sFound {
var packetsD *decode.D
streamsD.FieldStructFn("stream", func(d *decode.D) {
d.FieldValueU("serial_number", uint64(oggPageOut.StreamSerialNumber), "")
packetsD = d.FieldArray("packets")
})
s = &stream{
sequenceNo: oggPageOut.SequenceNo,
packetD: packetsD,
codec: codecUnknown,
}
streams[oggPageOut.StreamSerialNumber] = s
}
// if !sFound && !oggPageOut.IsFirstPage {
// // TODO: not first page and we haven't seen the stream before
// // log.Println("not first page and we haven't seen the stream before")
// }
// hasData := len(s.packetBuf) > 0
// if oggPageOut.IsContinuedPacket && !hasData {
// // TODO: continuation but we haven't seen any packet data yet
// // log.Println("continuation but we haven't seen any packet data yet")
// }
// if !oggPageOut.IsFirstPage && s.sequenceNo+1 != oggPageOut.SequenceNo {
// // TODO: page gap
// // log.Println("page gap")
// }
for _, ps := range oggPageOut.Segments {
if s.packetBuf == nil {
s.firstBit = d.Pos()
}
// TODO: decoder buffer api that panics?
psBytes := ps.Len() / 8
// TODO: cleanup
b, _ := ps.BytesRange(0, int(psBytes))
s.packetBuf = append(s.packetBuf, b...)
if psBytes < 255 { // TODO: list range maps of demuxed packets?
bb := bitio.NewBufferFromBytes(s.packetBuf, -1)
if s.codec == codecUnknown {
if b, err := bb.PeekBytes(len(vorbisIdentification)); err == nil && bytes.Equal(b, vorbisIdentification) {
s.codec = codecVorbis
} else if b, err := bb.PeekBytes(len(opusIdentification)); err == nil && bytes.Equal(b, opusIdentification) {
s.codec = codecOpus
}
}
switch s.codec {
case codecVorbis:
// TODO: err
_, _, _ = s.packetD.FieldTryFormatBitBuf("packet", bb, vorbisPacket)
case codecOpus:
// TODO: err
_, _, _ = s.packetD.FieldTryFormatBitBuf("packet", bb, opusPacket)
case codecUnknown:
s.packetD.FieldBitBuf("packet", bb)
}
s.packetBuf = nil
}
}
s.sequenceNo = oggPageOut.SequenceNo
if oggPageOut.IsLastPage {
delete(streams, oggPageOut.StreamSerialNumber)
}
validPages++
}
})
if validPages == 0 {
d.Invalid("no pages found")
}
return nil
}